///////////////////////////////////////////////////////////////////////////////
// PrimitiveFlipCache.hh
// This files contains methods to compute flips for triangulations, to compare
// them and to sort them (lexicographically). Furthermore it contains a basic
// implementation of an least recently used (LRU)Cache. This avoids having to
// recompute flips all the time. The cache is limited in the number of entries,
// so it only reserves a fixed amount of memory.
///////////////////////////////////////////////////////////////////////////////

#ifndef PRIMITIVEFLIPCACHE_HH
#define PRIMITIVEFLIPCACHE_HH
// For LRU Cache
#include "GKZ.hh"
#include <list>
#include <unordered_map>
#include <assert.h>

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

// Modified TriangFlips
#include "FlipData.hh"

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




// The flip cache uses to LRUCaches to store up- and downflips separatedly.
// Furthermore these are always sorted, since we need them in the right order.
template<bool trivialSymmetry, bool checkRegular, bool checkFine, bool smallGroup>
class PrimitiveFlipCache {
   private:
      const Chirotope*                _chiroptr;
      const gkz_type&                 gkz;
      const int                       size;
      SymmetryGroup                   _node_symmetries;
      mutable LRUCache<TriangNode, FlipData<trivialSymmetry, checkRegular, checkFine, smallGroup> > flips;
      mutable int									  hit;
      mutable int									  miss;
      const SymmetryBlackBox<smallGroup>&               sbb;
      const RegularityCheckCache            rcc;
      const GKZCache<checkFine>             gkzc;

      void populate_flips(const TriangNode& triang) const{
         if(flips.exist(triang)){
            hit++;
            return;
         } else {
            miss++;
            flips.put(triang, FlipData<trivialSymmetry, checkRegular, checkFine, smallGroup>(triang, _chiroptr, gkz, _node_symmetries, sbb, rcc, gkzc));
            if(!flips.exist(triang)){
               std::cout << "Something went horribly wrong." << std::endl;
            }
         }
      }

      void populate_flips(const TriangNode& before, const Flip& flip, const TriangNode& after)const {
         if(flips.exist(after)){
            hit++;
            return;
         } else {
            miss++;
            if(flips.exist(before)){
               hit++;
               FlipData<trivialSymmetry, checkRegular, checkFine, smallGroup> beforeData = flips.get(before);
               flips.put(after, beforeData.mutate(flip, after));
            } else {
               miss++;
               flips.put(after, FlipData<trivialSymmetry, checkRegular, checkFine, smallGroup>(after, _chiroptr, gkz, _node_symmetries, sbb, rcc, gkzc));
            }
         }
      }

   public:
      PrimitiveFlipCache(
            const Chirotope&          chiro,
            const gkz_type&  gkz_in,
            const int size_in,
            const SymmetryGroup&      seed_symmetries,
            const SymmetryBlackBox<smallGroup>& sbb_in) :
         sbb(sbb_in), rcc(size_in, gkz_in),
         _chiroptr(&chiro), gkz(gkz_in), size(size_in), 
         flips(size_in), gkzc(size_in, gkz_in),
         _node_symmetries(seed_symmetries){
            hit=0;
            miss=0;
         }

      const pmFlipContainer& get_upflips(const TriangNode& triang) const{
         populate_flips(triang);
         return flips.get(triang).get_upflips();
      }

      const pmFlipContainer& get_downflips(const TriangNode& triang) const{
         populate_flips(triang);
         return flips.get(triang).get_downflips();
      }

      const int get_delta(const TriangNode& triang) const{
         populate_flips(triang);
         return flips.get(triang).get_downflips().size();
      }

      const pmFlipContainer& get_upflips(const TriangNode& before, const Flip& flip, const TriangNode& after) const {
         populate_flips(before, flip, after);
         return flips.get(after).get_upflips();
      }
      
      const int get_delta(const TriangNode& before, const Flip& flip, const TriangNode& after) const{
         populate_flips(before, flip, after);
         return flips.get(after).get_downflips().size();
      }

      const pmFlipContainer& get_downflips(const TriangNode& before, const Flip& flip, const TriangNode& after) const {
         populate_flips(before, flip, after);
         return flips.get(after).get_downflips();
      }



      void print_stat() const {
         std::cout << "Hits: " << hit << "  Miss: " << miss << std::endl;
      }
};

#endif
