/* Copyright (c) 1997-2018
   Ewgenij Gawrilow, Michael Joswig (Technische Universitaet Berlin, Germany)
   http://www.polymake.org

   This program is free software; you can redistribute it and/or modify it
   under the terms of the GNU General Public License as published by the
   Free Software Foundation; either version 2, or (at your option) any
   later version: http://www.gnu.org/licenses/gpl.txt.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.
--------------------------------------------------------------------------------
*/

#include "polymake/perl/glue.h"
#include "polymake/perl/macros.h"
#include "polymake/perl/wrappers.h"

#include "polymake/FacetList.h"
#include "polymake/Heap.h"
#include "polymake/Set.h"

namespace pm { namespace perl {

class SchedulerHeap : public fl_internal::Table
{
public:
   typedef fl_internal::Table super;

   class ChainAgent : public fl_internal::facet
   {
   public:
      explicit ChainAgent() : heap_index(-1) {}

      void zero_weight(int max_weight)
      {
         std::fill(weight+0, weight+max_weight+1, 0);
      }
      void copy_weight(const ChainAgent* ancestor, int max_weight)
      {
         std::copy(ancestor->weight+0, ancestor->weight+max_weight+1, weight+0);
      }

      const fl_internal::Facet& as_vertex_set() const
      {
         return reinterpret_cast<const fl_internal::Facet&>(static_cast<const fl_internal::facet&>(*this));
      }

      int heap_index;
      int weight[1];
   };

   class weight_comparator
   {
   public:
      typedef ChainAgent first_argument_type;
      typedef ChainAgent second_argument_type;
      typedef int result_type;

      explicit weight_comparator(int max_weight_arg)
         : max_weight(max_weight_arg) {}

      result_type operator() (const ChainAgent& c1, const ChainAgent& c2) const
      {
         int diff=0;
         for (int i=0; i<=max_weight; ++i) {
            diff=c1.weight[i] - c2.weight[i];
            if (diff != 0) break;
         }
         return diff;
      }

      int get_max_weight() const { return max_weight; }

   private:
      const int max_weight;
   };

   class HeapPolicy {
   public:
      typedef SV* value_type;
      typedef ChainAgent key_type;
      typedef weight_comparator key_comparator_type;

      explicit HeapPolicy(int max_weight_arg)
         : wt_cmp(max_weight_arg) {}

      static const key_type& key(value_type sv)
      {
         return *extract_agent(sv);
      }

      const key_comparator_type& key_comparator() const { return wt_cmp; }

      static int position(value_type sv)
      {
         return extract_agent(sv)->heap_index;
      }

      static void update_position(value_type sv, int old_pos, int new_pos)
      {
         ChainAgent* agent=extract_agent(sv);
         assert(agent->heap_index==old_pos);
         agent->heap_index=new_pos;
      }

   private:
      const weight_comparator wt_cmp;
   };

   typedef Heap<HeapPolicy> heap_t;

   SchedulerHeap(pTHX_ int max_weight, int n_props)
      : super(sizeof(ChainAgent) + sizeof(int) * max_weight, n_props)
      , heap(HeapPolicy(max_weight), 64)
      , PmPerlInterpreterMemberInit(pi)
        tell_dropped(nullptr)
      , chain_in_progress(nullptr)
      , ancestor_agent(nullptr)
      , tentative_agent(nullptr)
      , popcount(0)
      , maxsize(0)
   {}

   ~SchedulerHeap() { kill_chains(); }

   void clear();

   SV* pop() { ++popcount; return heap.pop(); }
   bool push(SV* chain);

   const heap_t::queue_t& get_queue() const { return heap.get_queue(); }

   int get_max_weight() const { return heap.key_comparator().get_max_weight(); }
   size_t get_popcount() const { return popcount; }
   size_t get_maxsize() const { return maxsize; }
   size_t get_totalsize() const { return _size; }

   void new_tentative_agent(SV* chain);

   ChainAgent* get_tentative_agent() { return tentative_agent; }

   void clear_vertex_filter() { vertex_filter.clear(); }

   void set_tell_dropped(SV* sub) { tell_dropped=sub; }

   static int RuleChain_agent_index;
   static SV* class_descr;

   static ChainAgent* extract_agent(SV* sv)
   {
      sv=PmArray(sv)[RuleChain_agent_index];
      assert(SvIOK_UV(sv));
      return INT2PTR(ChainAgent*, SvUVX(sv));
   }

   static ChainAgent* extract_agent_wary(SV* sv, bool deref=true)
   {
      if (deref) sv=SvRV(sv);
      sv=AvARRAY(sv)[RuleChain_agent_index];
      if (SvIOK_UV(sv))
         return INT2PTR(ChainAgent*, SvUVX(sv));
      else
         return nullptr;
   }

   void add_to_vertex_filter(AV* set_list);

   bool is_promising(AV* set_list);

#if POLYMAKE_DEBUG
   void sanity_check() const;
#endif
private:
   void kill_chains();

   heap_t heap;
   Set<int> vertex_filter;
   Set<int> tentative_vertex_set;

   PmPerlInterpreterMemberDecl(const pi);
   SV* tell_dropped;
   SV* chain_in_progress;
   ChainAgent* ancestor_agent;
   ChainAgent* tentative_agent;
   size_t popcount, maxsize;
};

void SchedulerHeap::new_tentative_agent(SV* chain)
{
   if (!tentative_agent)
      tentative_agent=new(facet_alloc.allocate()) ChainAgent();

   if ((ancestor_agent=extract_agent_wary(chain)) != nullptr) {
      tentative_agent->copy_weight(ancestor_agent, get_max_weight());
      tentative_vertex_set=ancestor_agent->as_vertex_set();
   } else {
      // first descendant of the initial chain
      tentative_agent->zero_weight(get_max_weight());
      tentative_vertex_set.clear();
   }

   chain_in_progress=SvRV(chain);
}

bool SchedulerHeap::push(SV* chain)
{
   dTHXa(pi);
   if (SvRV(chain) != chain_in_progress) return false;
   SV* agent_sv=PmArray(chain)[RuleChain_agent_index];
   sv_setuv(agent_sv, PTR2UV(tentative_agent));
   SvIsUV_on(agent_sv);

   for (fl_internal::subset_iterator<Set<int>, false> subs_it=findSubsets(tentative_vertex_set, std::false_type());
        !subs_it.at_end();  ++subs_it) {
      const ChainAgent* smaller_chain_agent=static_cast<const ChainAgent*>(subs_it.operator->());
      if (smaller_chain_agent != ancestor_agent &&
          heap.key_comparator()(*smaller_chain_agent, *tentative_agent) >= 0) {
         // found an inferior chain with the same or less new properties and equal or higher weight
         if (smaller_chain_agent->heap_index >= 0) {
            SV* smaller_chain=heap.erase_at(smaller_chain_agent->heap_index);
            if (tell_dropped) {
               PmStartFuncall(1);
               PUSHs(smaller_chain);
               PUTBACK;
               glue::call_func_void(aTHX_ tell_dropped);
            }
            SvREFCNT_dec(smaller_chain);
         }
         erase_facet(*smaller_chain_agent);
      }
   }

   push_back_new_facet(tentative_agent);
   insert_cells(tentative_agent, entire(tentative_vertex_set));

   heap.push(SvREFCNT_inc_simple_NN(chain));
   assign_max(maxsize, heap.get_queue().size());

   chain_in_progress=nullptr;
   ancestor_agent=nullptr;
   tentative_agent=nullptr;
   tentative_vertex_set.clear();

   return true;
}

void SchedulerHeap::kill_chains()
{
   dTHXa(pi);
   for (auto q_it=entire(heap.get_queue());  !q_it.at_end();  ++q_it)
      SvREFCNT_dec(*q_it);
}

void SchedulerHeap::clear()
{
   kill_chains();
   super::clear_facets();
   heap.clear();
   chain_in_progress=nullptr;
   ancestor_agent=nullptr;
   tentative_agent=nullptr;
   popcount=0;
   maxsize=0;
}

void SchedulerHeap::add_to_vertex_filter(AV* set_list)
{
   for (SV **setref_p=AvARRAY(set_list), **setref_last=setref_p+AvFILLp(set_list);  setref_p<=setref_last;  ++setref_p) {
      AV* vertex_list=(AV*)SvRV(*setref_p);
      for (SV **vertex_p=AvARRAY(vertex_list), **vertex_last=vertex_p+AvFILLp(vertex_list);  vertex_p<=vertex_last;  ++vertex_p) {
         vertex_filter += SvIVX(*vertex_p);
      }
   }
}

bool SchedulerHeap::is_promising(AV* set_list)
{
   const int ancestor_vertices=tentative_vertex_set.size();

   for (SV **setref_p=AvARRAY(set_list), **setref_last=setref_p+AvFILLp(set_list);  setref_p<=setref_last;  ++setref_p) {
      AV* vertex_list=(AV*)SvRV(*setref_p);
      for (SV **vertex_p=AvARRAY(vertex_list), **vertex_last=vertex_p+AvFILLp(vertex_list);  vertex_p<=vertex_last;  ++vertex_p) {
         const int v=SvIVX(*vertex_p);
         if (!vertex_filter.contains(v))
            tentative_vertex_set += v;
      }
   }
   if (tentative_vertex_set.size() == ancestor_vertices)
      // no new properties added - no progress
      return false;

   for (fl_internal::superset_iterator ss_it=findSupersets(tentative_vertex_set, false);
        !ss_it.at_end();  ++ss_it) {
      if (heap.key_comparator()(static_cast<const ChainAgent&>(*ss_it), *tentative_agent) <= 0)
         // found another chain with at least the same new properties and weight not exceeding that of the new chain
         return false;
   }

   return true;
}

#if POLYMAKE_DEBUG
void SchedulerHeap::sanity_check() const
{
   dTHXa(pi);
   int pos=0;
   for (auto q_it=entire(heap.get_queue());  !q_it.at_end();  ++q_it, ++pos) {
      const ChainAgent* agent=extract_agent_wary(*q_it);
      if (!agent || agent->heap_index != pos) {
         Perl_croak(aTHX_ "corruption in element %d", pos);
      }
   }
   if (!heap.sanity_check()) {
      Perl_croak(aTHX_ "corruption in heap");
   }
}
#endif

int SchedulerHeap::RuleChain_agent_index=0;
SV* SchedulerHeap::class_descr=nullptr;

} }

using namespace pm::perl;
using namespace pm::perl::glue;

#define RetrieveHeapArg(...) \
   MAGIC* mg=get_cpp_magic(SvRV(self)); \
   __VA_ARGS__ SchedulerHeap* heap=reinterpret_cast<__VA_ARGS__ SchedulerHeap*>(mg->mg_ptr)

MODULE = Polymake::Core::Scheduler::Heap           PACKAGE = Polymake::Core::Scheduler::Heap

PROTOTYPES: DISABLE

void
new(pkg, max_weight, n_props)
   SV* pkg;
   I32 max_weight;
   I32 n_props;
PPCODE:
{
   if (!SchedulerHeap::class_descr) {
      // this initialization can't be done in the BOOT block because CPlusPlus is not yet initialized that early
      SchedulerHeap::class_descr=OpaqueClassRegistrator<SchedulerHeap>::register_it("Polymake::Core::Scheduler::Heap", nullptr);
      SchedulerHeap::RuleChain_agent_index=CvDEPTH(get_cv("Polymake::Core::Scheduler::TentativeRuleChain::heap_agent", false));
   }
   SV* ref=newSV_type(SVt_NULL);
   MAGIC* mg=glue::allocate_canned_magic(aTHX_ ref, SchedulerHeap::class_descr, value_alloc_magic, 0);
   new(mg->mg_ptr) SchedulerHeap(aTHX_ max_weight, n_props);
   PUSHs(sv_2mortal(ref));
   PERL_UNUSED_ARG(pkg);
}

void
size(self)
   SV* self;
PPCODE:
{
   dTARGET;
   RetrieveHeapArg(const);
   PUSHi(heap->get_queue().size());
}

void
maxsize(self)
   SV* self;
PPCODE:
{
   dTARGET;
   RetrieveHeapArg(const);
   PUSHi(heap->get_maxsize());
}

void
totalsize(self)
   SV* self;
PPCODE:
{
   dTARGET;
   RetrieveHeapArg(const);
   PUSHi(heap->get_totalsize());
}

void
popcount(self)
   SV* self;
PPCODE:
{
   dTARGET;
   RetrieveHeapArg(const);
   PUSHi(heap->get_popcount());
}

void
reset(self)
   SV* self;
PPCODE:
{
   RetrieveHeapArg();
   heap->clear();
}

void
pop(self)
   SV* self;
PPCODE:
{
   RetrieveHeapArg();
   if (!heap->get_queue().empty()) {
      SV* chain_ref=heap->pop();
      PUSHs(sv_2mortal(chain_ref));
   } else {
      PUSHs(&PL_sv_undef);
   }
}

void
new_tentative_chain(self, chain)
   SV* self;
   SV* chain;
PPCODE:
{
   RetrieveHeapArg();
   heap->new_tentative_agent(chain);
}

void
add_weight(self, wt_ref)
   SV* self;
   SV* wt_ref;
PPCODE:
{
   RetrieveHeapArg();
   SchedulerHeap::ChainAgent* agent=heap->get_tentative_agent();
   const int major=SvIVX(PmArray(wt_ref)[0]);
   const int minor=SvIVX(PmArray(wt_ref)[1]);
   agent->weight[heap->get_max_weight()-major] += minor;
}

void
push(self, chain)
   SV* self;
   SV* chain;
PPCODE:
{
   RetrieveHeapArg();
   if (!heap->push(chain))
      Perl_croak(aTHX_ "Scheduler::Heap - wrong usage: push() without preceding new_tentative_chain()");
}

void
clear_vertex_filter(self)
   SV* self;
PPCODE:
{
   RetrieveHeapArg();
   heap->clear_vertex_filter();
}

void
add_to_vertex_filter(self, set_list_ref)
   SV* self;
   SV* set_list_ref;
PPCODE:
{
   if (!SvROK(set_list_ref) ||
       (set_list_ref=SvRV(set_list_ref), SvTYPE(set_list_ref) != SVt_PVAV) ||
       (AvFILLp((AV*)set_list_ref)<0))
      croak_xs_usage(cv, "[ non-empty list ]");

   RetrieveHeapArg();
   heap->add_to_vertex_filter((AV*)set_list_ref);
}

void
is_promising(self, set_list_ref)
   SV* self;
   SV* set_list_ref;
PPCODE:
{
   if (!SvROK(set_list_ref) ||
       (set_list_ref=SvRV(set_list_ref), SvTYPE(set_list_ref) != SVt_PVAV) ||
       (AvFILLp((AV*)set_list_ref)<0))
      croak_xs_usage(cv, "[ non-empty list ]");

   RetrieveHeapArg();
   if (heap->is_promising((AV*)set_list_ref))
      PUSHs(&PL_sv_yes);
   else
      PUSHs(&PL_sv_no);
}

void
unpack_weight(self, chain)
   SV* self;
   SV* chain;
PPCODE:
{
   RetrieveHeapArg(const);
   const SchedulerHeap::ChainAgent* agent=SchedulerHeap::extract_agent_wary(chain);
   if (agent) {
      const int limbs=heap->get_max_weight()+1;
      EXTEND(SP, limbs);
      for (const int *l=agent->weight+0, *end=l+limbs; l<end; ++l)
         PUSHs(sv_2mortal(newSViv(*l)));
   }
}

void
describe_facet(self, chain)
   SV* self;
   SV* chain;
PPCODE:
{
   PERL_UNUSED_ARG(self);
   const SchedulerHeap::ChainAgent* agent=SchedulerHeap::extract_agent_wary(chain);
   if (agent) {
      EXTEND(SP, agent->size() + 1);
      mPUSHi(agent->get_id());
      for (auto cell_it=agent->begin();  !cell_it.at_end();  ++cell_it)
         mPUSHi(cell_it.index());
   }
}

void
tell_dropped(self, subref)
   SV* self;
   SV* subref;
PPCODE:
{
   if (!SvROK(subref) || (subref=SvRV(subref), SvTYPE(subref) != SVt_PVCV))
      croak_xs_usage(cv, "\\&sub");
   RetrieveHeapArg();
   heap->set_tell_dropped(subref);
}

=pod
// Local Variables:
// mode:C++
// c-basic-offset:3
// indent-tabs-mode:nil
// End:
=cut
