/*
 * app_halfopenconecomponents.cpp
 *
 *  Created on: 5 Sep 2025
 *      Author: anders
 */

#include <cstdint>
#include <vector>
#include <map>
#include <sstream>
#include <fstream>

#include <mutex>
#include <thread>

#include "gfanlib_tableau.h"
#include "gfanapplication.h"
#include "printer.h"
#include "log.h"

#include "transitiveclosure.h"

using namespace std;

using namespace gfan;



class HalfOpenConeComponentsApplication : public GFanApplication
{
	InputFileOption inputFileOption;
	SimpleOption nConesInInputOption;
	SimpleOption connectivityThroughEdgesOption;
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";
  }
  HalfOpenConeComponentsApplication():
	  nConesInInputOption("--inputsize","Output the number of cones in input"),
	  connectivityThroughEdgesOption("--unboundededgeconnectivity","Let polyhedra be connected, if they share (not a vertex), but an unbounded edge.")
  {
	  registerOptions();
  }

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

  typedef gfan::CircuitTableInt64 typ; // type for integers
  static Matrix<typ> raysToEdges(Cone<typ> C, Matrix<typ> m)
  {
	Matrix<typ> ret(0,m.getWidth());
	for(int i=0;i<m.getHeight();i++)
		for(int j=0;j<m.getHeight();j++)
		{
			auto v=m[i].toVector()+m[j].toVector();
			auto D=intersection(C.link(v),C);
			HalfOpenCone<typ> DD(D.getInequalities(),D.getEquations(),Matrix<typ>(D.getAmbientDimension(),0));
	//		HalfOpenCone<typ>::representedPolyhedronIsBounded();
			if(!DD.representedPolyhedronIsBounded())
			if(D.getDimension()==C.getDimensionOfLinealitySpace()+2)
			{
				ret.appendRow(v);
			}
		}
		ret.sortAndRemoveDuplicateRows();
		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

	  log1 debug<<"COMPUTING ALLOWED VERTICES\n";

	  std::set<gfan::Vector<typ>> allowedVertices;

	  bool connectivityThroughEdges=connectivityThroughEdgesOption.getValue();
	  {
		  std::mutex mtx;
		  atomic_int64_t numberOfNonEmptyCones=0;
		  ThreadSafeSequenceStreamIterator i(inputFileOption.getFreshStreamReference());
		  vector<int> dummy(100);
		  std::for_each(std::execution::par, dummy.begin(),dummy.end(),[&i,&numberOfNonEmptyCones,&allowedVertices,&mtx,&connectivityThroughEdges](size_t t)
				  {
			  std::cerr<<t<<"\n";
			  while(auto s1=i.getNextString())
			  {
				  auto s=std::stringstream(s1.value().second);
				  auto c=HalfOpenCone<typ>::load(s);
				  if(!c.isEmpty())
				  {
					  numberOfNonEmptyCones++;
					  auto C=c.closure();
//					  auto m=C.getFacetNormals().transposed(); //call to getRays instead, followed by "faceContaining"
					  auto m=C.getRays();
					  if(connectivityThroughEdges)
					  	m=raysToEdges(C,m);
					  for(int i=0;i<m.getHeight();i++)
					  {
//						  auto H=Cone<typ>::hyperPlane(m[i].toVector());
						  auto H=C.link(m[i].toVector()).getLinealitySpaceAsCone();
						  auto F=intersection(C,H);
						  auto v=F.getSymmetryInvariantRelativeInteriorPoint();
						  log2 debug<<v.toString()<<"\n";
						  if(c.contains(v))
						  {
							  std::scoped_lock l(mtx);

							  log2 debug<<"Inserting\n";
							  allowedVertices.insert(v);///!!!!!!!
						  }
					  }
				  }
				  else
				  {
					  log1 debug<<"HalfOpenCone is empty!\n";
				  }
			  }
				  });
	  }

	  log1 debug<<"COMPUTING TRANSITIVE CLOSURE\n";
	  TransitiveClosure<gfan::Vector<typ>> classes;
	  {
		  {
			  ThreadSafeSequenceStreamIterator i(inputFileOption.getFreshStreamReference());
			  std::mutex mtx;
			  vector<int> dummy(100);
			  std::for_each(std::execution::par, dummy.begin(),dummy.end(),[&i,&allowedVertices,&classes,&mtx,&connectivityThroughEdges](size_t t)
					  {
				  std::cerr<<t<<"\n";
				  while(auto s1=i.getNextString())
				  {
					  auto s=std::stringstream(s1.value().second);

					  auto c=HalfOpenCone<typ>::load(s);
					  if(!c.isEmpty())
					  {
						  auto C=c.closure();
						  auto p=C.getSymmetryInvariantRelativeInteriorPoint();
						  {
							  scoped_lock l(mtx);
							  classes.addToBaseSet(p);
						  }
//						  auto m=C.getFacetNormals().transposed();
						  auto m=C.getRays();
							if(connectivityThroughEdges)
								m=raysToEdges(C,m);
						  for(int i=0;i<m.getHeight();i++)
						  {
//							  auto H=Cone<typ>::hyperPlane(m[i].toVector());
							  auto H=C.link(m[i].toVector()).getLinealitySpaceAsCone();
							  auto F=intersection(C,H);
							  auto v=F.getSymmetryInvariantRelativeInteriorPoint();
							  scoped_lock l(mtx);
							  if(allowedVertices.count(v))
								  classes.makeEquivalent(v,p); //note: all allowed vertices are eventually implicitly added to the base set
						  }
					  }

				  }
					  });

		  }
	  }

	  log1 debug<<"BUILDING NAMES\n";
	  int64_t N=classes.buildClassNames();
	  log1 debug<<"Number of classes:"<<(int)N<<"\n";

	  log1 debug<<"GROUPING INPUT\n";
	  std::multimap<int64_t,int64_t> classToIndex;
	  int64_t I=0;
	  {
		  ThreadSafeSequenceStreamIterator i(inputFileOption.getFreshStreamReference());
		  std::mutex mtx;
		  vector<int> dummy(100);
		  std::for_each(std::execution::par, dummy.begin(),dummy.end(),[&i,&I,&classToIndex,&allowedVertices,&classes,&mtx](size_t t)
				  {
			  std::cerr<<t<<"\n";
			  while(auto s1=i.getNextString())
			  {
				  auto s=std::stringstream(s1.value().second);
			  auto c=HalfOpenCone<typ>::load(s);
			  if(!c.isEmpty())
			  {
				  auto C=c.closure();
				  auto p=C.getSymmetryInvariantRelativeInteriorPoint();
				  scoped_lock l(mtx);
				  classToIndex.insert({classes.getClassName(classes.getRepresentative(p)),I});
				  I++;
			  }

		  }});
	  }
	  log1 debug<<"OUTPUTTING\n";

	  vector<string> inputStrings;
	  {
		  SequenceStreamIterator i(inputFileOption.getFreshStreamReference());
		  while(!i.eof())
		  {
			  auto S=i.getNextString();
			  auto s=std::stringstream(S);
			  auto c=HalfOpenCone<typ>::load(s);
			  if(!c.isEmpty())
				  inputStrings.push_back(S);
		  }
	  }

	  log1 debug<<"STRINGS READ\n";

	  for(int64_t i=0;i<N;i++)
	  {
		  std::vector<string> conesInComponent;
/*		  for(auto &c:f)
		  {
			  auto C=c.closure();
			  auto p=C.getSymmetryInvariantRelativeInteriorPoint();
			  if(i==classes.getClassName(classes.getRepresentative(p)))
				  conesInComponent.push_back(c);
		  }*/
		  auto r=classToIndex.equal_range(i);
		  for(auto I=r.first;I!=r.second;I++)
		  {
			  assert(I->second>=0);
			  assert(I->second<inputStrings.size());
			  if(!(I->second<inputStrings.size()))std::cerr<<I->second<<" "<<inputStrings.size()<<"\n";
			  conesInComponent.push_back(string());
			  swap(conesInComponent.back(),inputStrings[I->second]);
		  }
//		  save(conesInComponent,cout);
		  {
			  cout<<"(";
			  for(int i=0;i<conesInComponent.size();i++)
			  {
				  if(i)
					  cout<<",\n";
				  cout<<conesInComponent[i];
			  }
			  cout<<")\n";
		  }
	  }

	  return 0;
  }
};

static HalfOpenConeComponentsApplication theApplication;

