#ifndef AUGMENTED_POINT_CONFIGURATION_HH
#define AUGMENTED_POINT_CONFIGURATION_HH

#include "CommandlineOptions.hh"
#include "Chirotope.hh"
#include "Symmetry.hh"
#include "GKZ.hh"
#include "SymmetryBlackBox.hh"
#include "PointConfiguration.hh"
#include "Flip.hh"

template<typename checkFine, typename smallGroup, typename lexMinimal>
class AugmentedPointConfigurationNoSym;
      
template<typename checkFine, typename smallGroup, typename lexMinimal>
class AugmentedPointConfigurationSym;

template<typename checkFine, typename smallGroup, typename lexMinimal, typename APCSpec>
class AugmentedPointConfiguration_core {

   private:
      const PointConfiguration&           points;
      const Chirotope&                    chiro;
      const SymmetryGroup&                symmetries;
      const gkz_type                      gkz;

      friend class ::AugmentedPointConfigurationNoSym<checkFine, smallGroup, lexMinimal>;
      friend class ::AugmentedPointConfigurationSym<checkFine, smallGroup, lexMinimal>;


   public:
      typedef smallGroup smallGroupBool;
      typedef checkFine checkFineBool;
      typedef lexMinimal lexMinimalBool;
      typedef std::true_type trivialSymmetry;
      typedef pmFlipHash<gkz_vector_type, Flip> pmFlipContainer;


      AugmentedPointConfiguration_core(
            const PointConfiguration& points_in,
            const Chirotope&          chiro_in,
            const SymmetryGroup&      symmetries_in) :
         points(points_in), chiro(chiro_in),
         symmetries(symmetries_in),
         gkz(points_in) 
         {}
      
      const PointConfiguration& get_points() const{
         return points;
      }

      const Chirotope& get_chiro() const{
         return chiro;
      }

      const SymmetryGroup& get_symmetries() const{
         return symmetries;
      }

      const gkz_type& get_gkz() const{
         return gkz;
      }
      
      std::string get_triangulation_data(const TriangNode& triang) const {
         std::ostringstream bos;
         TriangNode canonical(static_cast<const APCSpec*>(this)->canonicalize(triang));
         bos << canonical;
         bos << " gkz: ";
         bos << gkz.gkz_as_string(canonical);
         return bos.str();
      }

      Int size() const {
         return gkz.size();
      }

      void init_check(const TriangNode& t) const {
         gkz.init_check<checkFine>(t);
      }

};

template<typename checkFine, typename smallGroup, typename lexMinimal>
class AugmentedPointConfigurationNoSym : public AugmentedPointConfiguration_core<checkFine, smallGroup, lexMinimal, AugmentedPointConfigurationNoSym<checkFine, smallGroup, lexMinimal>> {

   public:

      

      AugmentedPointConfigurationNoSym(
            const PointConfiguration& points_in,
            const Chirotope&          chiro_in,
            const SymmetryGroup&      symmetries_in) :
         AugmentedPointConfiguration_core<checkFine, smallGroup, lexMinimal, AugmentedPointConfigurationNoSym>(points_in, chiro_in, symmetries_in)
         {}
      
      bool check_triang_equality(const TriangNode& t1, const TriangNode& t2) const{
         return t1==t2;
      }
      
      Int is_lex_upflip(const Flip& flip, gkz_vector_type& result_gkz) const{
         Int result;
         if(checkFine::value){
            result_gkz = this->gkz.evaluate_fine(flip);
         } else {
            result_gkz = this->gkz.evaluate(flip);
         }
         if(lexMinimal::value) {
            result = -this->gkz.is_lex_positive(result_gkz);
         } else {
            result = this->gkz.is_lex_positive(result_gkz);
         }
         return result;
      }
      
      void printInitialTriang(CerrWrapper<false>& ost, const TriangNode& triang) const {
         ost << "Initial triang: " << triang << std::endl;
      }

      TriangNode canonicalize(const TriangNode& triang) const {
         return triang;
      }

};


template<typename checkFine, typename smallGroup, typename lexMinimal>
class AugmentedPointConfigurationSym: public AugmentedPointConfiguration_core<checkFine, smallGroup, lexMinimal, AugmentedPointConfigurationSym<checkFine, smallGroup, lexMinimal>>{

   private:
      const SymmetryBlackBox<checkFine, smallGroup, lexMinimal>  sbb;


   public:
      typedef smallGroup smallGroupBool;
      typedef std::false_type trivialSymmetry;

      AugmentedPointConfigurationSym(
            const PointConfiguration& points_in,
            const Chirotope&          chiro_in,
            const SymmetryGroup&      symmetries_in) :
         AugmentedPointConfiguration_core<checkFine, smallGroup, lexMinimal, AugmentedPointConfigurationSym>(points_in, chiro_in, symmetries_in),
         sbb(CommandlineOptions::orbit_cache_size(), this->symmetries, this->gkz)
         {}
      
      bool check_triang_equality(const TriangNode& t1, const TriangNode& t2) const{
         return sbb.are_equivalent_triangulations(t1, t2);
      }

      const SymmetryBlackBox<checkFine, smallGroup, lexMinimal>& get_sbb() const{
         return sbb;
      }

      Int is_lex_upflip(const Flip& flip, const TriangNode& source, const TriangNode& target, gkz_vector_type& result_gkz) const{
         const triang_vector_type optimalVector = this->gkz.triangulationAsVector(target);
         Int result;
         result_gkz = sbb.get_gkz_of_canonical(target) - sbb.get_gkz_of_canonical(source);
         if(checkFine::value){
            Scalar fineCoord = this->gkz.fine_coordinate(result_gkz);
            result_gkz = fineCoord | result_gkz;
         }
         if(lexMinimal::value) {
            result = -this->gkz.is_lex_positive(result_gkz);
         } else {
            result = this->gkz.is_lex_positive(result_gkz);
         }
         result_gkz = result_gkz | gkz_vector_type(optimalVector);
         return result;
      }
      
      void printInitialTriang(CerrWrapper<false>& ost, const TriangNode& triang) const {
         ost << "Initial triang: " << triang << std::endl;
         ost << "Orbit size: " << sbb.get_orbit_size(triang) << std::endl;
      }
      
      TriangNode canonicalize(const TriangNode& triang) const {
         return sbb.get_canonical_representative(triang);
      }

};

#endif
