/* 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_CORE_HH
#define FLIPDATA_CORE_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 "mptopcom_typedefs.hh"
#include "FlipChecker.hh"

template<typename FlipChecker, typename APCType, typename FlipDataSpec>
class FlipData_enhanced;

template<typename FlipChecker, typename APCType>
class FlipDataCyclic;

template<typename FlipChecker, typename APCType>
class FlipDataSimple;

template<typename FlipChecker, typename APCType>
class FlipDataSym;

template<typename FlipChecker, typename APCType>
class FlipDataNoSym;


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<typename FlipChecker, typename APCType, typename FlipDataSpec>
class FlipData_core {
   private:
      using pmFlipContainer = typename APCType::pmFlipContainer;
      // properties:
      const APCType&    apc;
      parameter_type   _no;
      parameter_type   _rank;
      const Chirotope&                _chiroptr;
      TriangNode source_triang;
      pmFlipContainer upflips, downflips;
      const FlipChecker& fc;
      
      friend class ::FlipDataSimple<FlipChecker, APCType>;
      friend class ::FlipDataSym<FlipChecker, APCType>;
      friend class ::FlipDataNoSym<FlipChecker, APCType>;
      friend class ::FlipData_enhanced<FlipChecker, APCType, FlipDataSym<FlipChecker, APCType>>;
      friend class ::FlipData_enhanced<FlipChecker, APCType, FlipDataNoSym<FlipChecker, APCType>>;
      
      void _add_new_flips(const SimplicialComplex& restrictions,
            const bool = false);
      
      void insert_raw_flip(Flip flip){
         static_cast<FlipDataSpec*>(this)->insert_raw_flip(flip);
      }
      
      void mutate_core(const Flip& flip){
         static_cast<FlipDataSpec*>(this)->_remove_destroyed_flips(flip);
         _add_new_flips(flip.second);
      }
      
      // Mutation constructor, should not be public
      FlipData_core(
            const APCType& apc_in,
            TriangNode triang,
            const pmFlipContainer& uf,
            const pmFlipContainer& df,
            const FlipChecker& fc_in
            ):
         apc(apc_in),
         source_triang(triang),
         upflips(uf), downflips(df),
         _no(triang.no()), _rank(triang.rank()),
         _chiroptr(apc.get_chiro()),
         fc(fc_in)
         {}


      FlipData_core(
            const APCType& apc_in,
            TriangNode triang,
            const FlipChecker& fc_in
            ):
         apc(apc_in),
         _no(triang.no()), _rank(triang.rank()),
         source_triang(triang), 
         _chiroptr(apc.get_chiro()),
         fc(fc_in){
         }
   
   public:
      const pmFlipContainer& get_upflips() const{
         return upflips;
      }

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

};




template<typename FlipChecker, typename APCType, typename FlipDataSpec>
void FlipData_core<FlipChecker, APCType, FlipDataSpec>::_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

