#include <math.h>
#include <unistd.h>

#include "camera.h"
#include "vector.h"
#include "scene.h"
#include "SE/include/ply.h"

#include "Common.h"
#include "Surface.h"
#include "plydecl.h"


static char *vrmlObj0 = "Group {\n\tchildren [\n\t\tShape {\n\t\t\tgeometry IndexedFaceSet {\n\t\t\t\tcoord Coordinate {\n\t\t\t\t\tpoint [ ";
static char *vrmlObj1 = " ]\n\t\t\t\t}\n\t\t\t\tsolid FALSE\n\t\t\t\tcoordIndex [ ";
static char *vrmlObj2 = " ]\n\t\t\t\tcolor Color {\n\t\t\t\t\tcolor [ ";
static char *vrmlObj3 = " ]\n\t\t\t\t}\n\t\t\t\tcolorIndex [ ";
static char *vrmlObj4 = " ]\n\t\t\t\tcolorPerVertex TRUE\n\t\t\t}\n\t\t}\n\t]\n}\n\n";


Surface::Surface(PATCH *p)
{
  patch = p;
}


Surface::~Surface()
{
}


ConstantSurface::ConstantSurface(PATCH *p,const COLOR &col) : Surface(p)
{
  //COLORCOPY(col,c);
  c = col;
}


ConstantSurface::~ConstantSurface()
{
}


void ConstantSurface::display()
{
  RGB                  rgb;
  VECTOR               v[4];
  int                  k;

  RadianceToRGB(c,&rgb);
  RenderSetColor(&rgb);
  
  for (k=0;k<patch->nrvertices;k++)
    {
      VECTORCOPY(*(patch->vertex[k]->point),v[k]);
    }

  RenderPolygonFlat(patch->nrvertices,v);
}


void ConstantSurface::writeVRML(FILE *outFile)
{
  RGB     rgb;
  VECTOR *v;
  int     k;

  fprintf(outFile,vrmlObj0);
  for (k=0;k<patch->nrvertices;k++)
    {
      v = patch->vertex[k]->point;

      if (k!=0)
	fprintf(outFile,",");
      
      fprintf(outFile,"%f %f %f",v->x,v->z,-v->y);
    }

  fprintf(outFile,vrmlObj1);

  if (patch->nrvertices==4)
    fprintf(outFile,"0,1,2,3");
  else
    fprintf(outFile,"0,1,2");

  fprintf(outFile,vrmlObj2);

  RadianceToRGB(c,&rgb);
  RGBGAMMACORRECT(rgb,renderopts.gamma);
  fprintf(outFile,"%f %f %f",rgb.r,rgb.g,rgb.b);

  fprintf(outFile,vrmlObj3);

  if (patch->nrvertices==4)
    fprintf(outFile,"0,0,0,0");
  else
    fprintf(outFile,"0,0,0");

  fprintf(outFile,vrmlObj4);
}



int ConstantSurface::getNumBytes()
{
  // Only the color
  return sizeof(float)*3;
}



DiffuseDecimatedSurface::DiffuseDecimatedSurface(PATCH *p,int fd,PlyFile* &ply) : Surface(p)
{
  FILE              *fich;
  DecimationVertex  dv;
  DecimationFace    df;

  float             version;
  int               filetype;
  int               nelems,nentries,nprops;
  char              **elemlist,*elemname;
  PlyProperty       **ppl;
  PlyOtherElems     *other_elements = NULL;

  int               i,j,k,o = 0;
  VECTOR            vo,vu,vv;

  // Open stream
  fich = fdopen(fd,"r");

  // Read ply
  ply = ply_read(fich,&nelems,&elemlist);
  //fprintf(stderr,"Number of elements :%i\n",nelems);

  ply_get_info (ply, &version, &filetype);
  //fprintf(stderr,"File version: %f\nFile type :%i",version,filetype); 

  // get the description of all elements
  for (i = 0; i < nelems; i++) 
    {
      elemname = elemlist[i];
      ppl = ply_get_element_description (ply,elemname,&nentries,&nprops);

      // Check vertex description
      if (equal_strings(decimation_elem_names[0],elemname)) 
	{
	  // Alloc memory for vertex entries
	  nv = nentries;
	  va = new VECTOR [nv];
	  ca = new COLOR [nv];

	  // Read what's interesting only
	  for(j=0;j<nprops;j++)
	    {
	      for (k=0;k<8;k++)
		{
		  // fprintf(stderr," property :%s\n",ppl[j]->name);
		  if (equal_strings(decimation_vert_props[k].name,ppl[j]->name))
		    {
		      ply_get_property(ply,elemname,&decimation_vert_props[k]);
		      break;
		    }
		}
	    }

	  // Read vertices one by one
	  for (j=0;j<nv;j++)
	    {
	      ply_get_element(ply,(void*)&dv);
	      
	      PatchUniformPoint(patch,dv.u,dv.v,&va[j]);
	      COLORSET(ca[j],dv.r,dv.g,dv.b);
	    }
	}
      else
	{
	  if (equal_strings(decimation_elem_names[1],elemname)) 
	    {
	      // Get faces
	      nf = nentries;
	      fa = new int [nf*3];
	      ply_get_element_setup(ply,decimation_elem_names[1],1,decimation_face_props);
	      
	      // Read what's interesting only
	      for(j=0;j<nprops;j++)
		{
		  // fprintf(stderr," property :%s\n",ppl[j]->name);
		  if (equal_strings(decimation_face_props[0].name,ppl[j]->name))
		    ply_get_property(ply,elemname,&decimation_face_props[0]);
		}
	
	      k=0;
	      for (j=0;j<nf;j++)
		{
		  ply_get_element(ply,(void*)&df);
		  
		  // determine orientation
		  VECTORSUBTRACT(va[df.verts[1]],va[df.verts[0]],vu);
		  VECTORSUBTRACT(va[df.verts[2]],va[df.verts[0]],vv);
		  VECTORCROSSPRODUCT(vu,vv,vo);
		  o = VECTORDOTPRODUCT(vo,patch->normal) > 0.0f ? 1 : 0;
      
		  if (o)
		    {
		      fa[k] = df.verts[0]; k++;
		      fa[k] = df.verts[1]; k++;
		      fa[k] = df.verts[2]; k++;
		    }
		  else
		    {
		      fa[k] = df.verts[2]; k++;
		      fa[k] = df.verts[1]; k++;
		      fa[k] = df.verts[0]; k++;
		    }
		
		  // Have we to do this ? (not specified in the ply doc ??)
		  free(df.verts);
		}
	    }
	  else
	    other_elements = ply_get_other_element (ply,elemname,nelems);
	}
    }	      
}



DiffuseDecimatedSurface::~DiffuseDecimatedSurface()
{
  delete va;
  delete ca;
  delete fa;
}



void DiffuseDecimatedSurface::display()
{
  VECTOR               v[3];
  RGB                  c[3];
  int                  i,k,l;

  for (k=0,i=0;i<nf;i++)
    {
      l = fa[k]; RadianceToRGB(ca[l],&c[0]); VECTORCOPY(va[l],v[0]); k++;
      l = fa[k]; RadianceToRGB(ca[l],&c[1]); VECTORCOPY(va[l],v[1]); k++;
      l = fa[k]; RadianceToRGB(ca[l],&c[2]); VECTORCOPY(va[l],v[2]); k++;
      RenderPolygonGouraud(3,v,c);
    }
}


void DiffuseDecimatedSurface::writeVRML(FILE *outFile)
{
  RGB     rgb;
  int     k,i;

  fprintf(outFile,vrmlObj0);

  for (k=0;k<nv;k++)
    {
      if (k!=0)
	fprintf(outFile,",");
      
      fprintf(outFile,"%f %f %f",va[k].x,va[k].z,-va[k].y);
    }

  fprintf(outFile,vrmlObj1);

  k=0;
  for (i=0;i<nf;i++)
    {
      if (k!=0)
	fprintf(outFile,",-1,");

      fprintf(outFile,"%i,%i,%i",fa[k],fa[k+1],fa[k+2]);
      
      k += 3;
    }

  fprintf(outFile,vrmlObj2);

  for (k=0;k<nv;k++)
    {
      if (k!=0)
	fprintf(outFile,",");
      
      RadianceToRGB(ca[k],&rgb);
      RGBGAMMACORRECT(rgb,renderopts.gamma);
      fprintf(outFile,"%f %f %f",rgb.r,rgb.g,rgb.b);      
    }

  fprintf(outFile,vrmlObj3);

  k=0;
  for (i=0;i<nf;i++)
    {
      if (k!=0)
	fprintf(outFile,",-1,");

      fprintf(outFile,"%i,%i,%i",fa[k],fa[k+1],fa[k+2]);
      
      k += 3;
    }

  fprintf(outFile,vrmlObj4);
}



int DiffuseDecimatedSurface::getNumBytes()
{  
  return (sizeof(int)*2) +                     // Array lengths
         (nv*sizeof(float)*5) +                // One vertice == param coords and color
         (nf*sizeof(int)*3);                   // One face    == 3 vertices indices
}



DiffuseCompressedSurface::DiffuseCompressedSurface(PATCH *p,int recursionpos,CoefficientStore *coefstore): Surface(p)
{
  rpos = recursionpos;
  cs = coefstore;
}



DiffuseCompressedSurface::~DiffuseCompressedSurface()
{
  if (cs!=NULL)
    delete cs;
}



void DiffuseCompressedSurface::display()
{
  deState.spp->basicConfigure(patch,rpos);
  deState.spp->display(cs);
}

void DiffuseCompressedSurface::writeVRML(FILE *)
{
}

int DiffuseCompressedSurface::getNumBytes()
{
  return cs->getNumBytes()+sizeof(int);
}


NonDiffuseSurface::NonDiffuseSurface(PATCH *p,int recursiondir) : Surface (p)
{
  int i,n;

  // Save recursion level
  rdir = recursiondir;

  // Alloc table of SurfDec 
  n = HemisphereScratchPad::numberOfPoints(rdir);
  sap = new Surface* [n];

  // Initialise
  for(i=0;i<n;i++)
    sap[i] = NULL;
}



NonDiffuseSurface::~NonDiffuseSurface()
{
  int i,n;

  if (sap != NULL)
    {
      // Find the number of components
      n = HemisphereScratchPad::numberOfPoints(rdir);

      // Free component
      for(i=0;i<n;i++)
	if (sap[i] != NULL)
	  delete sap[i];
      
      // Delete the array itself
      delete sap;
    }
}



void NonDiffuseSurface::display()
{
  VECTOR d;
  double phi,theta;

  VECTORSUBTRACT(origin,Camera.Z,d);
  vectorToSpherical(&d,&(((DENSITY_ESTIMATION_DATA*)patch->radiance_data)->coordsys),&phi,&theta);

  sap[HemisphereScratchPad::indexDirection(rdir,phi,theta)]->display();
}

void NonDiffuseSurface::writeVRML(FILE *)
{
}

int NonDiffuseSurface::getNumBytes()
{
  int i,n,c;
  
  c=0;
  n = HemisphereScratchPad::numberOfPoints(rdir);
  for (i=0;i<n;i++)
    c += sap[i]->getNumBytes();
  
  return c+sizeof(int);
}


void NonDiffuseSurface::setDiffuseSurface(const int idx,Surface *sp)
{
  sap[idx] = sp;
}

