///////////////////////////////////////////////////////////////////////////////
// SymmetryBlackBox.hh
// 
// This file helps us deal with the symmetry group. It contains methods to
// check equivalence of triangulations, compute a canonical representative,
// etc.
//
// Furthermore it contains caching mechanisms for some of the computations.
///////////////////////////////////////////////////////////////////////////////
#ifndef SYMMETRYBLACKBOX_HH
#define SYMMETRYBLACKBOX_HH
#include "TriangNode.hh"
#include "Symmetry.hh"
#include "LRUCache.hh"
#include "GKZ.hh"
#include "mptopcom_typedefs.hh"
#include "GroupDecomposer.hh"
#include "TriangulationOptimizer.hh"
#include "FixedGKZOptimizer.hh"

#include "polymake/group/permlib_tools.h"

template<bool smallGroup>
class SymmetryBlackBox {
   private:
      mutable int current=0;
      mutable LRUCache<TriangNode, TriangNode> canonicalCache;
      mutable LRUCache<TriangNode, gkz_vector_type> canonicalGKZCache;
      mutable LRUCache<TriangNode, int> classCache;
      const SymmetryGroup& group;
      std::list<Symmetry> group_elements;
      const gkz_type& gkz;
      const GroupDecomposer gd;

   public:
      SymmetryBlackBox(int size, const SymmetryGroup& gp, const gkz_type& gkz_in):
         canonicalGKZCache(size), canonicalCache(size),
         classCache(size), group(gp), gkz(gkz_in), gd(gp, gkz_in)
         {
            for(SymmetryGroup::const_iterator sym_iter = group.begin();
                  sym_iter != group.end();
                  ++sym_iter){
               Symmetry g(*sym_iter);
               group_elements.push_back(g);
            }
         }

      bool isTrivial() const{
         return group_elements.size() == 0;
      }

      TriangNode get_canonical_representative_inner(const TriangNode& t) const{
         if(group.card() == 0){
            return t;
         } else {
            if(smallGroup){
               // If the group is small, it is cheaper to just iterate over all
               // of it.
               TriangNode result(t), tmp;
               gkz_vector_type result_gkz(gkz.evaluate(t)), tmp_gkz;
               triang_vector_type result_tvec(gkz.triangulationAsVector(t)), tmp_tvec;
               for(const auto& g:group_elements){
                  tmp = g(t);
                  tmp_gkz = gkz.evaluate(tmp);
                  int comp = lex_compare(tmp_gkz, result_gkz);
                  if(comp == 1){
                     result = tmp;
                     result_gkz = tmp_gkz;
                     result_tvec = gkz.triangulationAsVector(tmp);
                  } else if (comp == 0){
                     tmp_tvec = gkz.triangulationAsVector(tmp);
                     if(result_tvec < tmp_tvec){
                        result = tmp;
                        result_gkz = tmp_gkz;
                        result_tvec = tmp_tvec;
                     }
                  }
               }
               return result;
            } else {
               TriangulationOptimizer td(t, gkz.evaluate(t), gd, gkz);
               TriangNode result(td.get_optimal());
               if(!canonicalGKZCache.exist(result)){
                  canonicalGKZCache.put(result, td.get_optimal_gkz());
               }
               return result;
            }
         }
      }

      int get_orbit_size(const TriangNode& t) const{
         pm::Set<TriangNode> eqclass;
         eqclass += t;
         for(std::list<Symmetry>::const_iterator g = group_elements.begin();
               g != group_elements.end();
               ++g){
            eqclass += ((*g)(t));
         }
         return eqclass.size();
      }


      const TriangNode& get_canonical_representative(const TriangNode& t) const{
         if(canonicalCache.exist(t)){
            return canonicalCache.get(t);
         } else {
            TriangNode result = get_canonical_representative_inner(t);
            canonicalCache.put(t, result);
            return canonicalCache.get(t);
         }
      }

      const gkz_vector_type& get_canonical_gkz(const TriangNode& t) const{
         const TriangNode& key(get_canonical_representative(t));
         if(canonicalGKZCache.exist(key)){
            return canonicalGKZCache.get(key);
         } else {
            gkz_vector_type result(gkz.evaluate(key));
            canonicalGKZCache.put(key, result);
            return canonicalGKZCache.get(key);
         }
      }

      // Check equivalence by checking equality of the canonical representative.
      bool are_equivalent_triangulations(const TriangNode& t1, const TriangNode& t2) const{
         return get_canonical_representative(t1) == get_canonical_representative(t2);
      }


      int getClassNumber(const TriangNode& t) const{
         const TriangNode& canonical = get_canonical_representative(t);
         if(classCache.exist(canonical)){
            return classCache.get(canonical);
         } else {
            classCache.put(canonical, current);
            current++;
            return classCache.get(canonical);
         }
      }

      void put(const TriangNode& t){
         classCache.put(t, current);
         current++;
      }
      
};
#endif
