/* Copyright (c) 1997-2009
   Ewgenij Gawrilow, Michael Joswig (Technische Universitaet Darmstadt, Germany)
   http://www.polymake.de

   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.
--------------------------------------------------------------------------------
   $Project: polymake $$Id: is_regular.cc 10087 2011-03-24 14:08:11Z sherrmann $
*/

#include "polymake/client.h"
#include "polymake/PowerSet.h"
#include "polymake/Matrix.h"
#include "polymake/ListMatrix.h"
#include "polymake/Rational.h"
#include "polymake/Array.h"
#include "polymake/linalg.h"

namespace polymake { namespace polytope {
 



perl::Object secondary_cone(const Matrix<Rational> &verts,const Array<Set <int> >& subdiv,perl::OptionSet options)
{

  const int n_vertices=verts.rows();
  const int ambient_dim=verts.cols()-1;
  const int n_facets=subdiv.size();


  //compute the set of all points that is not used in any face
  Set<int> not_used(sequence(0,n_vertices));
  for (int i=0; i<n_facets; ++i)
    not_used-=subdiv[i];

  //compute a full-dimensional orthogonal projection if verts is not full_dimensional
  const Matrix<Rational> affine_hull=null_space(verts);
  const int codim=affine_hull.rows();
  bool found=false;
  Set<int> coords;
  for (Entire< Subsets_of_k <const sequence &> >::const_iterator i=entire(all_subsets_of_k(sequence(0,ambient_dim),codim));!found&&!i.at_end(); ++i)
    if (det(affine_hull.minor(All,*i))!=0) {
      coords=*i;
      found=true;
    }
  const Matrix<Rational> vertices=verts.minor(All,~coords);  
  
  const int dim=vertices.cols()-1;

  // the equations and inequalities for the possible weight vectors
  // (without right hand side which will be 0)
  ListMatrix<Vector<Rational> > equats;
  ListMatrix<Vector<Rational> > inequs;

  Matrix<Rational> eqs;
  if (options["equations"]>> eqs) equats/=eqs;
  Set<int> tozero=options["lift_to_zero"];
  int face;
  if (!equats.rows() &&tozero.empty() && options["lift_face_to_zero"]>>face) {
    tozero+=subdiv[face];
  }
  for(Entire<Set<int> >::const_iterator j=entire(tozero);!j.at_end();++j)
    equats/=unit_vector<Rational>(n_vertices,*j);



  //now we start generating the equation and inequalities
  for (int i=0; i<n_facets; ++i) {
    const Set<int> b=basis_rows(vertices.minor(subdiv[i],All));

    //we have to map the numbers the right way:
    Set<int> basis;
    int k=0;
    Entire<Set<int> >::const_iterator l=entire(subdiv[i]);
    for(Entire<Set<int> >::const_iterator j=entire(b);!j.at_end();++j,++k,++l) {
      while(k<*j) {
	++k;
	++l;
      }
      basis.push_back(*l);
    }
    const Rational basis_det=det(vertices.minor(basis,All));
    const int basis_sign=basis_det>0?1:-1;
    const Set<int> non_basis=subdiv[i]-basis;

    //for each maximal face F, all points have to be lifted to the same facet
    for(Entire<Set<int> >::const_iterator j=entire(non_basis);!j.at_end();++j) {
      Vector<Rational> eq(n_vertices);
      int s=basis_sign;
      eq[*j]=s*basis_det;
      for(Entire<Set<int> >::const_iterator k=entire(basis);!k.at_end();++k) {
	const Set<int> rest=basis-scalar2set(*k);
	s=-s;
	eq[*k]=s*det(vertices[*j]/vertices.minor(rest,All));
      }
      equats/=eq;
    }


    // for all adjacent maximal faces, all vertices not contained in F have to be lifted
    // in the same direction
    for(int l=i+1; l<n_facets; ++l)
      if (rank(vertices.minor(subdiv[i]*subdiv[l],All))==dim) {
	const int j=*((subdiv[l]-subdiv[i]).begin());
	Vector<Rational> ieq(n_vertices);
	int s=basis_sign;      
	ieq[j]=s*basis_det;
	for(Entire<Set<int> >::const_iterator k=entire(basis);!k.at_end();++k) {
	  const Set<int> rest=basis-scalar2set(*k);
	  s=-s;
	  ieq[*k]=s*det(vertices[j]/vertices.minor(rest,All));
	}
	inequs/=ieq;
      }

  //additional equations for the non-used points
  for(Entire<Set<int> >::const_iterator l=entire(not_used);!l.at_end();++l) {
    Vector<Rational> ieq(n_vertices);
    int s=basis_sign;      
    ieq[*l]=s*basis_det;
    for(Entire<Set<int> >::const_iterator k=entire(basis);!k.at_end();++k) {
      const Set<int> rest=basis-scalar2set(*k);
      s=-s;
      ieq[*k]=s*det(vertices[*l]/vertices.minor(rest,All));
    }
    inequs/=ieq;
  }
    

  }
  perl::Object p("Cone<Rational>");

  p.take("EQUATIONS")<<equats;
  p.take("INEQUALITIES")<<inequs;

  if (options["test_regularity"]) {
     const int dim=p.give("CONE_DIM");
     if (dim!=(n_vertices-rank(equats)))
        throw std::runtime_error("Subdivision is non-regular.");
  }

  return p;
}


std::pair<bool,Vector<Rational> > is_regular(const Matrix<Rational> &verts,const Array<Set <int> >& subdiv,perl::OptionSet options)
{
  perl::Object res=secondary_cone(verts,subdiv,options);
  const int dim=res.give("CONE_DIM");
  const Matrix<Rational> equats=res.give("EQUATIONS");
  if (dim!=(equats.cols()-rank(equats)))
       return std::pair<bool,Vector<Rational> >(false,Vector<Rational>());
  const Vector<Rational> w=res.give("REL_INT_POINT");
  return std::pair<bool,Vector<Rational> >(true,w);
}


   UserFunction4perl("# @category Subdivisions"
		     "# For a given subdivision //subdiv// of //points// tests"
		     "# computes the corresponding secondary cone. If the subdivision"
                     "# is not regular, the result will be the trivial cone."
		     "# Options can be used to make the Cone [[POINTED]]."
		     "# @param Matrix points"
                     "# @param Array<Set<Int> > subdiv"
                     "# @return Cone"
                     "# @option Matrix<Rational> equations system of linear equation the cone is cut with."
                     "# @option Set<Int> lift_to_zero gives only lifting functions lifting the designated vertices to 0"
                     "# @option Int lift_face_to_zero gives only lifting functions lifting all vertices of the designated face to 0"
                     "# @author Sven Herrmann",
		     &secondary_cone,"secondary_cone($$;{equations => undef, lift_to_zero=>undef, lift_face_to_zero => undef,test_regularity=>0})");


  UserFunction4perl("# @category Subdivisions"
		     "# For a given subdivision //subdiv// of //points// tests"
		     "# if the subdivision is regular and if yes computes a weight"
		     "# vector inducing this subdivsion."
		     "# The output is a pair of bool and the weight vector."
                     "# Options can be used to ensure properties of the resulting vector."
                     "# The default is having 0 on all vertices of the first face of //subdiv//."
                     "# @param Matrix points"
                     "# @param Array<Set<Int> > subdiv"
                     "# @return Pair<bool,Cone>"
                     "# @option Matrix<Rational> equations system of linear equation the cone is cut with."
                     "# @option Set<Int> lift_to_zero gives only lifting functions lifting the designated vertices to 0"
                     "# @option Int lift_face_to_zero gives only lifting functions lifting all vertices of the designated face to 0"
		     "# @author Sven Herrmann",
		    &is_regular,"is_regular(Matrix<Rational>,$;{equations => undef, lift_to_zero=>undef, lift_face_to_zero => 0})");

} }
