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

///////////////////////////////////////////////////////////////////////////////
// File: GKZ.hh
// This file contains a class that can compute the GKZ vector of a
// triangulation. Given n points in RR^k, it stores all n choose k determinants
// one can form. These are then used to sum up the GKZ-vector for a
// triangulation.
//
// TODO:
// 1. Better algorithm for initialization, analogously to the algorithm
// building the Chirotope (avoid zeros, use minors already computed)
// 2. Switch to polymake vectors, since these are sparse
///////////////////////////////////////////////////////////////////////////////
#pragma once
#include "mptopcom_typedefs.hh"

#include "CommandlineOptions.hh"

#include "MarkedFlips.hh"
#include "TriangNode.hh"

#include <polymake/client.h>
#include <polymake/Map.h>
#include <polymake/Matrix.h>
#include <polymake/Vector.h>
#include <polymake/Set.h>
#include <polymake/linalg.h>
#include <polymake/SparseVector.h>

#include "helpers.hh"
#include "CerrWrappers.hh"

#include <stdexcept>


// This class computes our GKZ-vectors. At construction it computes the volumes
// of the simplices that one can build (i.e. the determinants of d+1 minors of
// the points). The method computing an actual GKZ-vector of a triangulation is
// called evaluate. Furthermore one can also compute the GKZ-vector of a flip.
template<typename Scalar, typename Vectortype, typename TriangVectortype>
class GKZ {
   private:
      const PointConfiguration& pts;
      // HashMap mapping simplices to their volumes
      pm::Map<basis_type, Scalar> abs_determinants;
      // Number all possible simplices
      Int total_simplices;
      std::set<basis_type> all_bases;
      pm::Map<basis_type, Int> basis2int;
      pm::Map<Int, basis_type> int2basis;
      // Save the rank = number of points, and dim = ambient dim of pts.
      parameter_type rk, dim;
      // Matrix to populate abs_determinants
      pm::Matrix<Scalar> pmMat;
      pm::Vector<Scalar> pointNorms;
      pm::Set<Int> indices;
      Scalar det_of_submat(const basis_type& basis){
         Int i=0;
         indices.clear();
         for(basis_type::const_iterator iter = basis.begin(); iter != basis.end(); ++iter){
            indices.insert(*iter);
            i++;
         }
         return pm::det(pmMat.minor(indices, pm::All));
      }

      void read_points(){
         pm::Rational x;
         for(Int i=0; i<rk; i++){
            for(Int j=0; j<dim; j++){
               x.copy_from(pts[i][j].get_mpq_t());
               pmMat(i,j) = pm::convert_to<Scalar>(x);
            }
         }
      }

      void populate_pointNorms(){
         for(Int i=0; i<pointNorms.dim(); i++){
            pointNorms[i] = pmMat[i] * pmMat[i] - 1;
         }
      }

      // Int abs value det_of_submat
      Scalar abs_det_of_submat(const basis_type& basis){
         // FIXME: We should be much more careful here, but for now this is ok.
         Scalar val = det_of_submat(basis);
         if(val < 0){
            return -val;
         } else {
            return val;
         }
      }
      // Walk over all card dim subsets of pts and save the absolute values of
      // the determinants.
      // FIXME: This method computes many subdeterminants multiple times. Can
      // we use the same optimized algorithm as for the Chirotope in TOPCOM?
      void populate_abs_determinants(Int pts_index, Int mat_index, basis_type basis){
         if(mat_index == dim){
            Scalar val = abs_det_of_submat(basis);
            if(val != 0){
               all_bases.insert(basis);
               total_simplices++;
               abs_determinants[basis] = val;
            }
         } else if (pts_index >=rk){
            std::cerr << "Fell in here!" << std::endl;
            return;
         } else {
            Int corank = dim - basis.card();
            for(Int i=pts_index; i<rk-corank+1; i++){
               basis += i;
               populate_abs_determinants(i+1, mat_index+1, basis);
               basis -= i;
            }
         }
      }

      void randomly_number_simplices(){
         Int n = all_bases.size();
         Int a = 2321443;
         a %= n;
         if(a<0 || a>=n){
            std::cerr << "Something went wrong." << std::endl;
         }
         Int c = a;
         for(const auto& basis : all_bases){
            basis2int[basis] = c;
            int2basis[c] = basis;
            c += a;
            c %= n;
            if(c < 0){
               c += n;
            }
            if(c >= n){
               c -= n;
            }
         }
      }

   public:
      GKZ(const PointConfiguration& points): pmMat(points.no(), points.rank()), pts(points), pointNorms(points.no()){
         rk = points.no();
         dim = points.rank();
         read_points();
         populate_pointNorms();
         total_simplices = 0;
         populate_abs_determinants(0,0,basis_type());
         randomly_number_simplices();
         if(CommandlineOptions::verbose()){
            cerrFirstWorker << "GKZ done." << std::endl;
         }
      }

      const pm::Map<basis_type, Int>& get_basis2int() const{
         return basis2int;
      }
      
      const pm::Map<Int, basis_type>& get_int2basis() const{
         return int2basis;
      }

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

      Scalar fine_coordinate(const gkz_vector_type& gkzvec) const{
         return (-1) * (pointNorms * gkzvec);
      }
      
      TriangVectortype triangulationAsVector(const TriangNode& triangulation) const{
         TriangVectortype result;
         for(TriangNode::const_iterator triangle = triangulation.begin();
               triangle != triangulation.end();
               ++triangle){
            if(!basis2int.exists(*triangle)){
               std::cerr << *triangle << ": Simplex not found" <<std::endl;
            }
            result += basis2int[*triangle];
         }
         return result;
      }
      
      // Check whether we might have overflow
      //
      // The normalized volume of the convex hull of the point configuration is
      // an upper bound on the entries in the gkz vectors. So if this is too
      // large, there might be trouble.
      //
      // With this upper bound on the entries we can check whether there might
      // be overflow in the extra coordinate for computing fine triangulations.
      template<typename checkFine>
      void init_check(const TriangNode& triangulation) const{
         Scalar normalizedVolume(0);
         cerrFirstWorker << "Initial triangulation: ";
         for(TriangNode::const_iterator triangle = triangulation.begin();
               triangle != triangulation.end();
               ++triangle){
            cerrFirstWorker << *triangle << " ";
            normalizedVolume += abs_determinants[*triangle];
            if(normalizedVolume < abs_determinants[*triangle]){
               throw std::runtime_error("Normalized volume makes Scalar type overflow.");
            }
         }
         if(checkFine::value){
            Scalar check(0);
            for(const auto& e:pointNorms){
               Scalar summand(e*normalizedVolume);
               if(summand < 0){
                  throw std::runtime_error("Possible overflow when computing extra gkz-coordinate for fine triangulations.");
               }
               // The normalized volume is never zero.
               if(summand/normalizedVolume != e){
                  throw std::runtime_error("Possible overflow when computing extra gkz-coordinate for fine triangulations.");
               }
               Scalar next(check + summand);
               if(next < check || next < summand){
                  throw std::runtime_error("Possible overflow when computing extra gkz-coordinate for fine triangulations.");
               }
               check = next;
            }
         }
         cerrFirstWorker << std::endl;
         cerrFirstWorker << "Normalized volume is: " << normalizedVolume << std::endl;
      }

      // Compute GKZ-vector of a triangulation
      Vectortype evaluate(const TriangNode& triangulation) const{
         Vectortype result(rk);
         Scalar val;
         for(TriangNode::const_iterator triangle = triangulation.begin();
               triangle != triangulation.end();
               ++triangle){
            val = abs_determinants[*triangle];
            for(basis_type::const_iterator entry = (*triangle).begin();
                  entry != (*triangle).end();
                  ++entry){
               result[*entry] += val;
            }
         }
         return result;
      }

      Vectortype evaluate_fine(const TriangNode& triangulation) const {
         Vectortype result = evaluate(triangulation);
         return fine_coordinate(result) | result;
      }

      // Return the GKZ vector of a triangulation as string for printing.
      std::string gkz_as_string(const TriangNode& triang) const{
         return pmVector2string(evaluate(triang));
      }
      std::string gkz_as_string(const Flip& flip) const{
         return pmVector2string(evaluate(flip));
      }

      // Compute GKZ-vector of a simplicial complex, multiply it by a factor.
      // The simplicial complex does not have to be a triangulation and most of
      // the time it will not be one. This method is needed to compute the
      // GKZ-vector of a flip.
      void evaluate(Vectortype& result, const FastSimplicialComplex& fsc, Int factor) const{
         Scalar val;
         for(FastSimplicialComplex::const_iterator triangle = fsc.begin();
               triangle != fsc.end();
               ++triangle){
            val = abs_determinants[*triangle];
            for(basis_type::const_iterator entry = (*triangle).begin();
                  entry != (*triangle).end();
                  ++entry){
               result[*entry] += factor*val;
            }
         }
      }

      // Compute GKZ-vector of a flip, i.e. the difference of the GKZ-vectors
      // of the corresponding two triangulations.
      Vectortype evaluate(const Flip& flip) const{
         Vectortype result(rk);
         evaluate(result, flip.first, -1);
         evaluate(result, flip.second, 1);
         return result;
      }
      
      Vectortype evaluate_fine(const Flip& flip) const {
         Vectortype result = evaluate(flip);
         return fine_coordinate(result) | result;
      }

      Int is_lex_positive(const gkz_vector_type& v) const{
         auto nonzero = pm::entire(pm::attach_selector(v, polymake::operations::non_zero()));

         if(!nonzero.at_end()){
            if(*nonzero > 0){
               return 1;
            } else if (*nonzero < 0){
               return -1;
            } else {
               cerrFirstWorker << "First entry was zero, this is a bug." << std::endl;
               return 0;
            }
         } else {
            return 0;
         }
      }
      
};


