#include <vector>
#include <limits>

#include <stdio.h>
#include <stdlib.h>

// stuff to define the mesh
#include <vcg/simplex/vertex/base.h>
#include <vcg/simplex/face/base.h>
#include <vcg/simplex/edge/base.h>
#include <vcg/complex/complex.h>

#include <vcg/math/quadric.h>
#include <vcg/complex/algorithms/clean.h>

// io
#include <wrap/io_trimesh/import.h>
#include <wrap/io_trimesh/export_ply.h>

// update
#include <vcg/complex/algorithms/update/topology.h>
#include <vcg/complex/algorithms/update/bounding.h>
#include <vcg/complex/algorithms/smooth.h>

// local optimization
#include <vcg/complex/algorithms/local_optimization.h>
#include <vcg/complex/algorithms/local_optimization/tri_edge_collapse.h>
#include <vcg/complex/algorithms/local_optimization/tri_edge_collapse_quadric_tex.h>

using namespace vcg;
using namespace tri;

// The class prototypes.
class MyVertex;
class MyEdge;
class MyFace;

struct MyUsedTypes: public UsedTypes<Use<MyVertex>::AsVertexType, Use<MyEdge>::AsEdgeType, Use<MyFace>::AsFaceType>{};

class MyVertex  : public Vertex< MyUsedTypes,
  vertex::VFAdj,
  vertex::Coord3f,
  vertex::Normal3f,
  vertex::Mark,
  vertex::BitFlags  >{
  };

class MyEdge : public Edge< MyUsedTypes> {};

typedef BasicVertexPair<MyVertex> VertexPair;

class MyFace    : public Face< MyUsedTypes,
  face::VFAdj,
  face::VertexRef,
  face::BitFlags,
  face::WedgeTexCoord2f> {};

// the main mesh class
class MyMesh    : public vcg::tri::TriMesh<std::vector<MyVertex>, std::vector<MyFace> > {};

class MyTriEdgeCollapseQTex: public TriEdgeCollapseQuadricTex< MyMesh, VertexPair, MyTriEdgeCollapseQTex, QuadricTexHelper<MyMesh> > {
            public:
            typedef  TriEdgeCollapseQuadricTex< MyMesh,  VertexPair, MyTriEdgeCollapseQTex, QuadricTexHelper<MyMesh> > TECQ;
            inline MyTriEdgeCollapseQTex(  const VertexPair &p, int i,BaseParameterClass *pp) :TECQ(p,i,pp){}
};


void TexDecimation(MyMesh &m, bool CleaningFlag,int TargetFaceNum)
{
  tri::TriEdgeCollapseQuadricTexParameter pp;

  pp.SetDefaultParams();
  if(CleaningFlag){
        int dup = tri::Clean<MyMesh>::RemoveDuplicateVertex(m);
        int unref =  tri::Clean<MyMesh>::RemoveUnreferencedVertex(m);
        printf("Removed %i duplicate and %i unreferenced vertices from mesh \n",dup,unref);
    }

    printf("reducing it to %i\n",TargetFaceNum);
    int t1=clock();

    tri::UpdateBounding<MyMesh>::Box(m);
    math::Quadric<double> QZero;
    QZero.SetZero();
    QuadricTexHelper<MyMesh>::QuadricTemp TD3(m.vert,QZero);
    QuadricTexHelper<MyMesh>::TDp3()=&TD3;

    std::vector<std::pair<vcg::TexCoord2<float>,Quadric5<double> > > qv;

    QuadricTexHelper<MyMesh>::Quadric5Temp TD(m.vert,qv);
    QuadricTexHelper<MyMesh>::TDp()=&TD;


    vcg::LocalOptimization<MyMesh> DeciSession(m, &pp);
//    cb(1,"Initializing simplification");
    DeciSession.Init<MyTriEdgeCollapseQTex>();

    DeciSession.SetTargetSimplices(TargetFaceNum);
    DeciSession.SetTimeBudget(0.1f);
  //	int startFn=m.fn;

    int faceToDel=m.fn-TargetFaceNum;
    int t2=clock();

    while( DeciSession.DoOptimization() && m.fn>TargetFaceNum )
    {
      printf("Simplifing heap size %i ops %i\n",int(DeciSession.h.size()),DeciSession.nPerfmormedOps);
    };

    DeciSession.Finalize<MyTriEdgeCollapseQTex>();

    int t3=clock();
    printf("mesh  %d %d Error %g \n",m.vn,m.fn,DeciSession.currMetric);
    printf("\nCompleted in (%i+%i) msec\n",t2-t1,t3-t2);
}

// mesh to simplify

int main(int argc, char**argv){

  int meshNum=argc-1;

//std::vector<MyMesh> meshVec(meshNum);

MyMesh meshVec[10];
int tt0=clock();
char buf[255];
int i;

for(i=0;i<meshNum;++i)
{
  int err=vcg::tri::io::Importer<MyMesh>::Open(meshVec[i],argv[i+1]);
  if(err)
  {
    printf("Unable to open mesh %s : '%s'\n",argv[i+1], vcg::tri::io::Importer<MyMesh>::ErrorMsg(err));
    exit(-1);
  }
  printf("mesh loaded %d %d \n",meshVec[i].vn,meshVec[i].fn);

 int t1=clock();
 tri::Smooth<MyMesh>::VertexCoordLaplacian(meshVec[i],5*i);

 TexDecimation(meshVec[i],true,meshVec[i].fn/2);
 int t2=clock();
 printf("%i %5.3f sec\n",i,float(t2-t1)/CLOCKS_PER_SEC);
 sprintf(buf,"out%i.ply",i);
 tri::io::ExporterPLY<MyMesh>::Save(meshVec[i],buf,false);
}

int tt1=clock();
printf("---Total %5.3f sec\n",float(tt1-tt0)/CLOCKS_PER_SEC);

for(int i=0;i<meshNum;++i)
{
 char buf[255];
 sprintf(buf,"out%i.ply",i);
 tri::io::ExporterPLY<MyMesh>::Save(meshVec[i],buf,tri::io::Mask::IOM_WEDGTEXCOORD,false);
}

//  TriEdgeCollapseQuadricParameter qparams;
//  qparams.QualityThr  =.3;
//  float TargetError=std::numeric_limits<float>::max();
//  bool CleaningFlag =false;
//     // parse command line.
//    for(int i=4; i < argc;)
//    {
//      if(argv[i][0]=='-')
//        switch(argv[i][1])
//      {
//        case 'H' : qparams.SafeHeapUpdate=true; printf("Using Safe heap option\n"); break;
//        case 'Q' : if(argv[i][2]=='y') { qparams.QualityCheck	= true;  printf("Using Quality Checking\n");	}
//                                  else { qparams.QualityCheck	= false; printf("NOT Using Quality Checking\n");	}                break;
//        case 'N' : if(argv[i][2]=='y') { qparams.NormalCheck	= true;  printf("Using Normal Deviation Checking\n");	}
//                                  else { qparams.NormalCheck	= false; printf("NOT Using Normal Deviation Checking\n");	}        break;
//        case 'O' : if(argv[i][2]=='y') { qparams.OptimalPlacement	= true;  printf("Using OptimalPlacement\n");	}
//                                  else { qparams.OptimalPlacement	= false; printf("NOT Using OptimalPlacement\n");	}        break;
//        case 'S' : if(argv[i][2]=='y') { qparams.ScaleIndependent	= true;  printf("Using ScaleIndependent\n");	}
//                                  else { qparams.ScaleIndependent	= false; printf("NOT Using ScaleIndependent\n");	}        break;
//        case 'B' : if(argv[i][2]=='y') { qparams.PreserveBoundary	= true;  printf("Preserving Boundary\n");	}
//                                  else { qparams.PreserveBoundary	= false; printf("NOT Preserving Boundary\n");	}        break;
//        case 'T' : if(argv[i][2]=='y') { qparams.PreserveTopology	= true;  printf("Preserving Topology\n");	}
//                                  else { qparams.PreserveTopology	= false; printf("NOT Preserving Topology\n");	}        break;
//        case 'q' :	qparams.QualityThr	= atof(argv[i]+2);	           printf("Setting Quality Thr to %f\n",atof(argv[i]+2)); 	 break;
//        case 'n' :	qparams.NormalThrRad = math::ToRad(atof(argv[i]+2));  printf("Setting Normal Thr to %f deg\n",atof(argv[i]+2)); break;
//        case 'b' :	qparams.BoundaryWeight  = atof(argv[i]+2);			printf("Setting Boundary Weight to %f\n",atof(argv[i]+2)); break;
//        case 'e' :	TargetError = float(atof(argv[i]+2));			printf("Setting TargetError to %g\n",atof(argv[i]+2)); break;
//        case 'P' :	CleaningFlag=true;  printf("Cleaning mesh before simplification\n"); break;

//        default  :  printf("Unknown option '%s'\n", argv[i]);
//          exit(0);
//      }
//      i++;
//    }



//  if(CleaningFlag){
//      int dup = tri::Clean<MyMesh>::RemoveDuplicateVertex(mesh);
//      int unref =  tri::Clean<MyMesh>::RemoveUnreferencedVertex(mesh);
//      printf("Removed %i duplicate and %i unreferenced vertices from mesh \n",dup,unref);
//  }


//  printf("reducing it to %i\n",FinalSize);
	
//  vcg::tri::UpdateBounding<MyMesh>::Box(mesh);
  
//  // decimator initialization
//  vcg::LocalOptimization<MyMesh> DeciSession(mesh,&qparams);
	
//  int t1=clock();
//  DeciSession.Init<MyTriEdgeCollapse>();
//  int t2=clock();
//  printf("Initial Heap Size %i\n",int(DeciSession.h.size()));

//  DeciSession.SetTargetSimplices(FinalSize);
//  DeciSession.SetTimeBudget(0.5f);
//  if(TargetError< std::numeric_limits<float>::max() ) DeciSession.SetTargetMetric(TargetError);

//  while(DeciSession.DoOptimization() && mesh.fn>FinalSize && DeciSession.currMetric < TargetError)
//    printf("Current Mesh size %7i heap sz %9i err %9g \r",mesh.fn, int(DeciSession.h.size()),DeciSession.currMetric);

//  int t3=clock();
//  printf("mesh  %d %d Error %g \n",mesh.vn,mesh.fn,DeciSession.currMetric);
//  printf("\nCompleted in (%i+%i) msec\n",t2-t1,t3-t2);
	
//  vcg::tri::io::ExporterPLY<MyMesh>::Save(mesh,argv[2]);
	return 0;

}