/*
 * app_tropicalhypersurfacereconstruction.cpp
 *
 *  Created on: Feb 5, 2011
 *      Author: anders
 */


/*
 * app_fancoarsening.cpp
 *
 *  Created on: Jul 19, 2010
 *      Author: anders
 */

#include "parser.h"
#include "printer.h"
#include "polynomial.h"
#include "division.h"
#include "lp.h"
#include "gfanapplication.h"
#include "polyhedralcone.h"
#include "polyhedralfan.h"
#include "symmetry.h"
#include "bsptree.h"
#include "field_rationals.h"
#include "linalg.h"
#include <map>
#include "subspace.h"

#include "polymakefile.h"

#include "traverser_sphere.h"
#include "traverser_bsptree.h"

class TropicalHypersurfaceReconstrutionApplication : public GFanApplication
{

  StringOption inputOption;
  SimpleOption challengeOption;
  SimpleOption projectionOption;
  SimpleOption symmetryOption;

  multimap<IntegerVector,int> coneIndicesAtRidge;
  vector<PolyhedralCone> F1,F2;
public:
  bool includeInDefaultInstallation()
  {
    return false;
  }

  const char *helpText()
  {
    return "Similar to _fancoarsening, but no fan structure required for algorithm\n";
  }
  TropicalHypersurfaceReconstrutionApplication():
    inputOption("-i","Specify the name of the input file.","polymake.out"),
    challengeOption("--challenge","...."),
    projectionOption("--projection","Read in a linear space and add it to the cones in the fan before reconstructing the hypersurface."),
    symmetryOption("--symmetry","Specify known symmetries of the output fan. The generators for the symmetry group are read after the projection matris.")
  {
    registerOptions();
  }

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

  class MyTarget : public SymmetricTarget
  {
    PolyhedralFan f;
    list<IntegerVector> normals;
    bool process(ConeTraverser &traverser)
    {
//      if(!f.contains(traverser.refToPolyhedralCone()))
        {
                        PolyhedralFan temp(traverser.refToPolyhedralCone().ambientDimension());
                        temp.insert(traverser.refToPolyhedralCone());
                        temp.printWithIndices(&debug);
//                        assert(0);
      }
      return true;
    }
  public:
    MyTarget(PolyhedralFan const &f_):
      f(f_)
    {

    }
/*    PolyhedralCone toCone()
    {
      PolyhedralCone c(normals,IntegerVectorList(),normals.begin()->size());
      c.canonicalize();
      return c;
    }*/
  };



  /*
   * returns permutation which has been APPLIED
   */
  PolyhedralCone coneReprentative(PolyhedralCone const &c2, SymmetryGroup const &g, IntegerVector &perm)
  {
    IntegerVector perm1;
    PolyhedralCone c=c2;
    c.canonicalize();
    IntegerVector up=c.getUniquePoint();
    up=g.orbitRepresentative(up,&perm1);
    IntegerVectorList s=g.stabilizer(up);
    c=c.permuted(perm1);
    PolyhedralCone ret=c;
    ret.canonicalize();
    perm=perm1;
    for(IntegerVectorList::const_iterator i=s.begin();i!=s.end();i++)
      {
        PolyhedralCone temp=c.permuted(*i);
        temp.canonicalize();
        if(temp<ret)
          {
            ret=temp;
            perm=SymmetryGroup::compose(*i,perm1);
          }
      }
    return ret;
  }

  /*
   * Assumes A is fulldimensional. Checks if the cone generated by rays and lines
   * intersects the relative interior of A.
   */
  static bool intersectsInterior(PolyhedralCone const &A, IntegerVectorList const &rays, IntegerVectorList const &lines)
  {
    int Anineq=A.getHalfSpaces().size();
    IntegerMatrix inequalities=combineLeftRight(combineLeftRight(combineLeftRight(
             -rowToIntegerMatrix(IntegerVector::allOnes(Anineq+rays.size())).transposed(),
             combineOnTop(rowsToIntegerMatrix(A.getHalfSpaces(),A.ambientDimension()),IntegerMatrix(rays.size(),A.ambientDimension()))),
             combineOnTop(IntegerMatrix(Anineq,rays.size()),IntegerMatrix::identity(rays.size()))),
             IntegerMatrix(Anineq+rays.size(),lines.size()));
    IntegerMatrix equations=combineLeftRight(combineLeftRight(combineLeftRight(
        rowToIntegerMatrix(IntegerVector(A.ambientDimension())).transposed(),
        -IntegerMatrix::identity(A.ambientDimension())),
        rowsToIntegerMatrix(rays,A.ambientDimension()).transposed()),
        rowsToIntegerMatrix(lines,A.ambientDimension()).transposed());

    bool fastResult=hasHomogeneousSolution(inequalities.getWidth(),inequalities.getRows(),equations.getRows());

    if(fastResult==false)debug<<"TEST-";

/*    {
      assert(A.dimension()==intersection(A,PolyhedralCone::givenByRays(rays,lines,A.ambientDimension())).dimension());

      bool slowResult;
    }*/
    return fastResult;
  }


  int main()
  {
    if(challengeOption.getValue())
      {
        PolyhedralFan f1=PolyhedralFan::readFan(inputOption.getValue(),true,0,0,0,1);
        int n=f1.getAmbientDimension();

        IntegerVectorList s=StringParser("{(0,1,2,3,8,9,10,11,4,5,6,7,12,13,14,15),"
                                         " (0,1,8,9,4,5,12,13,2,3,10,11,6,7,14,15),"
                                         " (0,8,2,10,4,12,6,14,1,9,3,11,5,13,7,15),"
                                         " (8,9,10,11,12,13,14,15,0,1,2,3,4,5,6,7)}").parseIntegerVectorList();

        SymmetryGroup sym(n);
        sym.computeClosure(s);
        PolyhedralFan f2(n);
        for(PolyhedralFan::coneIterator i=f1.conesBegin();i!=f1.conesEnd();i++)
          {
            for(SymmetryGroup::ElementContainer::const_iterator j=sym.elements.begin();j!=sym.elements.end();j++)
              f2.insert(i->permuted(*j));
          }

        debug<<f2.size();

        PolyhedralCone fundamentalDomain(sym.fundamentalDomainInequalities(true),IntegerVectorList(),n);
        fundamentalDomain.canonicalize();

        debug<<fundamentalDomain;


        vector<IntegerVectorList> generatorsOfSpans;
        for(PolyhedralFan::coneIterator i=f2.conesBegin();i!=f2.conesEnd();i++)generatorsOfSpans.push_back(i->generatorsOfSpan());


        {
          int numberOfSumsWithRightDimension=0;
          int numberWhichIntersect=0;
          PolyhedralFan L(n);
          //Just to see if this way of iterating is faster.
          typedef map<Subspace,set<PolyhedralCone> > MyContainer;
          MyContainer theCones1;
          MyContainer theCones2;
          for(PolyhedralFan::coneIterator i=f1.conesBegin();i!=f1.conesEnd();i++)
            theCones1[Subspace(i->generatorsOfSpan())].insert(*i);
          for(PolyhedralFan::coneIterator i=f2.conesBegin();i!=f2.conesEnd();i++)
            theCones2[Subspace(i->generatorsOfSpan())].insert(*i);
          debug<<"theCones1.size()"<<(int)theCones1.size()<<"\n";
          debug<<"theCones2.size()"<<(int)theCones2.size()<<"\n";
          for(MyContainer::const_iterator i=theCones1.begin();i!=theCones1.end();i++)
            {
            static int t;
              debug<<"BLBBAL"<<t++<<":"<<(int)L.size()<<"\n";
              for(MyContainer::const_iterator j=theCones2.begin();j!=theCones2.end();j++)
                {
                Subspace s=sum(i->first,j->first);
                if(s.dimension()==15)
                    {
//                  debug<<intersection(PolyhedralCone::givenByRays(IntegerVectorList(),s.getRepresentation(),fundamentalDomain.ambientDimension()),fundamentalDomain).dimension();
                    for(set<PolyhedralCone>::const_iterator i2=i->second.begin();i2!=i->second.end();i2++)
                      {
//                      debug<<intersection(PolyhedralCone::givenByRays(i2->extremeRays(),j->first.getRepresentation(),fundamentalDomain.ambientDimension()),fundamentalDomain).dimension();
                      IntegerVectorList IG=i2->extremeRays();

                      PolyhedralCone fundamental2=PolyhedralCone::givenByRays(IntegerVectorList(),s.getRepresentation(),fundamentalDomain.ambientDimension());
                      fundamental2=intersection(fundamental2,fundamentalDomain);
                      for(set<PolyhedralCone>::const_iterator j2=j->second.begin();j2!=j->second.end();j2++)
                    {
                        numberOfSumsWithRightDimension++;
                      IntegerVectorList JG=j2->extremeRays();
                      IntegerVectorList G=i2->generatorsOfLinealitySpace();//SAMEFOR BOTH
                      for(IntegerVectorList::const_iterator i=IG.begin();i!=IG.end();i++)JG.push_back(*i);

                      if(!intersectsInterior(fundamental2,JG,G))continue;

//                      PolyhedralCone A=PolyhedralCone::givenByRays(JG,G,n);
//                      PolyhedralCone A2=PolyhedralCone::givenByRays(JG,G,n);
                      //PolyhedralCone B=intersection(fundamentalDomain,A);
                      //if(B.dimension()==15)

                      {
                        PolyhedralCone A=PolyhedralCone::givenByRays(JG,G,n);
                        IntegerVector perm;
                        L.insert(coneReprentative(A,sym,perm));
                        //                        PolyhedralCone A=PolyhedralCone::givenByRays(JG,G,n);
//                        PolyhedralCone A=PolyhedralCone(JG,G,n);//dual cone
//                          A.canonicalize();
//                          L.insert(A);
                        numberWhichIntersect++;
                          static int a;
                          a++;
//                          if (a==30000)goto leave2;
                        }
                      //else{debug<<"skipping";}
                    }}
                    }
                }
            }
          leave2:
          debug<<"numberOfSumsWithRightDimension"<<numberOfSumsWithRightDimension<<"\n";
          debug<<"numberWhichIntersect"<<numberWhichIntersect<<"\n";
          debug<<"L.size"<<L.size()<<"\n";


          debug<<(int)sym.elements.size()<<"\n";

          vector<BSPTree::SmallCone> LL;
          for(PolyhedralFan::coneIterator i=L.conesBegin();i!=L.conesEnd();i++)
            {
//            debug<<"A";
            IntegerVectorList perms;
            set<PolyhedralCone> orbit;
              for(SymmetryGroup::ElementContainer::const_iterator j=sym.elements.begin();j!=sym.elements.end();j++)
                {
                  PolyhedralCone temp(i->permuted(*j));
                  if(!orbit.count(temp))
                    {
                    orbit.insert(temp);
                    perms.push_back(*j);
                    LL.push_back(BSPTree::SmallCone(&(*i),*j));
                    //if(LL.size()>500000)goto leave;
                    }
                }
              static int A;
              if(!((A++)&127))debug<<"A"<<A++<<(int)orbit.size()<<"\n";
            }
          leave:
          debug<<"LLsize:"<<(int)LL.size()<<"\n";

          BSPTree tree(n,LL,&fundamentalDomain,false);
//          BSPTree tree(n,LL,&fundamentalDomain);
          debug<<"Done\n";
          debug<<"Number of regions"<<tree.numberOfRegions()<<"\n";

          {
            BSPTreeTraverser2 traverser(n,tree,false);//change to true?

            SymmetricTargetFanBuilder myTarget(n,sym);
            symmetricTraverse(traverser,myTarget);
            myTarget.getFanRef().printWithIndices(&pout);
          }

          return 0;//need to do dual cones
        }
//        leave2:
        //600:301 1612751 3GB
//35 min:3000--900000  1660MB

        debug<<"maxconesinfan"<<f2.size()<<"\n";
        vector<PolyhedralCone> LL;

#if 0
        {
        PolyhedralFan L(n);
        for(PolyhedralFan::coneIterator i=f2.conesBegin();i!=f2.conesEnd();i++)
          {
            IntegerVectorList IG=i->extremeRays();
            static int t;
            debug<<t++<<":"<<(int)L.size()<<"\n";
            IntegerVectorList I=i->generatorsOfSpan();
            //IntegerVectorList I2=i->generatorsOfLinealitySpace();
            //for(IntegerVectorList::const_iterator k=I2.begin();k!=I2.end();k++)I.push_back(*k);
            int jj=0;
            for(PolyhedralFan::coneIterator j=f2.conesBegin();j!=i;j++,jj++)
//              for(PolyhedralFan::coneIterator j=f1.conesBegin();j!=f1.conesEnd();j++)
              {


                IntegerVectorList J=generatorsOfSpans[jj];//j->generatorsOfSpan();
                for(IntegerVectorList::const_iterator k=I.begin();k!=I.end();k++)
                  J.push_back(*k);
                IntegerMatrix M=rowsToIntegerMatrix(J);
                FieldMatrix M2=integerMatrixToFieldMatrix(M,Q);

               // PolyhedralCone C(IntegerVectorList(),J,n);
               // C.canonicalize();
                if(M2.reduceAndComputeRank()>=15)
                  {
                    IntegerVectorList JG=j->extremeRays();
                    IntegerVectorList G=i->generatorsOfLinealitySpace();//SAMEFOR BOTH
                    for(IntegerVectorList::const_iterator i=IG.begin();i!=IG.end();i++)JG.push_back(*i);
                    PolyhedralCone A=PolyhedralCone::givenByRays(JG,G,n);
                    PolyhedralCone B=intersection(fundamentalDomain,A);
                    if(B.dimension()==15)
//                    if(BSPTree::isIntersectionOfCodimensionOne(fundamentalDomain,A))
                     {
                        A.canonicalize();
                        L.insert(A);
                        static int a;
                        a++;
//                        if (a==10000)goto leave;
//                        debug<<"found one\n";
                      }
                  }
//                debug<<C.dimension();
              }
          }



leave:
        debug<<"Copying\n";
/*        for(PolyhedralFan::coneIterator i=L.conesBegin();i!=L.conesEnd();i++)
          {
            LL.push_back(*i);
          }
          */
        L.moveConesToVector(LL);
        }
#endif
        debug<<"Building tree..\n";
//        BSPTree tree(n,LL,&fundamentalDomain);
        debug<<"Done\n";
  //      debug<<"Number of regions"<<tree.numberOfRegions()<<"\n";

        return 0;
      }

    PolyhedralFan f1=PolyhedralFan::readFan(inputOption.getValue(),true,0,0,0);


    int n=f1.getAmbientDimension();
//n=3;
//    PolyhedralFan f2=PolyhedralFan::readFan("glued2",true,0,0,0);

    if(projectionOption.getValue())
      {
        FileParser P(Stdin);
        IntegerVectorList L=P.parseIntegerVectorList();
        IntegerVectorList empty;
        PolyhedralCone LL=          PolyhedralCone::givenByRays(empty,L,n);
        LL.canonicalize();
        for(PolyhedralFan::coneIterator i=f1.conesBegin();i!=f1.conesEnd();i++)
          {
            IntegerVectorList A=i->generatorsOfSpan();
            IntegerVectorList B=LL.generatorsOfSpan();
            for(IntegerVectorList::const_iterator i2=A.begin();i2!=A.end();i2++)B.push_back(*i2);
            if(::rank(rowsToIntegerMatrix(B,n))==n-1)
            {
            PolyhedralCone P=sum(*i,LL);
            P.canonicalize();
            if(P.dimension()==n-1)
              F1.push_back(P);
            }
          }
        debug<<"CONES OF CODIMENSION 1 "<<(int)F1.size()<<"\n";
        {
          PolyhedralFan F3(n);
          for(vector<PolyhedralCone>::const_iterator i=F1.begin();i!=F1.end();i++)
            F3.insert(*i);
          debug<<"UNIQUE CONES OF CODIMENSION 1 "<<(int)F3.size()<<"\n";
        }
      }
    else
      for(PolyhedralFan::coneIterator i=f1.conesBegin();i!=f1.conesEnd();i++)F1.push_back(*i);


    SymmetryGroup s(n);
    if(symmetryOption.getValue())
      {
        FileParser P(Stdin);
        s.computeClosure(P.parseIntegerVectorList());
      }
    s.createTrie();

    /*    for(int i=0;i<n;i++)
      {
        IntegerVectorList temp;
        temp.push_back(IntegerVector::standardVector(n,i));
        F1.push_back(PolyhedralCone(IntegerVectorList(),temp,n));
      }
*/
    AsciiPrinter P(Stdout);
debug<<"a\n";


//BSPTree tree(n,F1);
vector<BSPTree::SmallCone> a=BSPTree::buildPointers(F1);
BSPTree tree(n,a,0,false);

//    debug<<"a\n";
//tree.print();
debug<<"Number of regions"<<tree.numberOfRegions()<<"\n";

/*    map<IntegerVector,list<int> > adjacency;
    for(int i=0;i<F1.size();i++)
      {
        PolyhedralFan temp(F1[i].ambientDimension());
        temp.insert(F1[i]);
        PolyhedralFan facets=temp.facetComplex();
        for(PolyhedralFan::coneIterator j=facets.conesBegin();j!=facets.conesEnd();j++)
          adjacency[j->getUniquePoint()].push_back(i);
      }
    SphereTraverser traverser(F1,adjacency,F1[0].getUniquePoint(),*F1[0].getEquations().begin());

    MyTarget myTarget;
    SymmetryGroup s(f1.getAmbientDimension());
    symmetricTraverse(traverser,myTarget);

    myTarget.toCone().print(&debug);


*/




    {

      if(0)
      {
          BSPTreeTraverser2 traverser(n,tree,false);//change to true
    	  SymmetricTargetFanBuilder myTarget(n,s);
    	  symmetricTraverse(traverser,myTarget,&s);
    	  myTarget.getFanRef().printWithIndices(&pout,FPF_default,&s);
      }
      else
      {
          BSPTreeTraverser2 traverser(n,tree,true);
    	  SymmetricTargetVertexSetBuilder myTarget(n,s);
    	  traverser.calibrate();
    	  symmetricTraverse(traverser,myTarget,&s);
    	  pout<<myTarget.getVertexSet();
      }
    }

    return 0;
  }
};

static TropicalHypersurfaceReconstrutionApplication theApplication;

