/*
 * app_processcomplex.cpp
 *
 *  Created on: 13 Aug 2025
 *      Author: anders
 */




#include <iostream>
#include <fstream>
#include <stdlib.h>
#include <sstream>
#include <mutex>
#include <thread>
#include <execution>
#include "parser.h"
#include "printer.h"
#include "polynomial.h"
#include "division.h"
#include "buchberger.h"
#include "wallideal.h"
#include "lp.h"
#include "reversesearch.h"
#include "termorder.h"
#include "ep_standard.h"
#include "ep_xfig.h"
#include "polyhedralcone.h"
#include "gfanapplication.h"
#include "saturation.h"
#include "field_rationals.h"
#include "field_zmodpz.h"
#include "field_rationalfunctions.h"
#include "symmetry.h"
#include "linalg.h"
#include "fieldlp.h"
#include "integer.h"
#include "polynomialgcd.h"
#include "packedmonomial.h"
#include "gfanlib_zcone.h"
#include "gfanlib_tableau.h"
#include "gfanlib_hypersurfaceintersection.h"
#include "gfanlib_circuittableint.h"
#include "gfanlib_mixedvolume.h"
#include "lll.h"
#include "gfanlib_circuittableinteger.h"
#include "tropical2.h"
#include "app_anton.h"

using namespace gfan;

class ProcessComplexApplication : public GFanApplication
{
	InputFileOption inputFileOption;
	SimpleOption nConesInInputOption;
	SimpleOption fvectorOption;
	SimpleOption postComplexOption;
	SimpleOption optionLinealitySpace;
	SimpleOption optionUnboundedDirections;
	SimpleOption optionDebug;
	SimpleOption optionRecessionCone;
	IntegerOption optionSelectUnboundedBounded;
public:
  bool includeInDefaultInstallation()
  {
    return false;
  }
  const char *helpText()
  {
    return "This program is used for processing a set of half-open cones - for example produced by the _tropicalprevariety command with option --matrixoutput.\n";
  }
  ProcessComplexApplication():
	  fvectorOption("--fvector","Compute and output f-vector"),
	  nConesInInputOption("--inputsize","Output the number of cones in input"),
	  postComplexOption("--postcomplex","Output complex"),
	  optionLinealitySpace("--linealityspace","Output generators of lineality space"),
	  optionUnboundedDirections("--rays","Output --rays--??"),
	  optionRecessionCone("--recessioncone","Output closure of the recession cone (given by generators)."),
	  optionSelectUnboundedBounded("--unboundedbouded","Output a set of halfopen cones covering the bounded/unbounded relatively open faces of the input. 1 chooses unbounded, 2 chooses bounded."),
	  optionDebug("--debug","Enable debug.")
  {
	  registerOptions();
  }

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

  template<typename typ>
  gfan::Matrix<typ> convertMatrix(IntMatrix const &m)
  {
	  gfan::Matrix<typ> ret(m.getHeight(),m.getWidth());
	  for(int i=0;i<m.getHeight();i++)
		  for(int j=0;j<m.getWidth();j++)
			  ret[i][j]=typ(m[i][j]);
	  return ret;
  }

  IntMatrix rowsToIntegerMatrix2(IntegerVectorList const &l)//taken from app_mixedvolume.cpp
  {
	  assert(l.size());
	  IntMatrix ret(l.size(),l.front().size());
	  IntegerVectorList::const_iterator I=l.begin();
	  for(int i=0;i<ret.getHeight();i++,I++)
		  for(int j=0;j<ret.getWidth();j++)
			  ret[i][j]=(*I)[j];
	  return ret;
  }
  int main()
  {
    //	typedef gfan::CircuitTableInt128 typ; // type for integers
	  typedef gfan::CircuitTableInt64 typ; // type for integers
	  //	    typedef gfan::CircuitTableInteger typ; // type for integers
//	  auto f2=loadHalfOpenConeVector<typ>(cin);
#if 0
	  std::vector<HalfOpenCone<typ>> f;
	  for(auto &c:f2)
		  if(c.isEmpty())
			  debug<<"HalfOpenCone is empty!\n";
		  else
		  {
			  f.push_back(HalfOpenCone<typ>());
			  swap(c,f.back());
		  }

	  if(nConesInInputOption.getValue())
		  std::cout<<f.size()<<"\n";
if(0)	  {
		  std::set<gfan::Matrix<gfan::CircuitTableInt64>> theSet;
	  for(auto &K:f)
	  {
//		  cerr<<"HALFOPENCONE:";K.save(cerr);
//		  auto KK=K.closure();
//		  cerr<<"CLOSURE:";KK.save(cerr);
		  gfan::Matrix<gfan::CircuitTableInt64> linealitySpace=K.closure().getLinealitySpace();
//	        std::cerr<<"LinealitySpace:\n"<<linealitySpace.toString()<<"\n";
	        theSet.insert(linealitySpace);
	  }
	  	  theSet.erase(theSet.begin());
	  }
#endif


	int ambientDimension=-1;
	gfan::Matrix<typ> linealitySpace;
	{
		ThreadSafeSequenceStreamIterator i(inputFileOption.getFreshStreamReference(),true);
		if(auto s1=i.getNextString())
		{
			  auto s=std::stringstream(s1.value().second);
			  auto c=HalfOpenCone<typ>::load(s);

			  ambientDimension=c.getAmbientDimension()+1;
			  linealitySpace=c.closure().getLinealitySpace();
		}
		else
		{
			std::cerr<<"Error: The set contains no polyhedra.";
			assert(0);
		}
	}

	if(optionSelectUnboundedBounded.getValue())
	{
		std::cout<<"(\n";
		ThreadSafeSequenceStreamIterator i(inputFileOption.getFreshStreamReference());
		  while(auto s1=i.getNextString())
		  {
			bool first=true;
			  auto s=std::stringstream(s1.value().second);
			  auto c=HalfOpenCone<typ>::load(s);
			  vector<HalfOpenCone<typ>>	temp;
			  if(optionSelectUnboundedBounded.getValue()==2)
			  {
			  	  c.iterateOverBoundedPart(0,[&temp](HalfOpenCone<typ> const& c)mutable->void{temp.push_back(c);});
			  }
			  else
			  	temp.push_back(c.unionOfUnboundedFacesOfRepresentedConjectured());

			for(auto &c:temp)
			{
				if(!first)
				{
					std::cout<<",";
				}
				first=false;
				c.save(std::cout);
			}
		  }

		std::cout<<")\n";
	}

	  if(fvectorOption.getValue())
	  {
		  Vector<int> fv(ambientDimension);
		  ThreadSafeSequenceStreamIterator i(inputFileOption.getFreshStreamReference());
		  while(auto s1=i.getNextString())
		  {
			  auto s=std::stringstream(s1.value().second);
			  auto c=HalfOpenCone<typ>::load(s);
			  fv=fv+c.fVector();
		  }

		  std::cout<<fv<<"\n";
	  }


	  if(optionLinealitySpace.getValue())
	  {
		  std::cerr<<"LinealitySpace:\n"<<linealitySpace.toString()<<"\n";
		  std::cout<<linealitySpace.toString()<<"\n";
	  }

		//	  std::cerr<<"f.size():"<<f.size()<<"\n"<<std::flush;

//      if (f.empty()) std::cerr<<"No remaining cones!!!\n";
//      else
		RayCollector<typ> collector(linealitySpace);
	  {
			int count = 0;

			ThreadSafeSequenceStreamIterator i(inputFileOption.getFreshStreamReference(),true);
			  std::mutex mtx;
			  vector<int> dummy(100);
			  std::for_each(std::execution::par, dummy.begin(),dummy.end(),[&](size_t t)
					  {
			while(auto s1=i.getNextString())
			{
				auto s=std::stringstream(s1.value().second);
				auto c=HalfOpenCone<typ>::load(s);
				if (count++ % 5000 == 0)
					std::cerr << "ray collector: " << /*f.size()*/-count << " cones remaining\n";
				if(!c.isEmpty())
				{
					auto rays=c.closure().getRays();
					{
						scoped_lock l(mtx);
					for(int i=0;i<rays.getHeight();i++)
						collector.lookup(normalize(rays[i].toVector()));
					}
				}
			}
					  });
			std::cerr << "N_RAYS\n"<<collector.getRays().getHeight() <<"\n";


			if(optionRecessionCone.getValue())
			{
				auto rays=collector.getRays();
				auto actuallyUnbounded=rays;
				int64_t actual=0;
				for(int64_t i=0;i<rays.getHeight();i++)
					if(rays[i][0].isZero())
					{
						actuallyUnbounded[actual]=rays[i];
						actual++;
					}
				actuallyUnbounded=actuallyUnbounded.submatrixRows(0, actual);
				std::cout<<"("<<linealitySpace.getWidth()<<",\n"<<actuallyUnbounded<<","<<linealitySpace.toString()<<")\n";
			}
	  }
	  {
        if(postComplexOption.getValue()||optionUnboundedDirections.getValue()/*||optionRecessionCone.getValue()*/)
        {
        	std::vector<HalfOpenCone<typ>> f;
			ThreadSafeSequenceStreamIterator i(inputFileOption.getFreshStreamReference(),true);
			while(auto s1=i.getNextString())
			{
				auto s=std::stringstream(s1.value().second);
				auto c=HalfOpenCone<typ>::load(s);
        		if(c.isEmpty())
        			debug<<"HalfOpenCone is empty!\n";
        		else
        		{
        			f.push_back(HalfOpenCone<typ>());
        			swap(c,f.back());
        		}
			}

      	  //std::cerr<<extractFaceComplex(f,collector,linealitySpace,0); // (not a complex)
			//auto fc = extractFaceComplex(f,collector,linealitySpace,0,true/*closure*/,true/*only max cones*/);
			//PostComplex<typ> postcomplex(fc,true);



        	PostComplex<typ> postcomplex(f,collector);
			//std::cerr << postcomplex;

	        if(optionUnboundedDirections.getValue())
	        	std::cout<<postcomplex.rays;
			if(postComplexOption.getValue())
				std::cout<<postcomplex;
/*			if(optionRecessionCone.getValue())
			{
				std::cout<<"("<<linealitySpace.getWidth()<<",\n"<<postcomplex.rays.toString()<<","<<linealitySpace.toString()<<")\n";
			}*/
        }
      }


#if 0
      vector<HalfOpenCone<CircuitTableInteger> > f2;
      vector<gfan::Matrix<CircuitTableInteger> > f2Rays;
      f2.reserve(f.size());
      f2Rays.reserve(f.size());


      for(auto &a:f)
    	  if(!a.isEmpty())
    	  {
    		  auto temp=HalfOpenCone<CircuitTableInteger>(a);
    		  auto tempRays=temp.closure().getRays();
    		  gfan::Matrix<CircuitTableInteger> actualUnbounedDirections(0,ambientDimension);

    		  debug<<tempRays.toString();
    		  for(int i=0;i<tempRays.getHeight();i++)
    			  if(!tempRays[i][0].isNegative())
    				  tempRays.appendRow(tempRays[i].toVector().subvector(1,ambientDimension-1));
    		  f2Rays.push_back(tempRays);
    		  f2.push_back(temp);
    	  }
		  else
		  {
			  std::cerr<<"Empty set computed\n"<<a.toString()<<"\nLifted:\n"<<a.lifted.toString()<<"\n";
		  }

      std::cerr<<"DONE CONVERTING FROM INT64 AND COMPUTING RAYS\n";

	  auto linealitySpace=f2.begin()->closure().getLinealitySpace();
	  RayCollector<CircuitTableInteger> collector(linealitySpace);

	  int numberOfNonEmptyHalfOpenCones=0;
/*	  EquivalenceRelation rel;
	  for(int i=0;i<f2.size();i++)
		  {
			  set<int> toBeMadeEquivalent;
			  if(!(numberOfNonEmptyHalfOpenCones&255))std::cerr<<numberOfNonEmptyHalfOpenCones<<"\n";
			  numberOfNonEmptyHalfOpenCones++;
			  std::cerr<<"Getting rays\n";
			  auto rays=f2Rays[i];
			  std::cerr<<"Preparing"<<rays.getHeight()<<"\n";
			  for(int i=0;i<rays.getHeight();i++)
			  {
				  int index=collector.lookup(normalize(rays[i].toVector()));
				  if(rays[i][0].isNegative())
					  toBeMadeEquivalent.insert(index);
			  }
			  std::cerr<<"Making equivalent\n";
			  if(!toBeMadeEquivalent.empty())
				  for(auto i=toBeMadeEquivalent.begin();i!=toBeMadeEquivalent.end();i++)
					  rel.makeEquivalent(*i,*toBeMadeEquivalent.begin());
		  }
	  std::cerr<<"DONE SETTING UP EQUIVALENCES\n";
	  int N_RAYS=collector.getRays().getHeight();
	  rel.grow(collector.getRays().getHeight());
*/
//	  std::cerr<<"N_RAYS"<<N_RAYS<<"\n";
	  debug<<"Lineality"<<linealitySpace.toString()<<"\n";
	  for(auto &a:f2Rays)
		  debug<<a.toString();
#endif

	  return 0;
  }
};

static ProcessComplexApplication theApplication;
