#include "parser.h"
#include "printer.h"
#include "polynomial.h"
#include "wallideal.h"
#include "lp.h"
#include "polyhedralcone.h"
#include "gfanapplication.h"
#include "polyhedralfan.h"
#include "halfopencone.h"
#include "gfanlib_circuittableint.h"
#include "gfanlib_circuittableinteger.h"
#include "gfanlib_tableau.h"
#include "matrix.h"
#include "symmetry.h"
#include "log.h"
#include <map>

template<class typ>
bool lexLess(gfan::Vector<typ> a, gfan::Vector<typ> b)
{
	assert(a.size()==b.size());
	int i=0;
	for(;i<a.size();i++)
		if(a[i]!=b[i])
			break;
	if(i==a.size())return false;
	return a[i]<b[i];
}

template<class typ>
bool isBetter(gfan::Vector<typ> a, gfan::Vector<typ> b)
{
	log2 debug<<"Comparing "<<a.toString()<<" "<<b.toString()<<"\n";
	assert(a!=b);
	int i=0;
	while(a[i]==b[i])i++;
	auto A=a[i];
	auto B=b[i];
	a=-B*a;
	b=-A*b;
	bool ret=lexLess(b,a);
	log2 debug<<ret<<"\n";
	return ret;
}


class ToDisjointUnionApplication : public GFanApplication
{
/*	class ConeOrbit{
	public:
		gfan::Cone representative;
		map<IntegerVector,IntegerVector> elements; //Unique point to permutation
	};*/
	class SymmetricConeCollector
	{
		SymmetryGroup G;
	public:
		std::map<gfan::Vector<gfan::CircuitTableInteger>,gfan::Cone<gfan::CircuitTableInteger>> v; //Unique class representative
	public:
		SymmetricConeCollector(SymmetryGroup const &G_):
			G(G_)
		{
		}
		void insertCone(gfan::Cone<gfan::CircuitTableInteger> &C)
		{
			auto u=C.getRelativeInteriorPoint();
		}
		gfan::Cone<gfan::CircuitTableInteger> lookUp(gfan::Vector<gfan::CircuitTableInteger> const &u)
		{
			return v[u];
		}
		typedef std::map<gfan::Vector<gfan::CircuitTableInteger>,gfan::Cone<gfan::CircuitTableInteger>>::iterator iterator;
	};
//  SimpleOption optionSymmetry;
public:
  const char *helpText()
  {
    return "This program takes a list of generated cones fitting together in a complex. Then it tries to cover the support of that fan with a set of half-open cones.\n";
  }
  ToDisjointUnionApplication()
//    optionSymmetry("--symmetry",
//		   "Tell the program to read in generators for a group of symmetries (subgroup of $S_n$) after having read in the ring. The output is grouped according to these symmetries. Only one representative for each orbit is needed on the input.\n")
  {
    registerOptions();
  }

  const char *name()
  {
    return "_todisjointunion";
  }

  int main()
  {
	  using typ=gfan::CircuitTableInteger;


	  gfan::Vector<typ> firstFacetVector(0);
	  auto vec=gfan::Cone<typ>::loadConeVector(cin);
	  assert(vec.size());
	  int n=vec[0].getAmbientDimension();

	  std::multimap<gfan::Vector<typ>,gfan::Vector<typ>> ridgesFacets;

	  int d;
		 for(auto &C:vec)
		 {
			 debug<<".";
			gfan::Cone<typ> cone(C.getRays().transposed(),C.getLinealitySpace().transposed());
			d=cone.getDimension();
			auto facetUniquePoint=cone.getSymmetryInvariantRelativeInteriorPoint();
			if(firstFacetVector.size()==0)firstFacetVector=facetUniquePoint;
			auto normals=cone.getFacetNormals();

			for(int i=0;i<normals.getWidth();i++)
			{
				gfan::Vector<typ> v=normals.column(i);
				auto hyperplane=gfan::Cone<typ>::hyperPlane(v);
				auto cone2=intersection(cone,hyperplane);
				auto ridgeUniquePoint=cone2.getSymmetryInvariantRelativeInteriorPoint();
				ridgesFacets.insert({ridgeUniquePoint,facetUniquePoint});
				ridgesFacets.insert({facetUniquePoint,ridgeUniquePoint});
			}
		 }

		 std::map<gfan::Vector<typ>,gfan::Vector<typ>> ridgeBelongsTo;
		 std::map<gfan::Vector<typ>,gfan::Vector<typ>> ridgeBelongsToReason;

		 gfan::Matrix<typ> randomM(d-1,n);
		 for(int a=0;a<randomM.getHeight();a++)
			 for(int b=0;b<randomM.getWidth();b++)
				 randomM[a][b]=rand()%2000;

		 auto genericSpace=gfan::Cone<typ>(gfan::Matrix<typ>(0,n).transposed(),randomM.transposed());

		 std::multimap<gfan::Vector<typ>,pair<gfan::Vector<typ>,gfan::Vector<typ>>> ridgeFacetAlternatives;
		 {
		    	for(auto &C:vec)
		        	{
					 debug<<"*";
		    			gfan::Cone<typ> cone(C.getRays().transposed(),C.getLinealitySpace().transposed());
		    			auto facetUniquePoint=cone.getSymmetryInvariantRelativeInteriorPoint();
		    			auto normals=cone.getFacetNormals();

		    			for(int i=0;i<normals.getWidth();i++)
		    			{
		    				gfan::Vector<typ> v=normals.column(i);
		    				auto hyperplane=gfan::Cone<typ>::hyperPlane(v);
		    				auto cone2=intersection(cone,hyperplane);
		    				auto ridgeUniquePoint=cone2.getSymmetryInvariantRelativeInteriorPoint();

							auto cone3=cone.link(ridgeUniquePoint);

							cone3=intersection(cone3,genericSpace);
		    				assert(cone3.getDimension()==1);

		    				auto theGenerator=cone3.getSymmetryInvariantRelativeInteriorPoint();
		    				if(lexLess(theGenerator,gfan::Vector<typ>(ridgeUniquePoint.size())))
		    					ridgeFacetAlternatives.insert({ridgeUniquePoint,{facetUniquePoint,theGenerator}});
		    			}

		        	}
		 }

		 for(auto &p:ridgeFacetAlternatives)
		 {
			 log2 debug<<p.first.toString()<<p.second.first.toString()<<"\n";
		 }

		 for(auto &p:ridgeFacetAlternatives)
		 {
			 if(ridgeBelongsTo.count(p.first)==0||isBetter(ridgeBelongsToReason[p.first],p.second.second))
			 {
				 ridgeBelongsTo[p.first]=p.second.first;
				 ridgeBelongsToReason[p.first]=p.second.second;
			 }
		 }
		 log2 debug<<"size"<<(int)ridgeBelongsTo.size()<<"\n";


    	vector<gfan::HalfOpenCone<typ>> halfOpenConeVector;

    	for(auto &C:vec)
    	{
    		gfan::Cone<typ> cone(C.getRays().transposed(),C.getLinealitySpace().transposed());
    		auto facetUniquePoint=cone.getSymmetryInvariantRelativeInteriorPoint();
    		auto normals=cone.getFacetNormals();

    		gfan::Matrix<typ> strictNormals(0,normals.getHeight());
    		gfan::Matrix<typ> nonStrictNormals(0,normals.getHeight());
    		for(int i=0;i<normals.getWidth();i++)
    		{
    			gfan::Vector<typ> v=normals.column(i);
    			auto hyperplane=gfan::Cone<typ>::hyperPlane(v);
    			auto cone2=intersection(cone,hyperplane);
    			auto ridgeUniquePoint=cone2.getSymmetryInvariantRelativeInteriorPoint();
    			((ridgeBelongsTo[ridgeUniquePoint]!=facetUniquePoint)?strictNormals:nonStrictNormals).appendRow(v);
    		}
    		if(strictNormals.getHeight()==0)debug<<"ORIGININCLUDED\n";
    		gfan::HalfOpenCone<typ> halfOpenCone(nonStrictNormals.transposed(),cone.getEquations(),strictNormals.transposed());
			halfOpenConeVector.push_back(halfOpenCone);
		}

		save(halfOpenConeVector,cout);

		{
			set<gfan::Vector<typ>> reps;
			int64 totalCount=0;
			for(auto &c:halfOpenConeVector)
			{
				auto temp=c.collectUniqueRaysOfFaces();
				for(auto &v:temp)
					reps.insert(v);
				totalCount+=temp.size();
			}
			std::cerr<<"TotalFace count"<<totalCount<<"\n";
			std::cerr<<"UNique face reps:"<<reps.size()<<"\n";
		}
    return 0;
  }
};

static ToDisjointUnionApplication theApplication;
