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

///////////////////////////////////////////////////////////////////////////////
// RegularityCheckCache.hh
// 
// This file contains a cache for the regularity checks. When we walk down a
// flip and then check all the flips of the target triangulations for
// regularity, many of them will already have been previously checked, so a
// cache should be useful.
///////////////////////////////////////////////////////////////////////////////
#pragma once

#include "mptopcom_typedefs.hh"
#include "LRUCache.hh"
#include "GKZ.hh"
// #include "TriangNode.hh"
#include "TriangFlips.hh"


#include <unordered_set>
#include <polymake/Main.h>
#include <polymake/Matrix.h>
#include <polymake/Set.h>
#include <polymake/SparseVector.h>
#include <polymake/polytope/is_regular.h>

#include <polymake/polytope/solve_LP.h>


namespace pm {

   template <>
      struct iterator_cross_const_helper<__sc_const_iterator<IntegerSet>, true> {
         typedef __sc_const_iterator<IntegerSet> iterator;
         typedef __sc_const_iterator<IntegerSet> const_iterator;
      };

}

namespace std {
   template <>
      struct hash<std::pair<TriangNode, Flip>> {
         std::size_t operator()(const std::pair<TriangNode, Flip>& key) const {
            return std::hash<TriangNode>()(key.first) + std::hash<Flip>()(key.second);
         }
      };
};

class RegularityCheckCache {
   private:
      mutable LRUCache<TriangNode, bool> cache;
      mutable LRUCache<std::pair<TriangNode, Flip>, bool> regularFlipCache;
      const PointConfiguration& pts;
      const Chirotope& chiro;
      pm::Matrix<Scalar> pmPts;
      const gkz_type& gkz;

      void init(const std::string& lp_client);

   public: 
      RegularityCheckCache(Int size, const PointConfiguration& pts_in, const Chirotope& chiro_in, const std::string& lp_client, const gkz_type& gkz_in):
         pts(pts_in), chiro(chiro_in), cache(size), pmPts(pts.no(), pts.rank()), gkz(gkz_in),
         regularFlipCache(size)
         {
            init(lp_client);
            // Convert topcom pts to polymake pts.
            pm::Rational x;
            for(Int i=0; i<pts.no(); i++){
               for(Int j=0; j<pts.rank(); j++){
                  x.copy_from(pts[i][j].get_mpq_t());
                  pmPts(i,j) = pm::convert_to<Scalar>(x);
               }
            }
         };

      bool isRegular(const TriangNode& t) const{
         bool result;
         if(cache.exist(t)){
            result = cache.get(t);
         } else {
            result = isRegular_inner(t);
            // analyse_sec_cone(t);
            cache.put(t, result);
         }
         return result;
      }

      bool regularFlipLP(const TriangNode& s, const std::unordered_map<Flip, FlipInfo>& all_flips, const Flip& flip) const {
         gkz_vector_type fgkz(gkz.evaluate(flip));
         pm::ListMatrix<gkz_vector_type> fgkzvs;
         fgkzvs /= gkz_vector_type(-fgkz);

         auto it = all_flips.begin();
         while(it != all_flips.end())
         {
            if(it->second.isValid){
               gkz_vector_type ogkz(gkz.evaluate(it->first));
               if(ogkz != fgkz){
                     fgkzvs /= ogkz;
               }
            }
            ++it;
         }

         pm::Matrix<pm::Rational> flip_gkz_vectors(fgkzvs);
         Int nrows = flip_gkz_vectors.rows();
         const pm::Vector<pm::Rational> Obj(Scalar(0) | pm::ones_vector<pm::Rational>(nrows-1));
         const bool maximize = false;
         static const polymake::polytope::LP_Solver<pm::Rational>& solver = polymake::polytope::get_LP_solver<pm::Rational>();
         const auto S = solver.solve(pm::unit_matrix<pm::Rational>(nrows), T(flip_gkz_vectors), Obj, maximize);
         bool result;
         switch (S.status) {
            case polymake::polytope::LP_status::valid:
            case polymake::polytope::LP_status::unbounded:
               return false;
            default:
               return true;
         }
      }
      
      bool isRegular(const TriangNode& s, const gkz_vector_type& flip_gkz, const std::unordered_map<Flip, FlipInfo>& all_flips, const Flip& flip) const {
         std::pair<TriangNode, Flip> key(s, flip);
         if(regularFlipCache.exist(key)){
            return regularFlipCache.get(key);
         } else {
            bool result = regularFlipLP(s, all_flips, flip);
            Flip reverseFlip(flip);
            reverseFlip.swap();
            TriangNode target(0, s, flip);
            std::pair<TriangNode, Flip> reverseKey(target, reverseFlip);
            regularFlipCache.put(key, result);
            regularFlipCache.put(reverseKey, result);
            return result;
         }
         // return regularFlipLP(s, all_flips, flip);
      }

      void analyse_sec_cone(const TriangNode& tn) const {
         TriangFlips TF(chiro, tn, SymmetryGroup(chiro.no()), false);
         std::cerr << "Testing:" << std::endl;
         std::cerr << tn << std::endl;
         // std::cerr << TF << std::endl;
         const MarkedFlips& mf(TF.flips());
         Int nflips = 0;
         for(MarkedFlips::const_iterator flip = mf.begin(); flip!=mf.end(); flip++) nflips++;
         pm::Matrix<pm::Rational> flipineq(nflips, pts.no());
         std::cerr << mf.size() << " " << pts.no() << std::endl;
         Int i = 0;
         for(MarkedFlips::const_iterator flip = mf.begin(); flip!=mf.end(); flip++){
            const auto FR = flip->key();
            Flip F(tn, FR);
            // std::cerr << gkz.gkz_as_string(F) << std::endl;
            flipineq.row(i) = gkz.evaluate(F);
            i++;
            // TriangNode Ft(0, tn, F);
            // std::cerr << Ft << std::endl;
         }
         std::cerr << "Total flips: " << i << std::endl;
         // pm::cout << flipineq << pm::endl;
         static const pm::perl::OptionSet noopts;
         pm::Array<pm::Set<Int>> pmMC = triangulation2polymake(tn);
         std::pair<const pm::Matrix<Scalar>, const pm::Matrix<Scalar>> SC = polymake::polytope::secondary_cone_ineq(pmPts, pmMC, noopts);
         polymake::BigObject polymake_cone("polytope::Cone<Rational>");
         polymake_cone.take("INEQUALITIES") << SC.first;
         polymake_cone.take("EQUATIONS") << SC.second;
         polymake::BigObject our_cone("polytope::Cone<Rational>");
         our_cone.take("INEQUALITIES") << flipineq;

         Int polymake_dim = polymake_cone.give("CONE_DIM");
         Int our_dim = our_cone.give("CONE_DIM");
         std::cerr << "Dim check: " << (polymake_dim == our_dim) << std::endl;
         

         std::cerr << std::endl;
      }

      std::string get_secondary_cone_rays(const TriangNode& t) const {
         // Convert triangulation to polymake datatype
         pm::Array<pm::Set<Int>> pmMC = triangulation2polymake(t);
         // Make secondary cone ineqs
         static const pm::perl::OptionSet pos("lift_face_to_zero", 0);
         std::pair<const pm::Matrix<Scalar>, const pm::Matrix<Scalar>> SC = polymake::polytope::secondary_cone_ineq(pmPts, pmMC, pos);
         polymake::BigObject cone("polytope::Cone<Rational>");
         cone.take("INEQUALITIES") << SC.first;
         cone.take("EQUATIONS") << SC.second;
         pm::Matrix<pm::Rational> rays = cone.give("RAYS");
         std::string result = "Secondary cone rays: [";
         Int nrows = rays.rows();
         Int ncols = rays.cols();
         Int i = 0;
         Int j;
         for(const auto& row : rows(rays)){
            result += "[";
            j = 0;
            for(const auto& entry : row){
               result += scalar2string(entry);
               j++;
               if(j < ncols) result += ",";
            }
            result += "]";
            i++;
            if(i < nrows) result += ",";
         }
         return result;
      }

   
      bool subdivision_is_regular(const pm::Array<pm::Set<Int>>& pmMC) const {
         // Make secondary cone ineqs
         static const pm::perl::OptionSet pos("lift_face_to_zero", 0);
         std::pair<const pm::Matrix<Scalar>, const pm::Matrix<Scalar>> SC = polymake::polytope::secondary_cone_ineq(pmPts, pmMC, pos);
         bool result = is_regular_lp(SC);
         return result;
      }

   private:
      pm::Array<pm::Set<Int>> triangulation2polymake(const TriangNode& t) const {
         pm::Array<pm::Set<Int>> pmMC(t.card());
         Int i = 0;
         for(auto s : t) {
            pmMC[i] = pm::convert_to<pm::Set<Int>>(s);
            i++;
         }
         return pmMC;
      }

      bool isRegular_inner(const TriangNode& t) const{

         // Convert triangulation to polymake datatype
         pm::Array<pm::Set<Int>> pmMC = triangulation2polymake(t);
         // Make secondary cone ineqs
         static const pm::perl::OptionSet pos("lift_face_to_zero", 0);
         std::pair<const pm::Matrix<Scalar>, const pm::Matrix<Scalar>> SC = polymake::polytope::secondary_cone_ineq(pmPts, pmMC, pos);
         bool result = is_regular_lp(SC);
         return subdivision_is_regular(pmMC);
      }

      bool is_regular_lp(std::pair<const pm::Matrix<Scalar>, const pm::Matrix<Scalar>> SC) const {
         const Int n_vertices=pts.no();
         pm::Matrix<pm::Rational> H(SC.first);
         pm::Matrix<pm::Rational> E(SC.second);
         const pm::Vector<pm::Rational> Obj(Scalar(0) | pm::ones_vector<Scalar>(n_vertices));
         bool maximize = false;
         E = pm::zero_matrix<pm::Rational>(E.rows(),1) | E;
         H = ((pm::Rational(-1))*(pm::ones_matrix<pm::Rational>(H.rows(),1))) | H;

         static const polymake::polytope::LP_Solver<pm::Rational>& solver = polymake::polytope::get_LP_solver<pm::Rational>();
         const auto S = solver.solve(H, E, Obj, maximize);
         switch (S.status) {
            case polymake::polytope::LP_status::valid:
            case polymake::polytope::LP_status::unbounded:
               return true;
            default:
               return false;
         }
         return false;
      }

};



