/* 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 TRIANGULATION_ORACLE_HH
#define TRIANGULATION_ORACLE_HH

#include "PrimitiveFlipCache.hh"

template<typename FlipDataType, typename APCType>
class TriangulationOracle {
   public:
      using FlipChecker = typename FlipDataType::Checker;
      using APC = APCType;
      using pmFlipContainer = typename APCType::pmFlipContainer;
   private:
      const APCType apc;
      // Order is important, apc needs to be initialized before pfc.
      const FlipChecker fc;
      const PrimitiveFlipCache<FlipDataType, APCType>		  pfc;
      const InitialTriangFinder<typename FlipDataType::Initial, APCType> itf;
   public:
   

      TriangulationOracle(
            const PointConfiguration& points_in,
            const Chirotope&          chiro_in,
            const SymmetryGroup&      symmetries_in) :
         apc(points_in, chiro_in, symmetries_in),
         fc(apc),
         pfc(apc, CommandlineOptions::flip_cache_size(), fc), 
         itf(apc)
      {
      }

      bool flipsExist(const TriangNode& t) const {
         return pfc.exists(t);
      }

      const pmFlipContainer& get_upflips(const TriangNode& t) const {
         return pfc.get_upflips(t);
      }
      const pmFlipContainer& get_downflips(const TriangNode& t) const {
    return pfc.get_downflips(t);
      }
      const pmFlipContainer& get_upflips(const TriangNode& source, const Flip& flip, const TriangNode& target) const {
         return pfc.get_upflips(source, flip, target);
      }
      const pmFlipContainer& get_downflips(const TriangNode& source, const Flip& flip, const TriangNode& target) const {
         return pfc.get_downflips(source, flip, target);
      }

      // Determine whether v has more than j children, if yes, also return the
      // flipToChild.
      bool has_jth_child(const TriangNode& v, Int j, Flip& flipToChild) const {
         const pmFlipContainer& down(pfc.get_downflips(v));
         if(down.size() <= j){
            return false;
         }
         // FIXME: Bad workaround for not having random access. Instead pass
         // around iterators.
         Int c=0;
         for(const auto& flipit : down){
            if(c==j){
               flipToChild = flipit.second;
               return true;
            }
            c++;
         }
         return true;
      }

      bool check_equality(const TriangNode& t1, const TriangNode& t2) const {
         return apc.check_triang_equality(t1, t2);
      }

      // This method finds the predecessor p of v and simultaneously figures
      // out what number v has as a child of p.
      TriangNode numbered_predecessor(const TriangNode& v, const pmFlipContainer& up, Int& j) const {
         // Apply worst upflip to find predecessor
         const Flip& backFlip((up.back()).second);
         TriangNode result(0, v, backFlip);
         // Get sorted downflips of predecessor
         const pmFlipContainer& down(pfc.get_downflips(v, backFlip, result));

         // Introduce a counter to number the children
         Int c = 0;
         for(typename pmFlipContainer::const_iterator flip = down.begin();
               flip != down.end();
               ++flip){
            if(check_equality(v, TriangNode(0, result, (*flip).second))){
               j = c;
               break;
            }
            c++;
         }
         return result;
      }
      TriangNode numbered_predecessor(const TriangNode& v, Int& j) const {
         const pmFlipContainer& up(pfc.get_upflips(v));
         return numbered_predecessor(v, up, j);
      }
      TriangNode numbered_predecessor(const TriangNode& source, const Flip& flip, const TriangNode& v, Int& j) const {
         const pmFlipContainer& up(pfc.get_upflips(source, flip, v));
         return numbered_predecessor(v, up, j);
      }
      
      bool is_good_flip(const TriangNode& source, const Flip& flip, const TriangNode& target) const {
         Int discard;
         const TriangNode& predecessor = numbered_predecessor(source, flip, target, discard);
         return check_equality(predecessor, source);
      }
      
      TriangNode get_initial_triang(
         const parameter_type      no,
         const parameter_type      rank,
         const SimplicialComplex&  seed
         ) const {
         TriangNode result = TriangNode(0, no, rank, seed);
         apc.init_check(result);
         if (CommandlineOptions::verbose()) {
            apc.printInitialTriang(cerrWrap, result);
         }
         if(APCType::checkFineBool::value && (!isFine(result))){
            SimplicialComplex fine = FineTriang(apc.get_chiro());
            result = TriangNode(0, no, rank, fine);
         }
         return result;
      }

      // Find the triangulation without any further upflips.
      TriangNode find_max_triang(const TriangNode& initial) const {
         return itf.find_root(initial);
      }

      void print_pfc_stat() const {
         pfc.print_stat();
      }

      const FlipChecker& get_fc() const{
         return fc;
      }

      const APCType& get_apc() const {
         return apc;
      }

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

};

#endif
