///////////////////////////////////////////////////////////////////////////////
// TriangulationOptimizer.hh
//
// This object computes the canonical (i.e. lex largest) class of a
// triangulation.
//
// The basic principle is the following: The GroupDecomposer decomposes the
// group in a way that allows one to find the canonical representative with a
// fast DFS. The TriangulationOptimizer object encapsulates this DFS.
///////////////////////////////////////////////////////////////////////////////

#ifndef TRIANGULATIONOPTIMIZER_HH
#define TRIANGULATIONOPTIMIZER_HH

#include "GroupDecomposer.hh"
#include "mptopcom_typedefs.hh"
#include "FixedGKZOptimizer.hh"
#include <polymake/Bitset.h>

class TriangulationData {
private:
   TriangNode triangulation;
   gkz_vector_type gkz;
   std::map<Scalar, pm::Bitset > sorted_gkz;

   // Produce partition of gkz vector into parts with the same value. Saved
   // in a polymake map, since then it is sorted by value.
   void filter_gkz(){
      sorted_gkz.clear();
      for(int i=0; i<gkz.dim(); i++){
         sorted_gkz[0-gkz[i]] += i;
      }
   }

   public:
      TriangulationData(TriangulationData&& td) = default;
      TriangulationData(const TriangNode& t, const gkz_vector_type& g):
      triangulation(t), gkz(g){
         filter_gkz();
      }
      TriangulationData mutate(const gp_elem* s) const{
         gkz_vector_type tmp(gkz);
         for(int i=0; i<s->n(); i++){
            tmp[(*s)[i]] = gkz[i];
         }
         return TriangulationData((*s)(triangulation), tmp);
      }
      TriangNode&& get_triang() {
         return std::move(triangulation);
      }
      const gkz_vector_type& get_gkz() const{
         return gkz;
      }
      gkz_vector_type&& move_gkz() {
         return std::move(gkz);
      }
      const std::map<Scalar, pm::Bitset >& get_sorted_gkz() const{
         return sorted_gkz;
      }

};

class TriangulationOptimizer {
   private:
      TriangNode optimal;
      gkz_vector_type optimal_gkz;
      const GroupDecomposer& gd;
      const gkz_type& gkz;


      // Find lex largest representative of class that also lex maximizes the
      // gkz vector.
      void optimize(){
         optimize_gkz(std::move(TriangulationData(optimal, optimal_gkz)));
         optimize_representative();
      }

      // Every set in result corresponds to a subset of indices of the gkz
      // vector with the same value. These need to be stabilized if we want to
      // change the representative while preserving the gkz vector.
      const gkz_vector_type signature() const{
         gkz_vector_type result(optimal_gkz.dim());
         pm::Map<Scalar, int> numbers, count;
         for(int i=0; i<result.dim(); i++){
            if(numbers.exists(optimal_gkz[i])){
               result[i] = numbers[optimal_gkz[i]];
               count[optimal_gkz[i]]++;
            } else {
               result[i] = i;
               numbers[optimal_gkz[i]] = i;
               count[optimal_gkz[i]] = 1;
            }
         }
         return result;
      }


      // Find a representative of the given class with lex largest gkz vector.
      void optimize_gkz(TriangulationData td){
         int fixed = 0, bound = gd.get_bound();
         std::stack<std::list<const gp_elem*>> switchStack;
         std::stack<std::list<const gp_elem*>::const_iterator> iteratorStack;
         std::stack<TriangulationData> tdStack;
         //std::cout << "Creation ok" << std::endl;
         tdStack.push(std::move(td));
         while(tdStack.size()>0){
            const TriangulationData& current = tdStack.top();
            if(fixed > gd.get_bound()){
               // Is leaf
               update_optimal(tdStack.top());
               tdStack.pop();
               fixed--;
               //std::cout << "Found leaf." << std::endl;
            } else {
               // Are the switches already computed?
               if(tdStack.size() > switchStack.size()){
                  std::list<const gp_elem*> switches;
                  find_next_switches(fixed, switches, tdStack.top());
                  switchStack.push(std::move(switches));
                  iteratorStack.push(switchStack.top().begin());
               }
               // Are we at the end?
               if(iteratorStack.top() == switchStack.top().end()){
                  //std::cout << "At end." << std::endl;
                  iteratorStack.pop();
                  switchStack.pop();
                  tdStack.pop();
                  fixed--;
               } else {
                  // Descent
                  const gp_elem* g = *(iteratorStack.top());
                  //std::cout << "Applying " << *g << std::endl;
                  tdStack.push(std::move(tdStack.top().mutate(g)));
                  ++iteratorStack.top();
                  fixed++;
               }
            }

         }
      }

      // Find the lex largest representative, while keeping the gkz-vector.
      void optimize_representative(){
         gkz_vector_type sig = signature();
         NewTRO ntro(optimal, gd, gkz, sig);
         optimal = ntro.get_optimal();
      }


      // Check whether there are group elements that we can apply to make gkz
      // vector lex better.
      void find_next_switches(int fixed, std::list<const gp_elem*>& switches, const TriangulationData& td){
         std::map<Scalar,pm::Bitset>::const_iterator desired = td.get_sorted_gkz().begin();
         while(!gd.has_switches(fixed, desired->second, switches)){
            ++desired;
         }
      }

      // Check whether current entry is lex better than saved optimal entry, if
      // yes, update.
      void update_optimal(TriangulationData& td){
         // std::cout << "Updating: cur " << gd.stringify(current_gkz) << " vs opt: " << gd.stringify(optimal_gkz) << std::endl;
         if(1 == lex_compare(td.get_gkz(), optimal_gkz)){
            // FIXME
            // This is probably too much copying.
            optimal = std::move(td.get_triang());
            optimal_gkz = std::move(td.move_gkz());
         }
      }

   public:
      TriangulationOptimizer(const TriangNode& t, 
         const gkz_vector_type& gkzt, 
         const GroupDecomposer& gd_in, 
         const gkz_type& gkz_in) :
         optimal(t), optimal_gkz(gkzt),
         gd(gd_in), gkz(gkz_in)
   { }
      
      const TriangNode& get_optimal(){
         gd.get_bound();
         optimize();
         return optimal;
      }

      const gkz_vector_type& get_optimal_gkz(){
         return optimal_gkz;
      }

};

#endif

