/* Copyright (c) 2018
   Charles Jordan (Hokkaido University, Sapporo, Japan),
   Michael Joswig, Lars Kastner (Technische Universitaet Berlin, Germany)

   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 3, 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.
-----------------------------------------------------------------------------
*/

//////////////////////////////////////////////////////////////////////////
// ReverseSearch.hh
// This is a refactor of a copy of SymmetricBFS.hh
// produced: 29/06/98 jr
// last change: 29/06/98 jr
/////////////////////////////////////////////////////////////////////////
#ifndef MPTRY_HH
#define MPTRY_HH

#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <cstdlib>
#include <ctime>

#include <assert.h>
#include <set>
#include <map>

#include <polymake/Main.h>
#include <polymake/Map.h>
#include <polymake/Set.h>
#include <polymake/Vector.h>


#include "PrimitiveFlipCache.hh"
#include "GKZ.hh"
#include "TreeGraph.hh"
#include "HashSet.hh"
#include "SimplicialComplex.hh"

#include "CommandlineOptions.hh"

#include "Chirotope.hh"
#include "Flip.hh"
#include "MarkedFlips.hh"
#include "TriangNode.hh"
#include "TriangFlips.hh"
#include "Symmetry.hh"
#include "RegularityCheck.hh"
#include "FineTriang.hh"

#include "mptopcom_typedefs.hh"
#include "SymmetryBlackBox.hh"

#ifdef MTS
void mtstcreturn(const TriangNode& tn);
void put_output(const TriangNode& tn, size_type count, const gkz_type& gkz);
#endif


#ifndef STL_CONTAINERS
typedef HashSet<SimplicialComplex>            triang_container_type;
typedef HashSet<TriangNode>                   orbit_type;
typedef HashMap<TriangNode, TriangFlips>      tnode_container_type; // Node->Flips
// These two types are for our new things:
#else
typedef std::set<SimplicialComplex>            triang_container_type;
typedef std::set<TriangNode>                   orbit_type;
typedef std::map<TriangNode, TriangFlips>      tnode_container_type; // Node->Flips
// These two types are for our new things:
// typedef std::tr1::unordered_set<SimplicialComplex>            triang_container_type;
// typedef std::tr1::unordered_set<TriangNode>                   orbit_type;
// typedef std::tr1::unordered_map<TriangNode, TriangFlips>      tnode_container_type; // Node->Flips

inline std::ostream& operator<<(std::ostream& ost, const tnode_container_type& tnc) {
   for (tnode_container_type::const_iterator iter = tnc.begin();
         iter != tnc.end();
         ++iter) {
      ost << iter->first << std::endl;
   }
   return ost;
}

inline std::istream& operator>>(std::istream& ist, tnode_container_type& tnc) {
   char dash;
   char arrow;

   TriangNode tn_reader;
   TriangFlips tf_reader;

   char c;

   tnc.clear();
   ist >> std::ws >> c;
   if (c == '[') {
      while (ist >> std::ws >> c) {
         if (c == ']') {
            break;
         }
         if (c == ',') {
            continue;
         }
         ist.putback(c);
         if (!(ist >> std::ws >> tn_reader)) {
#ifdef READ_DEBUG
            cerrWrap << "std::istream& operator>>(std::istream&, tnode_container_type&): "
               << "key not found." << std::endl;
#endif
            ist.clear(std::ios::failbit);
            return ist;
         }
         if (!(ist >> std::ws >> dash >> arrow)) {
#ifdef READ_DEBUG
            cerrWrap << "std::istream& operator>>(std::istream&, chirotope_data&): "
               << "`->' not found." << std::endl;
#endif
            ist.clear(std::ios::failbit);
            return ist;
         }
         if (!(ist >> std::ws >> tf_reader)) {
#ifdef READ_DEBUG
            cerrWrap << "std::istream& operator>>(std::istream&, chirotope_data&): "
               << "data not found." << std::endl;
#endif
            ist.clear(std::ios::failbit);
            return ist;
         }
         tnc[tn_reader] = tf_reader;
      }
   }
   else {
#ifdef READ_DEBUG
      cerrWrap << "std::istream& operator>>(std::istream&, tnode_container_type&): "
         << "missing `" << '[' << "'." << std::endl;
#endif
      ist.clear(std::ios::failbit);
      return ist;
   }
   ist.clear(std::ios::goodbit);
   return ist;
}

#endif





#ifdef CHECK_NEW
static tnode_container_type all_triangs;
#endif

class ReverseSearchWrapper{
   public:
      virtual ~ReverseSearchWrapper() {}

      virtual size_type totalcount() = 0;
      
      virtual TriangNode get_initial_triang(
         const parameter_type      no,
         const parameter_type      rank,
         const SimplicialComplex&  seed
         ) = 0;

      virtual TriangNode find_max_triang(const TriangNode& initial_triang) = 0;

      virtual long generic_reverse_search(const TriangNode& initial, int Delta,
            long max_nodes, long max_depth) = 0;
      
      virtual void auto_reverse_search(
          const parameter_type      no,
            const parameter_type      rank,
            const SimplicialComplex&  seed
      ) = 0;

};



template<bool trivialSymmetry, bool checkRegular=false, bool _make_tree=false, bool checkFine=false, bool smallGroup=false>
class ReverseSearch : public ReverseSearchWrapper{
   private:
      const PointConfiguration&       _pointsptr;
      const Chirotope&                _chiro;
      size_type                       _totalcount;
      size_type                       _symcount;
      // Order is important, gkz needs to be initialized before sbb and pfc.
      const gkz_type gkz;
      const PrimitiveFlipCache<trivialSymmetry, checkRegular, checkFine, smallGroup>				  pfc;
      const SymmetryBlackBox<smallGroup>                   sbb;
      pm::Vector<int>                 gkz_vector;
      TreeGraph<trivialSymmetry, checkRegular, checkFine, smallGroup>      tree;
   public:
      // results:
      inline const size_type totalcount() const { return _totalcount; }
      inline const size_type symcount()   const { return _symcount; }



      // Determine whether a triangulation has a possible upflip. If yes, then
      // the lex maximal upflip will be stored in frout and true is returned.
      // We store the flips marked by mark_flips for further use.
      bool has_upflip(const TriangNode& triangulation, Flip& frout, MarkedFlips& all_flips){
         const pmFlipContainer& up(pfc.get_upflips(triangulation));
         int s = up.size();
         if(s > 0){
            frout = (up.back()).second;
            return true;
         }
         return false;
      }

      // Determine whether v has more than j children, if yes, also return the
      // flipToChild.
      bool has_jth_child(const TriangNode& v, int j, Flip& flipToChild){
         const pmFlipContainer& down(pfc.get_downflips(v));
         if(down.size() <= j){
            return false;
         }
         // FIXME: Bad workaround for not having random access. Instead pass
         // around iterators.
         int c=0;
         for(pmFlipContainer::const_iterator flipit = down.begin();
               flipit != down.end();
               ++flipit){
            if(c==j){
               flipToChild = flipit->second;
               return true;
            }
            c++;
         }
         return true;
      }

      bool check_equality(const TriangNode& t1, const TriangNode& t2){
         if(trivialSymmetry){
            return t1 == t2;
         } else {
            return sbb.are_equivalent_triangulations(t1, t2);
         }
      }

      // This method finds the predecessor p of v and simultaneously figures
      // out what number v has as a child of p.
      TriangNode numbered_predecessor(const TriangNode& v, const pmFlipContainer& up, int& j){
         // Apply worst upflip to find predecessor
         const Flip& backFlip((up.back()).second);
         TriangNode result(0, v, backFlip);
         // Get sorted downflips of predecessor
         const pmFlipContainer& down(pfc.get_downflips(v, backFlip, result));

         // Introduce a counter to number the children
         int c = 0;
         for(pmFlipContainer::const_iterator flip = down.begin();
               flip != down.end();
               ++flip){
            if(check_equality(v, TriangNode(0, result, (*flip).second))){
               j = c;
               break;
            }
            c++;
         }
         return result;
      }
      TriangNode numbered_predecessor(const TriangNode& v, int& j){
         const pmFlipContainer& up(pfc.get_upflips(v));
         return numbered_predecessor(v, up, j);
      }
      TriangNode numbered_predecessor(const TriangNode& source, const Flip& flip, const TriangNode& v, int& j){
         const pmFlipContainer& up(pfc.get_upflips(source, flip, v));
         return numbered_predecessor(v, up, j);
      }
      
      void log_node_callback(const TriangNode& triang){
#ifdef MTS
         put_output(triang, _totalcount, gkz);
#else
         std::cout << _totalcount << ": " << triang << "pm: " 
            << gkz.gkz_as_string(triang); 
         if(!trivialSymmetry){
            std::cout << " op: " << gkz.gkz_as_string(sbb.get_canonical_representative(triang));
         }
         std::cout << std::endl;
#endif
         _totalcount++;
      }

      void log_node(const TriangNode& triang){
         log_node_callback(triang);
      }

      bool is_good_flip(const TriangNode& source, const Flip& flip, const TriangNode& target){
         int discard;
         const TriangNode& predecessor = numbered_predecessor(source, flip, target, discard);
         return check_equality(predecessor, source);
      }

      long generic_reverse_search(const TriangNode& initial, int d,
            long max_nodes, long max_depth){
         bool overbudget;
         long count = 0;
         TriangNode v(initial);
         int Delta = pfc.get_delta(v);
         //log_node(initial);
         int j=-1, depth=0;
         while((depth != 0) or (j < Delta)){
            overbudget = false;
            while(j < Delta && !overbudget){
               j++;
               TriangNode Avj;
               Flip flip;
               if(has_jth_child(v, j, flip)){
                  Avj =  TriangNode(0, v, flip);
                  if(is_good_flip(v, flip, Avj)){
                     if(_make_tree){
                        tree.add_node_safely(Avj);
                        tree.add_edge(v, Avj);
                     }
                     log_node(Avj);
                     Delta = pfc.get_delta(v, flip, Avj);
                     v = Avj;
                     count++;
                     j = -1;
                     depth++;
                     if ((max_nodes>0 && count >= max_nodes) || 
                           (max_depth>0 && depth==max_depth))
                     {
                        overbudget = true;
#ifdef MTS
                        mtstcreturn(v);
#endif
                     }
                  }
               }
            }
            if(depth > 0){
               v = numbered_predecessor(v, j);
               Delta = pfc.get_delta(v);
               depth--;
            }
         }
         return count;
      }

      /* Hall-Knuth estimate for number of nodes in subtree rooted at
       * initial using one random probe.
       * TODO: estimate number of triangulations for --regular ?
       */
      unsigned long long estimate(const TriangNode& initial){
        TriangNode v(initial);
        TriangNode Avj;
        int children[gkz.size()];
        int j=-1;
        unsigned int num;
        unsigned long long ret = 1;
        unsigned long long d = 1;
        Flip flip;

        while (1)
        {
          int Delta = pfc.get_delta(v);
          num = 0;
          j=-1;
          while (j<Delta){
            j++;
            if (has_jth_child(v, j, flip) &&
                is_good_flip(v, flip, TriangNode(0, v, flip))){
                  children[num++] = j;
            }
          }
    	  if (num == 0) /* leaf */
            return ret;
          j = rand()%num;
          has_jth_child(v, children[j], flip);
          Avj = TriangNode(0, v, flip);
          v = Avj;
          d *= num;
          ret += d;
        }
        /* unreachable */
        return ret;
      }
      
      TriangNode get_initial_triang(
         const parameter_type      no,
         const parameter_type      rank,
         const SimplicialComplex&  seed
         ){
         TriangNode result = TriangNode(_symcount, no, rank, seed);
         gkz.init_check(result);
         if (CommandlineOptions::verbose()) {
            cerrWrap << "Initial triang: " << result << std::endl;
            cerrWrap << "Orbit size: " << sbb.get_orbit_size(result) << std::endl;
            cerrWrap << "Is regular: " << gkz.isRegular(result) << std::endl;
         }
         if(checkFine && (!isFine(result))){
            SimplicialComplex fine = FineTriang(_chiro);
            result = TriangNode(_symcount, no, rank, fine);
         }
         return result;
      }

      // Find the triangulation without any further upflips.
      TriangNode find_max_triang(const TriangNode& initial){
         TriangNode result(initial);
         Flip fr;
         MarkedFlips tmp;
         while(has_upflip(result, fr, tmp)){
            result = TriangNode(0, result, fr);
         } 
         return result;
      }

      size_type totalcount(){
         return _totalcount;
      }

      // For the single threaded case:
      // 1. Create initial triangulation
      // 2. Find lex maximal triangulation
      // 3. Run reverse search
      // This method was created to avoid duplication of the constructors used
      // for mptopcom1 and mptopcom.
      void auto_reverse_search(
         const parameter_type      no,
         const parameter_type      rank,
         const SimplicialComplex&  seed
      ){
         int estimates = CommandlineOptions::estimates();

         cerrWrap << "TrivialSymmetry: " << trivialSymmetry << std::endl;
         TriangNode initial_triang = get_initial_triang(no, rank, seed);
         TriangNode lex_optimal = find_max_triang(initial_triang);
         if (estimates == 0){
           if(_make_tree){
              tree.add_node_safely(lex_optimal);
           }
           log_node(lex_optimal);
           generic_reverse_search(lex_optimal, gkz.size(), -1, -1);
           pfc.print_stat();
           if(_make_tree){
              tree.print();
              tree.print_polymake();
           }
           cerrWrap << "Totalcount: " << _totalcount << std::endl;
         }
         else{
           unsigned long long tot = 0;
           srand(time(NULL));
           for (int i=0; i<estimates; i++){
             tot += estimate(lex_optimal);
           }
           cerrWrap << "Estimate: " << tot / estimates << " nodes" << std::endl;
         }

      }

      ReverseSearch(
            const PointConfiguration& points,
            const Chirotope&          chiro,
            const SymmetryGroup&      symmetries) :
         gkz(points, chiro),
         _pointsptr(points), 
         _chiro(chiro),
         _totalcount(0), _symcount(0),
         sbb(CommandlineOptions::orbit_cache_size(), symmetries, gkz),
         pfc(chiro, gkz, CommandlineOptions::flip_cache_size(), 
         SymmetryGroup(points.no()), sbb), 
         tree(gkz, sbb, chiro, symmetries)
   {
   }

};

// template<bool trivialSymmetry, bool checkRegular=false, bool _make_tree=false, bool checkFine=false, bool smallGroup=false>
template<bool checkRegular, bool makeTree, bool checkFine, bool smallGroup>
inline ReverseSearchWrapper * constructReverseSearch_trivialSymmetries(
      const PointConfiguration& points,
      const Chirotope&          chiro,
      const SymmetryGroup&      symmetries,
      const bool                only_fine_triangs){
   bool trivialSymmetries = symmetries.size() == 0;
   trivialSymmetries |= CommandlineOptions::make_marked_tree();
   if(trivialSymmetries){
      return new ReverseSearch<true, checkRegular, makeTree, checkFine, smallGroup>(points, chiro, symmetries);
   } else {
      return new ReverseSearch<false, checkRegular, makeTree, checkFine, smallGroup>(points, chiro, symmetries);
   }
}


template<bool makeTree, bool checkFine, bool smallGroup>
inline ReverseSearchWrapper * constructReverseSearch_checkRegular(
      const PointConfiguration& points,
      const Chirotope&          chiro,
      const SymmetryGroup&      symmetries,
      const bool                only_fine_triangs){
   bool checkRegular = CommandlineOptions::check_regular();
   if(checkRegular){
      return constructReverseSearch_trivialSymmetries<true, makeTree, checkFine, smallGroup>(points, chiro, symmetries, only_fine_triangs);
   } else {
      return constructReverseSearch_trivialSymmetries<false, makeTree, checkFine, smallGroup>(points, chiro, symmetries, only_fine_triangs);
   }
}


template<bool checkFine, bool smallGroup>
inline ReverseSearchWrapper * constructReverseSearch_makeTree(
      const PointConfiguration& points,
      const Chirotope&          chiro,
      const SymmetryGroup&      symmetries,
      const bool                only_fine_triangs){
   bool makeTree = CommandlineOptions::make_tree();
   if(makeTree){
      return constructReverseSearch_checkRegular<true, checkFine, smallGroup>(points, chiro, symmetries, only_fine_triangs);
   } else {
      return constructReverseSearch_checkRegular<false, checkFine, smallGroup>(points, chiro, symmetries, only_fine_triangs);
   }
}


template<bool smallGroup>
inline ReverseSearchWrapper * constructReverseSearch_checkFine(
      const PointConfiguration& points,
      const Chirotope&          chiro,
      const SymmetryGroup&      symmetries,
      const bool                only_fine_triangs){
   bool checkFine = CommandlineOptions::fine_only();
   if(checkFine){
      return constructReverseSearch_makeTree<true, smallGroup>(points, chiro, symmetries, only_fine_triangs);
   } else {
      return constructReverseSearch_makeTree<false, smallGroup>(points, chiro, symmetries, only_fine_triangs);
   }
}


inline ReverseSearchWrapper * constructReverseSearch_smallGroup(
      const PointConfiguration& points,
      const Chirotope&          chiro,
      const SymmetryGroup&      symmetries,
      const bool                only_fine_triangs){
   bool smallGroup = true;
   bool sg = CommandlineOptions::small_group();
   bool lg = CommandlineOptions::large_group();
   int n = symmetries.card();
   cerrFirstWorker << "Group size is N = " << n << std::endl;
   if(sg && !lg){
      smallGroup = true;
   } else if (!sg && lg){
      smallGroup = false;
   } else {
      if(n > 20){
         smallGroup = false;
      }
   }
   if(smallGroup){
      return constructReverseSearch_checkFine<true>(points, chiro, symmetries, only_fine_triangs);
   } else {
      return constructReverseSearch_checkFine<false>(points, chiro, symmetries, only_fine_triangs);
   }
}


inline ReverseSearchWrapper * constructReverseSearch(
      const PointConfiguration& points,
      const Chirotope&          chiro,
      const SymmetryGroup&      symmetries,
      const bool                only_fine_triangs){
   return constructReverseSearch_smallGroup(points, chiro, symmetries, only_fine_triangs);
}



#endif

// eof ReverseSearch.hh
