/* Copyright (c) 1997-2015
   Ewgenij Gawrilow, Michael Joswig (Technische Universitaet Berlin, Germany)
   http://www.polymake.org

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

#include "polymake/client.h"
#include "polymake/Vector.h"
#include "polymake/Matrix.h"
#include "polymake/IncidenceMatrix.h"
#include "polymake/Map.h"
#include "polymake/linalg.h"
#include "polymake/common/hermite_normal_form.h"
#include "polymake/tropical/thomog.h"

namespace polymake { namespace tropical {


	/*
	 * @brief Takes a rational matrix and makes each row integer by 
	 * multiplying it with an appropriate integer
	 */
	Matrix<Integer> make_rowwise_integer(const Matrix<Rational> &m) {
	  Matrix<Integer> result(m.rows(), m.cols());
	  for(int r = 0; r < m.rows(); r++) { 
		  Integer mult = 1;
		  for(int c = 0; c < m.cols(); c++) {
			  mult *= denominator(m(r,c));
		  }
		  result.row(r) = mult* m.row(r);
	  }
	  return result;
	}

	/**
	 * @brief Computes the property [[LATTICE_NORMALS]]
	 */
	void compute_lattice_normals(perl::Object C) {
		//Extract necessary values
		IncidenceMatrix<> maximalPolytopes = C.give("MAXIMAL_POLYTOPES");
		IncidenceMatrix<> codimensionOnePolytopes = C.give("CODIMENSION_ONE_POLYTOPES");
		IncidenceMatrix<> maximalAtCodim = C.give("MAXIMAL_AT_CODIM_ONE");
		IncidenceMatrix<> maximalConeNormals = C.give("MAXIMAL_POLYTOPES_AFFINE_HULL_NORMALS");
		Map<std::pair<int,int>,int> facetNormalsByPairs = C.give("FACET_NORMALS_BY_PAIRS");
		Matrix<Rational>  facetNormals = C.give("FACET_NORMALS");
		Matrix<int> maximalPolytopesFacets = C.give("MAXIMAL_POLYTOPES_FACETS");
		Matrix<Rational> 	affineHull = C.give("AFFINE_HULL");
		int fanDim = C.give("FAN_DIM");
		
		Map<std::pair<int,int>,Vector<Integer> > lattice_normals;

		//We forget the constant coefficient of all normals, since we only care about
		//the (non-affine) space spanned by a cone
		if(affineHull.cols() > 0)
			affineHull.col(0) = zero_vector<Rational>(affineHull.rows());
		if(facetNormals.cols() > 0)
			facetNormals.col(0) = zero_vector<Rational>(facetNormals.rows());

		//The equation x_0 = 0 has to be added manually to all matrices, since it is
		//assumed implicitly for complexes. This ensure that every lattice normal
		//has a leading 0.
		Vector<Integer> add_eq = affineHull.cols() > 0 ? unit_vector<Integer>(affineHull.cols(),0) :
			Vector<Integer>();

		//Iterate through all the key-value pairs of facetNormalsByPairs
		for(Entire<Map<std::pair<int,int>,int> >::iterator key = entire(facetNormalsByPairs); 
				!key.at_end(); key++) {
			//Construct the affine hull normals matrix of the codimension one facet
			//Make sure the additional equation of the facet wrt to the maximal cone is at the bottom
			Matrix<Rational> normal_matrix = affineHull.minor(maximalConeNormals.row( (*key).first.second),All);
			normal_matrix = normal_matrix / facetNormals.row( (*key).second); 

			//Compute the transformation to a weak Hermite normal form.
			SparseMatrix<Integer> transformation = 
					polymake::common::hermite_normal_form( add_eq / make_rowwise_integer(normal_matrix),false).second;
			
			//The last fanDim-1 columns of the transformation matrix are a Z-basis for
			//the space spanned by the maximal cone. The last fanDim-2 columns do the same
			//for the codimension one cone. Hence the column with index cols()-fanDim+1 is
			//a representative of the lattice normal (up to sign)
			Vector<Integer> lnormal(transformation.col(transformation.cols() - fanDim +1));

			//The facet normal must be positive on the lattice normal
			if( maximalPolytopesFacets( (*key).first.second, (*key).second) * facetNormals.row( (*key).second) * lnormal < 0) {
				lnormal = -lnormal;
			}

			lattice_normals[ (*key).first] = lnormal;
		}

		C.take("LATTICE_NORMALS") << lattice_normals;

	}//END compute lattice normals


	/**
	 * @brief Checks whether two sets of lattice normals are equivalent for a given cycle
	 * (i.e. lattice normals are the same modulo the respective codimension one faces)
	 * @param Matrix<Rational> vertices The VERTICES of the cycle
	 * @param Matrix<Rational> lineality The LINEALITY_SPACE of the cycle
	 * @param IncidenceMatrix<> codim The CODIMENSION_ONE_POLYTOPES of the cycle
	 * @param Map<..> lnormals1 The first set of lattice normals
	 * @param Map<..> lnormals2 The second set of lattice normals.
	 * @return bool
	 */
	bool compare_lattice_normals(Matrix<Rational> &vertices, Matrix<Rational> &lineality, const IncidenceMatrix<> &codim,
										const Map<std::pair<int,int>,Vector<Integer> > lnormals1, const Map<std::pair<int,int>,Vector<Integer> > lnormals2) {
		//First we check if the maps are defined on the same key set
		if(lnormals1.size() != lnormals2.size()) return false;

		//We only need far rays and affine coordinates without leading coordinate for span computation
		Set<int> far_rays = far_points(vertices);
		if(vertices.rows() > 0) {
			vertices = tdehomog(vertices).minor(All,~scalar2set(0));
		}
		if(lineality.rows() > 0) {
			lineality = tdehomog(lineality).minor(All,~scalar2set(0));
		}

		for(Entire<Map<std::pair<int,int>,Vector<Integer> > >::const_iterator l1pairs = entire(lnormals1); !l1pairs.at_end(); l1pairs++) {
			if(!lnormals2.contains( (*l1pairs).first)) return false;
			
			//If the key exists, check equality of normals
			Vector<Rational> v1( lnormals1[ (*l1pairs).first ] );
				v1 = tdehomog_vec(v1).slice(~scalar2set(0));
			Vector<Rational> v2( lnormals2[ (*l1pairs).first ] );
				v2 = tdehomog_vec(v2).slice(~scalar2set(0));

			int cone_index = (*l1pairs).first.first;

			Matrix<Rational> span_matrix = vertices.minor( codim.row(cone_index) * far_rays,All) / lineality;
			span_matrix /= v1;
			if(rank(span_matrix) != rank(span_matrix / v2)) return false;
		}
		
		return true;

	}

	Function4perl(&compute_lattice_normals,"compute_lattice_normals(Cycle)");
	Function4perl(&compare_lattice_normals, "compare_lattice_normals(Matrix, Matrix, IncidenceMatrix, Map< Pair<Int,Int>, Vector<Integer> >, Map< Pair<Int,Int>, Vector<Integer> >)");

}}

