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

#ifndef FLIPDATA_HH
#define FLIPDATA_HH
#include "Pair.hh"
#include "HashSet.hh"
#include "SimplicialComplex.hh"

#include "CommandlineOptions.hh"

#include "Chirotope.hh"
#include "Circuits.hh"
#include "Flip.hh"
#include "MarkedFlips.hh"
#include "TriangNode.hh"
#include "Symmetry.hh"

// For using polymake
#include <polymake/client.h>
#include <polymake/Map.h>
#include "GKZ.hh"

#include <list>

#include "SymmetryBlackBox.hh"
#include "RegularityCheckCache.hh"
#include "GKZCache.hh"
#include "mptopcom_typedefs.hh"


// Determine the support of a triangulation, i.e. which vertices it uses.
pm::Set<int> support(const TriangNode& t){
   pm::Set<int> result;
   for(TriangNode::const_iterator triangle = t.begin();
         triangle != t.end();
         ++triangle){
      for(basis_type::const_iterator entry = (*triangle).begin();
            entry != (*triangle).end();
            ++entry){
         result += *entry;
      }
   }
   return result;
}

// Method for determining whether a triangulation is fine, i.e. uses all
// lattice points.
bool isFine(const TriangNode& t){
   int n = t.no();
   pm::Set<int> s=support(t);
   // std::cout << "N: " << n << " s: " << s.size() << " t: " << t << std::endl;
   return n == s.size();
}



template<bool trivialSymmetry, bool checkRegular, bool checkFine, bool smallGroup>
class FlipData {
   private:
      // properties:
      parameter_type   _no;
      parameter_type   _rank;
      const Chirotope&                _chiroptr;
      const gkz_type&                 gkzref;
      const SymmetryGroup&           _node_symmetries;
      const SymmetryBlackBox<smallGroup>&        sbb;
      const RegularityCheckCache&    rcc;
      const GKZCache<checkFine>&                gkzc;
      const TriangNode source_triang;
      TriangNode source_optimal;
      pmFlipContainer upflips, downflips;
      std::list<Flip> all_flips;
      // methods:
      void _remove_destroyed_flips(const Flip& flip);
      void _add_new_flips(const SimplicialComplex& restrictions,
            const bool = false);

      void insert_raw_flip(Flip flip);
      
      int is_lex_upflip(Flip flip, const TriangNode& target, gkz_vector_type& result_gkz){
         int result;
         if(trivialSymmetry){
            result_gkz = gkzc.evaluate(flip);
            result = gkzref.is_lex_positive(result_gkz);
         } else {
            const triang_vector_type optimalVector = gkzref.triangulationAsVector(target);
            result_gkz = gkzc.evaluate(source_optimal, target);
            result = gkzref.is_lex_positive(result_gkz);
            result_gkz = result_gkz | gkz_vector_type(optimalVector);
         }
         return result;

      }

      int is_lex_upflip(Flip flip, gkz_vector_type& result_gkz){
         int result;
         result_gkz = gkzc.evaluate(flip);
         result = gkzref.is_lex_positive(result_gkz);
         return result;
      }

      void check_and_insert_flip(Flip flip){
         TriangNode target = get_target_triangulation(flip);
         if(checkRegular){
            bool isRegular = rcc.isRegular(target);
            if(!isRegular){
               return;
            }
         }
         if(checkFine){
            if(!isFine(target)){
               return;
            } 
         }
         gkz_vector_type current_gkz;
         int isUpflip = is_lex_upflip(flip, target, current_gkz);
         insert_flip(flip, current_gkz, isUpflip);
      }

      void insert_flip(Flip flip, gkz_vector_type flip_gkz, int isUpflip){
         if(trivialSymmetry){
            if(isUpflip == 1){
               upflips.insert(flip_gkz, flip);
            } else {
               downflips.insert(flip_gkz, flip);
            }
         } else {
            // If there is a symmetry group acting, check for existence.
            if(isUpflip == 1){
               if(!upflips.exists(flip_gkz)){
                  upflips[flip_gkz] = flip;
               }
            } else if(isUpflip == -1){
               if(!downflips.exists(flip_gkz)){
                  downflips[flip_gkz] = flip;
               }
            }
         }
      }
      
      void populate(){
         upflips.clear();
         downflips.clear();
         for(std::list<Flip>::const_iterator flip=all_flips.begin();
               flip != all_flips.end();
               ++flip){
            check_and_insert_flip(*flip);
         }
      }

      TriangNode get_target_triangulation(const Flip& flip) const{
         if(trivialSymmetry){
            return TriangNode(0, source_triang, flip);
         } else {
            return sbb.get_canonical_representative(TriangNode(0, source_triang, flip));
         }
      }

   public:
      FlipData(
            TriangNode triang,
            const Chirotope* chiro,
            const gkz_type& gkz,
            const SymmetryGroup&      seed_symmetries,
            const SymmetryBlackBox<smallGroup>& sbb_in,
            const RegularityCheckCache& rcc_in,
            const GKZCache<checkFine>& gkzc_in
            ):
         sbb(sbb_in), rcc(rcc_in), gkzc(gkzc_in),
         _no(triang.no()), _rank(triang.rank()),
         source_triang(triang), 
         _chiroptr(*chiro),
         gkzref(gkz),
         _node_symmetries(seed_symmetries){
            if(trivialSymmetry){
               source_optimal = triang;
            } else {
               source_optimal = sbb.get_canonical_representative(triang);
            }
            _add_new_flips(source_triang);
            if(!trivialSymmetry){
               populate();
            } else if(checkRegular || checkFine){
               populate();
            }
         }

      FlipData(
            TriangNode triang,
            const Chirotope& chiro,
            const gkz_type& gkz,
            const SymmetryGroup&      seed_symmetries,
            const pmFlipContainer& uf,
            const pmFlipContainer& df,
            const std::list<Flip>& af,
            const SymmetryBlackBox<smallGroup>& sbb_in,
            const RegularityCheckCache& rcc_in,
            const GKZCache<checkFine>& gkzc_in
            ):
         sbb(sbb_in), rcc(rcc_in), gkzc(gkzc_in),
         all_flips(af),
         source_triang(triang),
         upflips(uf), downflips(df),
         _no(triang.no()), _rank(triang.rank()),
         _chiroptr(chiro),
         gkzref(gkz),
         _node_symmetries(seed_symmetries)
         {
            if(trivialSymmetry){
               source_optimal = triang;
            } else {
               source_optimal = sbb.get_canonical_representative(triang);
            }
         }

      const pmFlipContainer& get_upflips() const{
         return upflips;
      }

      const pmFlipContainer& get_downflips() const{
         return downflips;
      }


      FlipData mutate(const Flip& flip, const TriangNode& target){
         FlipData result(target, _chiroptr, gkzref, _node_symmetries, upflips, downflips, all_flips, sbb, rcc, gkzc);
         result._remove_destroyed_flips(flip);
         result._add_new_flips(flip.second);
         if(!trivialSymmetry){
            result.populate();
         } else if (checkRegular || checkFine){
            result.populate();
         }
         return result;
      }


};

template<bool trivialSymmetry, bool checkRegular, bool checkFine, bool smallGroup>
void FlipData<trivialSymmetry, checkRegular, checkFine, smallGroup>::insert_raw_flip(Flip flip){
   if(trivialSymmetry && !checkRegular && !checkFine){
      gkz_vector_type current_gkz;
      int isUpflip = is_lex_upflip(flip, current_gkz);
      insert_flip(flip, current_gkz, isUpflip);
   } else {
      all_flips.push_back(flip);
   }
}


struct is_evil {
   const Flip& flip;
   is_evil(const Flip& in) : flip(in){}
   bool operator() (const Flip& f) { return !(f.first * flip.first).is_empty(); }
};

template<bool trivialSymmetry, bool checkRegular, bool checkFine, bool smallGroup>
void FlipData<trivialSymmetry, checkRegular, checkFine, smallGroup>::_remove_destroyed_flips(const Flip& flip) {
   if(!trivialSymmetry || checkRegular || checkFine){
      all_flips.remove_if(is_evil(flip));
   } else {
      // FIXME
      // Not sure how this should be done. The problem is that we want to loop
      // over the flips, while erasing some (hopefully in an intelligent way).
      // Attempts:
      // 1. Copy the maps and loop over the copies.
      // 2. Remember the things we need to remove and remove them later. Better
      //    than 1.
      std::vector<pmFlipContainer::iterator> toRemove;
      for(pmFlipContainer::iterator upf = upflips.begin();
            upf != upflips.end();
            ++upf){
         if(!((*upf).second.first * flip.first).is_empty()){
            toRemove.push_back(upf);
         }
      }
      while(toRemove.size() > 0){
         upflips.erase(toRemove.back());
         toRemove.pop_back();
      }
      for(pmFlipContainer::iterator downf = downflips.begin();
            downf != downflips.end();
            ++downf){
         if(!((*downf).second.first * flip.first).is_empty()){
            toRemove.push_back(downf);
         }
      }
      while(toRemove.size() > 0){
         downflips.erase(toRemove.back());
         //      downflips.erase(toRemove.back());
         toRemove.pop_back();
      }
   }
}

template<bool trivialSymmetry, bool checkRegular, bool checkFine, bool smallGroup>
void FlipData<trivialSymmetry, checkRegular, checkFine, smallGroup>::_add_new_flips(
      const SimplicialComplex& restriction,
      const bool               forbid_card_change) {
   // bool forbid_vertex_removal = checkFine;
   bool forbid_vertex_removal = false;
#ifndef STL_CONTAINERS
   static HashSet<dependent_set_type> dependent_sets;
#else
   static std::set<dependent_set_type>     dependent_sets;
#endif
   static dependent_set_type          dependent_set;
#ifndef STL_CONTAINERS
   static SimplicialComplex           done_set;
#else
   static std::set<Simplex>                done_set;
#endif

   const Simplex tn_support(source_triang.support());
   const Simplex groundset(Permutation(_no, _no));
   const Simplex missing_vertices(groundset - tn_support);
   // any new flip in tn must contain one of the flipped-in simplices:
   size_type countdown(restriction.card());
   for (SimplicialComplex::const_iterator iter1 = restriction.begin();
         iter1 != restriction.end();
         ++iter1) {
      --countdown;
      const Simplex& simp1(*iter1);
      if(
#ifndef STL_CONTAINERS
            done_set.contains(simp1, _rank)
#else
            done_set.find(simp1) != done_set.end()
#endif
        ){
         // processed already:
         continue;
      }
#ifndef STL_CONTAINERS
      done_set.insert(simp1, _rank);
#else
      done_set.insert(simp1);
#endif
      if (CommandlineOptions::verbose() && (_no > 50)) {
         std::cerr << "... still " << countdown << " simplices to check for flips ..." << std::endl;
      }
      // search for missing interior points:
      for (Simplex::const_iterator missing_iter = missing_vertices.begin();
            missing_iter != missing_vertices.end();
            ++missing_iter) {
         dependent_set = simp1 + *missing_iter;
         if(
#ifndef STL_CONTAINERS
               dependent_sets.member(dependent_set)
#else
               dependent_sets.find(dependent_set) != dependent_sets.end()
#endif
           ){
            // processed already:
            continue;
         }
         // try to build a flip from dependent_set:
         const FlipRep fliprep(_chiroptr, dependent_set, source_triang);
         if (fliprep) {
            // succeeded:
            if (forbid_vertex_removal && fliprep.kills_vertex()) {
               continue;
            }
            if (forbid_card_change && !fliprep.is_balanced()) {
               continue;
            }
            // insert flip:
            // _flips[fliprep] = false;
            insert_raw_flip(Flip(source_triang, fliprep));
            dependent_sets.insert(dependent_set);
         }
         else {
            // save processed dependent set:
            dependent_sets.insert(dependent_set);
         }      
      }
      int count(0);
      // search for adjacent simplices:
      for (SimplicialComplex::const_iterator iter2 = source_triang.begin();
            iter2 != source_triang.end();
            ++iter2) {
         const Simplex& simp2(*iter2);
         dependent_set = (simp1 + simp2);
         if (dependent_set.card() != _rank + 1) {
            continue;
         }
         // simp2 is adjacent to simp1:
         if(
#ifndef STL_CONTAINERS
               dependent_sets.member(dependent_set)
#else
               dependent_sets.find(dependent_set) != dependent_sets.end()
#endif
           ){
            // processed already:
            continue;
         }
         // try to build a flip from dependent_set:
         const FlipRep fliprep(_chiroptr, dependent_set, source_triang);
         if (fliprep) {
            // succeeded:
            if (forbid_vertex_removal && fliprep.kills_vertex()) {
               continue;
            }
            // 	else if (CommandlineOptions::reduce_points() && !fliprep.kills_vertex()) {
            // 	  continue;
            // 	}
            else if (CommandlineOptions::dont_add_points() && fliprep.adds_vertex()) {
               continue;
            }
            // insert flip:
            // _flips[fliprep] = false;
            insert_raw_flip(Flip(source_triang, fliprep));
            dependent_sets.insert(dependent_set);
         }
         else {
            // save processed dependent set:
            dependent_sets.insert(dependent_set);
         }
         if (++count > _rank) {
            // we have all neighbors already:
            break;
         }
      }
   }
   dependent_sets.clear();
   done_set.clear();
   if (!forbid_vertex_removal
         && CommandlineOptions::neighborcount() 
#ifndef STL_FLIPS
         // && (_flips.load() < _no - _rank)
#else
         // && (_flips.size() < _no - _rank)
#endif
      ){
      std::cerr << "triangulation" << std::endl
         << source_triang << std::endl;
#ifndef STL_FLIPS
      // << "has only " << _flips.load() << " flips." << std::endl;
#else
      // << "has only " << _flips.size() << " flips." << std::endl;
#endif
   }
}
#endif
