#ifndef TREEGRAPH_HH
#define TREEGRAPH_HH
#include "TriangNode.hh"
#include "GKZ.hh"
#include "SymmetryBlackBox.hh"
#include "PointConfiguration.hh"

typedef std::set<std::pair<int, int> > graph_edges_type;
typedef std::map<TriangNode, int> graph_node_labels_type;


template<bool trivialSymmetry, bool checkRegular, bool checkFine, bool smallGroup>
class TreeGraph {
   private:
      const Chirotope& chiro;
      const SymmetryGroup& symmetries;
      const gkz_type& gkz;
      graph_edges_type edges;
      graph_node_labels_type node_labels;
      std::map<int, TriangNode> node_numbers;
      int nnodes;
      const SymmetryBlackBox<smallGroup>& sbb;
      const bool hasGroupAction;
   public:
      TreeGraph(const gkz_type& gkz_in,
            const SymmetryBlackBox<smallGroup>& sbb_in,
            const Chirotope& c,
            const SymmetryGroup& sg): 
         chiro(c), symmetries(sg), sbb(sbb_in), gkz(gkz_in), hasGroupAction(trivialSymmetry && !sbb.isTrivial()){
            nnodes = 0;
         }
      // Add a node to tree.
      void add_node(const TriangNode& triang){
         if(trivialSymmetry){
            node_labels[triang] = nnodes;
            node_numbers[nnodes] = triang;
            nnodes++;
         } else {
            node_labels[sbb.get_canonical_representative(triang)] = nnodes;
            node_numbers[nnodes] = sbb.get_canonical_representative(triang);
            nnodes++;
         }
      }
      // Add an edge to tree.
      void add_edge(const TriangNode& source, const TriangNode& target){
         int s, t;
         if(trivialSymmetry){
            s = node_labels[source];
            t = node_labels[target];
         } else {
            s = node_labels[sbb.get_canonical_representative(source)];
            t = node_labels[sbb.get_canonical_representative(target)];
         }
         std::pair<int, int> edge(s,t);
         edges.insert(edge);
      }


      void print(){
         for(int i=0; i<nnodes; i++){
            std::cout << i << ": Triang " << node_numbers[i];
            if(trivialSymmetry){
               std::cout << ", GKZ: " << gkz.gkz_as_string(node_numbers[i]);
               if(hasGroupAction){
                  std::cout << ", Class: " << sbb.getClassNumber(node_numbers[i]);
               }
               std::cout << std::endl;
            } else {
               std::cout << ", GKZ: " << gkz.stringify(sbb.get_canonical_gkz(node_numbers[i]));
               std::cout << std::endl;
            }
         }
      }
      bool has_node(const TriangNode& triang){
         if(trivialSymmetry){
            return node_labels.find( triang ) != node_labels.end();
         } else {
            return node_labels.find( sbb.get_canonical_representative(triang) ) != node_labels.end();
         }
      }
      // Add a node safely: First check whether it already exists, only add if
      // it does not.
      void add_node_safely(const TriangNode& triang){
         if(trivialSymmetry){
            if(!has_node(triang)){
               add_node(triang);
            }
         } else {
            if(!has_node(sbb.get_canonical_representative(triang))){
               add_node(triang);
            }
         }
      }

      void print_polymake_edges(const graph_edges_type& e){
         std::cout << "$edges = new Array<Set<Int>>([";
         for(const auto& edge :e){
            std::cout << "[" << edge.first << ", " << edge.second << "],";
         }
         std::cout << "]);" << std::endl;
         std::cout << "$g = graph_from_edges($edges);" << std::endl;

      }
      // Print first three entries of a vector
      // Output tree in polymake format
      void print_polymake_inner(){
         print_polymake_edges(edges);
         std::cout << "$coordsMatFull = new Matrix([";
         for(int i=0; i<nnodes; i++){
            if(trivialSymmetry){
               std::cout << gkz.gkz_as_string(node_numbers[i]);
            } else {
               std::cout << gkz.stringify(sbb.get_canonical_gkz(node_numbers[i]));
            }
            if(i<nnodes - 1){
               std::cout << ",";
            }
         }
         std::cout << "]);" << std::endl;
         std::cout << "$coordsMat = $coordsMatFull->minor(All, [0,1,2]);" << std::endl;
      }

      void print_polymake(){
         int r, g, b, cl;
         print_polymake_inner();
         std::cout << "$g->VISUAL(Coord=>$coordsMat";
         std::cout << ", VertexColor=>[";
         if(!hasGroupAction){
            // Make fine nodes red
            if(!checkFine){
               for(int i=0; i<nnodes; i++){
                  if(isFine(node_numbers[i])){
                     std::cout << "\'1 0 0 \'";
                  } else {
                     std::cout << "\'0 0 0 \'";
                  }
                  if(i<nnodes - 1){
                     std::cout << ", ";
                  }
               }
            } else {
               for(int i=0; i<nnodes; i++){
                  if(gkz.isRegular(node_numbers[i])){
                     std::cout << "\'1 0 0 \'";
                  } else {
                     std::cout << "\'0 0 0 \'";
                  }
                  if(i<nnodes - 1){
                     std::cout << ", ";
                  }
               }
            }
         } else {
            // Colors according to classes
            for(int i=0; i<nnodes; i++){
               cl = sbb.getClassNumber(node_numbers[i]);
               if(cl == -1){
                  cl = 0;
               }
               int n = 2;
               float nm1 = (float)(n-1);
               r = cl % n;
               g = ((cl - r)/n) % n;
               b = ((((cl - r)/n)-g)/n) % n;
               std::cout << "\'" << (((float)r)/nm1) << " " << (((float)g)/nm1) << " " << (((float)b)/nm1) << "\'";
               if(i<nnodes - 1){
                  std::cout << ", ";
               }
            }
         }
         std::cout << "]);" << std::endl;
      }

      void print_polymake_fine(){
         int r, g, b, cl;

         edges.clear();
         std::list<TriangNode> keep;
         for(int i=0; i<nnodes; i++){
            if(isFine(node_numbers[i])){
               keep.push_back(node_numbers[i]);
            }
         }
         node_numbers.clear();
         node_labels.clear();
         nnodes=0;
         for(const auto& t : keep){
            add_node_safely(t);
         }
         print();
         std::cout << "ts: " << trivialSymmetry << " " << symmetries << std::endl;
         for(int i=0; i<nnodes; i++){
            FlipData<trivialSymmetry, checkRegular, checkFine, smallGroup> fd(node_numbers[i], &chiro, gkz, symmetries, true, sbb);
            std::cout << i << ": " << fd.get_allflips().size();
            std::cout << " up: " << fd.get_upflips().size();
            std::cout << " down: " << fd.get_downflips().size();
            std::cout << std::endl;
            const pmFlipContainer& df = fd.get_downflips();
            for(const auto& flip : df){
               TriangNode target(0, node_numbers[i], flip.second);
               add_edge(node_numbers[i], target);
            }
         }
         print_polymake_edges(edges);
         std::cout << "$coordsMatFull = new Matrix([";
         for(int i=0; i<nnodes; i++){
            if(!hasGroupAction){
               std::cout << gkz.gkz_as_string(node_numbers[i]);
            } else {
               std::cout << gkz.stringify(sbb.get_canonical_gkz(node_numbers[i]));
            }
            if(i<nnodes - 1){
               std::cout << ",";
            }
         }
         std::cout << "]);" << std::endl;
         std::cout << "$coordsMat = $coordsMatFull->minor(All, [0,1,2]);" << std::endl;
         std::cout << "$g->VISUAL(Coord=>$coordsMat);" << std::endl;//, VertexColor=>[";
      }
};
#endif
