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

///////////////////////////////////////////////////////////////////////////////
// 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 "LRUCache.hh"
#include "SymmetryBlackBox.hh"
#include "RegularityCheckCache.hh"
#include "FlipChecker.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<typename FlipDataType, typename APCType>
class PrimitiveFlipCache {
   private:
      using FlipChecker = typename FlipDataType::Checker;
      using pmFlipContainer = typename APCType::pmFlipContainer;

      const APCType&                   apc;
      const Int                       size;
      mutable LRUCache<TriangNode, FlipDataType > flips;
      mutable Int									  hit;
      mutable Int									  miss;
      const FlipChecker&    fc;

      void populate_flips(const TriangNode& triang) const{
         if(flips.exist(triang)){
            hit++;
            return;
         } else {
            miss++;
            flips.put(triang, FlipDataType(apc, triang, fc));
            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++;
               FlipDataType beforeData = flips.get(before);
               flips.put(after, beforeData.mutate(flip, after));
            } else {
               miss++;
               flips.put(after, FlipDataType(apc, after, fc));
            }
         }
      }

   public:
      PrimitiveFlipCache(
            const APCType& apc_in,
            const Int size_in,
            const FlipChecker& fc_in) :
         apc(apc_in), size(size_in), 
         flips(size_in), fc(fc_in){
            hit=0;
            miss=0;
         }

      bool exists(const TriangNode& t) const {
         return flips.exist(t);
      }

      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 {
         cerrWrap << "Hits: " << hit << "  Miss: " << miss << std::endl;
      }
};

#endif
