/* Copyright (c) 2018 -- 2022
   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 AUGMENTED_TRIANGULATION_HH
#define AUGMENTED_TRIANGULATION_HH

#include "TriangNode.hh"

template<typename PropertyOracle>
class AugmentedTriangulation {
   private:
      const PropertyOracle& oracle;
      using pmFlipContainer = typename PropertyOracle::pmFlipContainer;
      TriangNode triangulation;
      Int nDownflips;

   public:
      AugmentedTriangulation(const PropertyOracle& po, const TriangNode& t): 
         oracle(po),
         triangulation(t)
      {
         nDownflips = oracle.get_downflips(triangulation).size();
      }
      
      AugmentedTriangulation(const AugmentedTriangulation& t): 
         oracle(t.oracle),
         triangulation(t.triangulation)
      {
         nDownflips = oracle.get_downflips(triangulation).size();
      }
      
      AugmentedTriangulation(const PropertyOracle& po, const TriangNode& source, const Flip& flip):        
         oracle(po), triangulation(0, source, flip)
      {
         nDownflips = oracle.get_downflips(source, flip, triangulation).size();
      }

      bool has_jth_child(Int j) const {
         if(nDownflips <= j){
            return false;
         } else {
            return true;
         }
      }

      Int get_Delta() const {
         return nDownflips;
      }
      
      AugmentedTriangulation get_jth_child(Int j) const {
         Flip flipToChild;
         Int c=0;
         for(const auto& flipit : oracle.get_downflips(triangulation)){
            if(c==j){
               flipToChild = flipit.second;
               break;
            }
            c++;
         }
         return AugmentedTriangulation(oracle, triangulation, flipToChild);
      }

      std::list<AugmentedTriangulation> get_all_neighbors() const {
         std::list<AugmentedTriangulation> result;
         std::list<Flip> flips;
         for(const auto& flipit : oracle.get_downflips(triangulation)){
            flips.push_back(flipit.second);
         }
         for(const auto& flipit : oracle.get_upflips(triangulation)){
            flips.push_back(flipit.second);
         }
         for(const auto& flip: flips) {
            result.push_back(AugmentedTriangulation(oracle, triangulation, flip));
         }
         return result;
      }

      AugmentedTriangulation& operator=(const AugmentedTriangulation& other){
         triangulation = other.triangulation;
         nDownflips = other.nDownflips;
         return *this;
      }

      const TriangNode& get_triang() const {
         return triangulation;
      }

      bool operator==(const AugmentedTriangulation& other) const {
         return oracle.check_equality(triangulation, other.triangulation);
      }

      bool has_predecessor(const AugmentedTriangulation& candidate) const {
         const Flip& backFlip((oracle.get_upflips(triangulation).back()).second);
         const TriangNode predecessor(0, triangulation, backFlip);
         return oracle.check_equality(predecessor, candidate.triangulation);
      }
      
      AugmentedTriangulation get_predecessor(Int& j) const {
         const Flip& backFlip((oracle.get_upflips(triangulation).back()).second);
         AugmentedTriangulation result(oracle, triangulation, backFlip);
         const pmFlipContainer& down(result.oracle.get_downflips(result.triangulation));
         // Introduce a counter to number the children
         Int c = 0;
         for(typename pmFlipContainer::const_iterator flip = down.begin();
               flip != down.end();
               ++flip){
            if(oracle.check_equality(triangulation, TriangNode(0, result.triangulation, (*flip).second))){
               j = c;
               break;
            }
            c++;
         }
         return result;
      }

      friend std::hash<AugmentedTriangulation>;

};

namespace std {

// #ifndef WITH_PM_BITSET
// 
// template<typename PropertyOracle>
//   struct hash<AugmentedTriangulation<PropertyOracle>> {
//      std::size_t operator()(const AugmentedTriangulation<PropertyOracle>& in) const
//      {
//         const TriangNode& tn(in.oracle.get_apc().canonicalize(in.triangulation));
//         std::size_t result = 0;
//         for (TriangNode::const_iterator it=tn.begin(); it!=tn.end(); it++) {
//            result += it->get_rep();
//         }
//         return result;
//      }
//   };
// 
// #else
template<typename PropertyOracle>
   struct hash<AugmentedTriangulation<PropertyOracle>> {
      std::size_t operator()(const AugmentedTriangulation<PropertyOracle>& in) const
      {
         return std::hash<TriangNode>()(in.oracle.get_apc().canonicalize(in.triangulation));
      }
   };
// #endif
}

#endif
