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

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>

#include "defs.h"
#include "array.h"
#include "main.h"
#include "fill.h"
#include "texture.h"
#include "mesh.h"
#include "warp.h"
#include "itemBuffer.h"
#include "billboard.h"
#include "remesh.h"
#include "ccint.h"

Imposter mountain(HLEVELS, VLEVELS);

int *images[VLEVELS*HLEVELS];
float *image = (float *) malloc (WindowSize*WindowSize*3*sizeof(float));
float *dep = (float *) malloc (WindowSize*WindowSize*sizeof(float));

GLuint *texName[VLEVELS*HLEVELS];

float S_CENTER[3], S_RAD;


float sampleRadius (int x0, int x1)
{
    return 1.5 * (x1 - x0);
}


void outputImage (char *name, float *image, int ex)
{
    FILE *rgb = fopen (name,"w");
    
    fputs ("P6\n", rgb);
    fputs ("256 256\n",rgb);
    fputs ("255\n",rgb);

    float *p = image;
    
    for (int i = 0; i<WindowSize; i++)
    {
	for (int j = 0; j<WindowSize; j++)
	{
	    for (int k = 0; k < 3; k++)
	    {
		putc ( (unsigned char) floor (255*(*p++)), rgb);
		
	    }
	}
    }
    fclose (rgb);    

    if (ex)
	exit(0);
}

/********************************************************************
 *
 * FUNCTION: maxHeight
 * DOES: returns the maximum height of the terrain in the quad 
 *       bounded by the input
 *
 ********************************************************************/

float maxHeight( int x0, int y0, int x1, int y1 )

{
  float x, max;

  max = -99999;

  for (int i=x0; i<=MIN(x1,smooth_heights->xdim-1); i++)
    for (int j=y0; j<=MIN(y1,smooth_heights->ydim-1); j++) {
      x = smooth_heights->data[i][j];
      if (x > max)
        max = x;
    }

  return (max+0.5);
}



float minHeight( int x0, int y0, int x1, int y1 )
{
  float x, min;

  min = 99999;

  for (int i=x0; i<=MIN(x1,smooth_heights->xdim-1); i++)
    for (int j=y0; j<=MIN(y1,smooth_heights->ydim-1); j++) {
      x = smooth_heights->data[i][j];
      if (x < min)
        min = x;
    }

  return min;
}


/*
int idInGroup (unsigned short int group, int index, int id)
{
    // find the group number that has the correct id
    int i;
    int gnum = -1;;
    for (int i = 0; i < billboards[index].numBBs; i++)
    {
	if (billboards[index].bboards[i].id == id)
	{
	    gnum = i;
	    break;
	}
    }

    if (gnum == -1) printf ("NOOOOOOO!!!!!!!!\n");

    if (gnum == 0)
    {
	if (((group & 0200) / 128 == 1) || group < 256)
	    return 1;
    }
    else if (gnum == 1)
    {
	if (((group & 0100) / 64 == 1) || group < 128)
	    return 1;
    }
    else if (gnum == 2)
    {
	if (((group & 040) / 32 == 1) || group < 64)
	    return 1;
    }
    else if (gnum == 3)
    {
	if (((group & 020) / 16 == 1) || group < 32)
	    return 1;
    }
    else if (gnum == 4)
    {
	if (((group & 010) / 8 == 1) || group < 16)
	    return 1;
    }

    return 0;
}
*/

int idInGroup (unsigned short int group, int index, int id)
{
    // find the group number that has the correct id
    int i;
    int gnum = -1;;
    for (int i = 0; i < billboards[index].numBBs; i++)
    {
	if (billboards[index].bboards[i].id == id)
	{
	    gnum = i;
	    break;
	}
    }

    if (gnum == -1) printf ("NOOOOOOO!!!!!!!!\n");

    if (gnum == 0)
    {
	if ((group & 0200) / 128 == 1)
	    return 1;
    }
    else if (gnum == 1)
    {
	if ((group & 0100) / 64 == 1) 
	    return 1;
    }
    else if (gnum == 2)
    {
	if ((group & 040) / 32 == 1) 
	    return 1;
    }
    else if (gnum == 3)
    {
	if ((group & 020) / 16 == 1) 
	    return 1;
    }
    else if (gnum == 4)
    {
	if ((group & 010) / 8 == 1) 
	    return 1;
    }

    return 0;
}

void drawBBterrain (int index, int id, int x0, int y0, int x1, int y1)
{
    // generate a list of all terrain triangles belonging to billboards
    // closer than the current one (so they can be omitted in drawing)

    int i,j;

    int length = x1 - x0;
    int width = y1 - y0;
    
    glEnable (GL_CULL_FACE);
    glEnable (GL_DEPTH_TEST);
    glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    int ident = 0;

    for (i = x0; i < x1; i++)
    {
	for (j = y0; j < y1; j++)
	{
	    //if (idFromGroup(triLL[i][j].groups, index) == id)
	    if (idInGroup (triLL[i][j].groups, index, id))
	    {
		glBegin (GL_POLYGON);

		glColor3ub( RedIntensities->data[i][j], GreenIntensities->data[i][j], BlueIntensities->data[i][j] );
		glVertex3f ((float) i, (float) j, smooth_heights->data[i][j]);

		glColor3ub( RedIntensities->data[i+1][j], GreenIntensities->data[i+1][j], BlueIntensities->data[i+1][j] );
		glVertex3f ((float) i+1, (float) j, smooth_heights->data[i+1][j]);

		glColor3ub( RedIntensities->data[i][j+1], GreenIntensities->data[i][j+1], BlueIntensities->data[i][j+1] );
		glVertex3f ((float) i, (float) j+1, smooth_heights->data[i][j+1]);		
				
		glEnd();
	    } 

	    ident++;
	    //if (idFromGroup(triUR[i][j].groups, index) == id)
	    if (idInGroup (triUR[i][j].groups, index, id))
	    {
		glBegin (GL_POLYGON);  

		glColor3ub( RedIntensities->data[i+1][j], GreenIntensities->data[i+1][j], BlueIntensities->data[i+1][j] );
		glVertex3f ((float) i+1, (float) j, smooth_heights->data[i+1][j]);

		glColor3ub( RedIntensities->data[i+1][j+1], GreenIntensities->data[i+1][j+1], BlueIntensities->data[i+1][j+1] );
		glVertex3f ((float) i+1, (float) j+1, smooth_heights->data[i+1][j+1]);

		glColor3ub( RedIntensities->data[i][j+1], GreenIntensities->data[i][j+1], BlueIntensities->data[i][j+1] );
		glVertex3f ((float) i, (float) j+1, smooth_heights->data[i][j+1]);

		glEnd();
	    }
	    
	    ident++;
	}
  
    }

    glDisable (GL_CULL_FACE);
    glDisable (GL_STENCIL_TEST);
    //free (omit);
    glutSwapBuffers();
    
}


void drawMeshPart (int index, int bb)
{
    Vertex v0, v1, v2;
    
    float worldcd0[3];
    float worldcd1[3];
    float worldcd2[3];

    int i;

    glDrawBuffer (GL_FRONT);
    
    for (i = 0; i < billboards[index].bboards[bb].numfaces; i++)
    {

	int vindex0 = billboards[index].bboards[bb].faces[i].vertices[0];
	int vindex1 = billboards[index].bboards[bb].faces[i].vertices[1];
	int vindex2 = billboards[index].bboards[bb].faces[i].vertices[2];
	
	v0 = billboards[index].bboards[bb].verts[vindex0];
	v1 = billboards[index].bboards[bb].verts[vindex1];
	v2 = billboards[index].bboards[bb].verts[vindex2];
	
	worldcd0[0] = v0.world[0];
	worldcd0[1] = v0.world[1];
	worldcd0[2] = v0.world[2];
	
	worldcd1[0] = v1.world[0];
	worldcd1[1] = v1.world[1];
	worldcd1[2] = v1.world[2];
	
	worldcd2[0] = v2.world[0];
	worldcd2[1] = v2.world[1];
	worldcd2[2] = v2.world[2];
	
	//glDisable (GL_TEXTURE_2D);
	glDisable(GL_DEPTH_TEST);

	//if (depths[index][WindowSize*v0.y+v0.x] > .99||depths[index][WindowSize*v1.y+v1.x] > .99||depths[index][WindowSize*v2.y+v2.x] > .99)
	//glColor3f(0,.8,0);
	//else
	    glColor3f( .8, 0, 0 );
	
	glBegin (GL_LINE_LOOP);
	glVertex3fv (worldcd0);
	glVertex3fv (worldcd1);
	glVertex3fv (worldcd2);
	glEnd();

	glEnable(GL_DEPTH_TEST);
	//glEnable (GL_TEXTURE_2D);
    }

    glDrawBuffer (GL_BACK);
}

void removeBackgroundTris (int index, int *ditem, int numfaces)
{
    int *tcount = (int *) malloc (numfaces*sizeof(int));
    int *bcount = (int *) malloc (numfaces*sizeof(int));

    int i;

    for (i = 0; i < numfaces; i++)
    {
	tcount[i] = 0;
	bcount[i] = 0;
    }
    
    for (i = 0; i < WindowSize*WindowSize; i++)
    {
	if (ditem[i] != -1)
	{
	   if (depths[index][i] < 0.98)
	       tcount[ditem[i]]++;
	   else
	       bcount[ditem[i]]++;
	}
    }

    for (i = 0; i < numfaces; i++)
    {
	if ((tcount[i] + bcount[i]) != 0)
	{
	    if ((float)tcount[i]/(float)(tcount[i]+bcount[i]) < 0.8)
	    {
		// remove this triangle from the mesh
		Dfaces[index][i].remove = 1;
	    }
	}
    }
}


void expandImage (int index, float *d, float *image)
{
    int i, j, icount, dcount;
    float Rave, Gave, Bave, Dave;

    int MODDEPTH = 0;

    int *marker;
    if (MODDEPTH)
    {
	marker = (int *) malloc (WindowSize*WindowSize*sizeof(int));

	for (i = 0; i < WindowSize*WindowSize; i++)
	    marker[i] = 0;
    }
    
    for (i = 0; i < WindowSize; i++)
    {
	for (j = 0; j < WindowSize; j++)
	{
	    if (d[WindowSize*i+j] > 0.99)
	    {
		Rave = 0; Gave = 0; Bave = 0; Dave = 0;
		icount = 0; dcount = 0;
		for (int w = i-2; w< i+3; w++)
		{
		    for (int r = j-2; r<j+3; r++)
		    {		
			if (w >=0 && r >= 0 && w < WindowSize && r < WindowSize)
			{
			    if (d[WindowSize*w+r] < 0.98)
			    {
				Rave += image[3*(WindowSize*w+r)];
				Gave += image[3*(WindowSize*w+r)+1];
				Bave += image[3*(WindowSize*w+r)+2];
				icount++;
				if (MODDEPTH && marker[WindowSize*w+r] == 0)
				{
				    Dave += d[WindowSize*w+r];
				    dcount++;
				}
			    }
			}
		    }
		}
		if (icount)
		{
		    image[3*(WindowSize*i+j)] = Rave/(float)icount;
		    image[3*(WindowSize*i+j)+1] = Gave/(float)icount;
		    image[3*(WindowSize*i+j)+2] = Bave/(float)icount;
		}
		if (MODDEPTH && dcount)
		{
		    d[WindowSize*i+j] = Dave/(float)dcount;
		    marker[WindowSize*i+j] = 1;
		}
	    }
	}
    }
}

void recordFaces (int vlevel, int hlevel, int x0, int y0, int x1, int y1)

{
    int eye_index = HLEVELS*vlevel + hlevel;

    float d = sqrt ((EYE[0]-S_CENTER[0])*(EYE[0]-S_CENTER[0])+(EYE[1]-S_CENTER[1])*(EYE[1]-S_CENTER[1])+(EYE[2]-S_CENTER[2])*(EYE[2]-S_CENTER[2]));
    
    float near = d - S_RAD;
    float far = d + S_RAD;
    float angle = 2*atan (S_RAD/d);
    angle = angle * (180/pi);

    float up[3];
    up[0] = -1 * (EYE[2]-S_CENTER[2]);
    up[1] = 0;//(EYE[0]-S_CENTER[0]);
    up[2] = EYE[0] - S_CENTER[0];
    
    glMatrixMode (GL_PROJECTION);
    glLoadIdentity();
    gluPerspective (angle, 1.0, near, far);

    printf ("%d\n", eye_index);
    printf ("%f %f %f\n", EYE[0],EYE[1],EYE[2]);
    
    glMatrixMode (GL_MODELVIEW);
    glLoadIdentity();
    gluLookAt (EYE[0], EYE[1], EYE[2], S_CENTER[0], S_CENTER[1], S_CENTER[2], up[0], up[1], up[2]);
    draw_terrain();

    //storeEye(HLEVELS*vlevel+hlevel);
    storeDepths (eye_index);
    storeCamera (eye_index);
    float *im = (float *) malloc (WindowSize*WindowSize*3*sizeof(float));
    glReadPixels (0, 0, WindowSize, WindowSize, GL_RGB, GL_FLOAT, im);

    makeMesh (eye_index, im);
    
    int numOrigFaces = divideMesh (eye_index);

    int nbb = billboards[eye_index].numBBs;
    texName[eye_index] = (GLuint *) malloc ((nbb+1) * sizeof (GLuint));
    glGenTextures (nbb+1, texName[eye_index]);

    glBindTexture (GL_TEXTURE_2D, texName[eye_index][nbb]);
    glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexImage2D (GL_TEXTURE_2D, 0, GL_RGB, WindowSize, WindowSize, 0, GL_RGB, GL_FLOAT, im);
    
    int *titem = (int *) malloc ( WindowSize * WindowSize * sizeof(int) );
    int *mitem = (int *) malloc ( WindowSize * WindowSize * sizeof(int) );
    int *ditem = (int *) malloc ( WindowSize * WindowSize * sizeof(int) );
    
    itemize (eye_index, 0, titem, x0, y0, x1, y1);

    printf ("1\n");
    int c;
    if (eye_index == 24)
	scanf ("%d", &c);
       
    /*
    FILE *item = fopen ("item", "w");
    for (int y = 0; y < WindowSize*WindowSize; y++)
	fprintf (item, "%d ", titem[y]);
    */
	
    itemize (eye_index, 1, mitem, x0, y0, x1, y1);

    printf ("2\n");
    if (eye_index == 24)
	scanf ("%d", &c);
    
    itemize (eye_index, 2, ditem, x0, y0, x1, y1);

    printf ("3\n");
    initializeGroupData (eye_index, titem, mitem, nbb, x0, y0, x1, y1);

    printf ("4\n");

    int OUTPUT = 18;
    FILE *tdata;

    removeBackgroundTris (eye_index, ditem, numOrigFaces);

    printf ("5\n");
    int k;
    for (k = 0; k < nbb; k++)
    //for (int k = nbb-1; k>=0; k--)
    {
	int id = billboards[eye_index].bboards[k].id;

	printf ("drawing bb %d\n",k);
	drawBBterrain (eye_index, id, x0, y0, x1, y1);

	glReadPixels (0,0,WindowSize,WindowSize, GL_DEPTH_COMPONENT, GL_FLOAT, dep);
	glReadPixels (0, 0, WindowSize, WindowSize, GL_RGB, GL_FLOAT, image);
	
	//outputImage ("before.ppm", image, 0);
	expandImage (eye_index, dep, image);

	if (eye_index == 24)
	{
	    if (k == 0)
		outputImage ("0.ppm", image, 0);
	    else if (k == 1)
		outputImage ("1.ppm", image, 0);
	    else if (k == 2)
		outputImage ("2.ppm", image, 0);
	    else if (k == 3)
		outputImage ("3.ppm", image, 0);
	}
	
	meshSet (eye_index, id, k, dep);	

	int STOP = 35;
	int check;
	if (eye_index == STOP)
	  scanf ("%d",&check);

	
	glBindTexture (GL_TEXTURE_2D, texName[eye_index][k]);
	glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexImage2D (GL_TEXTURE_2D, 0, GL_RGB, WindowSize, WindowSize, 0, GL_RGB, GL_FLOAT, image);

	if (0&&OUTPUT == eye_index)
	{
	    if (k == 0)
		tdata = fopen ("set1/0.tex", "w");
	    else if (k == 1)
		tdata = fopen ("set1/1.tex", "w");
	    else if (k == 2)
		tdata = fopen ("set1/2.tex", "w");
	    else if (k == 3)
		tdata = fopen ("set1/3.tex", "w");
	    else if (k == 4)
		tdata = fopen ("set1/4.tex", "w");
	    else if (k == 5)
		tdata = fopen ("set1/5.tex", "w");

	    float *p = image;
	    for (int q = 0; q < 3*WindowSize*WindowSize; q++)
		putc ( (unsigned char) floor (255*(*p++)), tdata);
		//fprintf (tdata, "%d ", (unsigned char) floor (255*((*p++)/(float)MAXINT)) );
	    fclose(tdata);
	}
	
	drawMeshPart (eye_index, k);

	if (eye_index == STOP)
	    scanf ("%d",&check);

	
	//if (eye_index == 24)
	//{
	    // calculate new the tex coordinates for the disoc faces, for the
	    // image from above
	    
	    newTexCoords (eye_index, dep, k);
	    //}
    }
    
    if (eye_index == OUTPUT)
    {
	// output vdata info
	FILE *vdata = fopen ("vdata8", "w");
	fprintf (vdata, "%f %f %f\n", EYE[0], EYE[1], EYE[2]);

	for (k = 0; k < nbb; k++)
	{
	    int nfaces = billboards[eye_index].bboards[k].numfaces;
	    for (int cface = 0; cface < nfaces; cface++)
	    {
		if ((billboards[eye_index].bboards[k].faces[cface].disoc == 0 || DISOC) && (billboards[eye_index].bboards[k].faces[cface].remove == 0 || REMOVE == 0))
		{
		    int vindex0 = billboards[eye_index].bboards[k].faces[cface].vertices[0];
		    int vindex1 = billboards[eye_index].bboards[k].faces[cface].vertices[1];
		    int vindex2 = billboards[eye_index].bboards[k].faces[cface].vertices[2];
		    
		    Vertex v0 = billboards[eye_index].bboards[k].verts[vindex0];
		    Vertex v1 = billboards[eye_index].bboards[k].verts[vindex1];
		    Vertex v2 = billboards[eye_index].bboards[k].verts[vindex2];

		    int tid = billboards[eye_index].bboards[k].faces[cface].tid; 
		    float x0 = v0.world[0];
		    float y0 = v0.world[1];
		    float z0 = v0.world[2];
		    float tx0 = v0.tx;
		    float ty0 = v0.ty;
		    
		    float x1 = v1.world[0];
		    float y1 = v1.world[1];
		    float z1 = v1.world[2];
		    float tx1 = v1.tx;
		    float ty1 = v1.ty;
		    
		    float x2 = v2.world[0];
		    float y2 = v2.world[1];
		    float z2 = v2.world[2];
		    float tx2 = v2.tx;
		    float ty2 = v2.ty;

		    if (billboards[eye_index].bboards[k].faces[cface].disoc==0)
		    {
			fprintf (vdata, "%d %d %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f\n", tid, k, x0,y0,z0,tx0,ty0,x1,y1,z1,tx1,ty1,x2,y2,z2,tx2,ty2);
		    }
		    else
			fprintf (vdata, "%d %d %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %d\n", -1, k, x0,y0,z0,tx0,ty0,x1,y1,z1,tx1,ty1,x2,y2,z2,tx2,ty2,tid);
		}
	    }
	}

	fclose (vdata);
    }
	
    
    glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
    free (titem);
    free (mitem);
}


// goes through all eye positions for a quad and calls recordFaces 
// on each

void init_box_texture (int x0, int y0, int x1, int y1)
{
  float z0 = minHeight (x0, y0, x1, y1);
  float z1 = maxHeight (x0, y0, x1, y1);
  float radius;
  int vlevel = 0;
  int hlevel = 0;
  float theta = 0;
  float phi = 0;
  float centre[3] = {(x0+x1)/2.0, (y0+y1)/2.0, z1};//z0};

  S_CENTER[0] = (x0+x1)/2.0;
  S_CENTER[1] = (y0+y1)/2.0;
  S_CENTER[2] = (z0+z1)/2.0;

  S_RAD = sqrt ((S_CENTER[0]-x0)*(S_CENTER[0]-x0) + (S_CENTER[1]-y0)*(S_CENTER[1]-y0) + (S_CENTER[2]-z0)*(S_CENTER[2]-z0));

  // disable texture mapping so the terrain is drawn normally
  TEX = 0;

  radius = sampleRadius (x0, x1);
  float phiMax = acos ((z1+1-z0)/radius);
  phi_step  = pi/(2*(VLEVELS-1));
  phi = 0.0;
  while (vlevel < VLEVELS )
    {
	while (hlevel < HLEVELS)
	{
          // x = rsinpcost, y = rsinpsint, z = rcosp
	  EYE[0] = radius * sin(phi)*cos(theta) + centre[0];
	  EYE[1] = radius * sin (phi)*sin(theta) + centre[1];
	  EYE[2] = radius * cos(phi) + centre[2];

	  recordFaces (vlevel, hlevel, x0, y0, x1, y1);
	    
	  theta += theta_step;
	  hlevel++;
	  if (vlevel == 0)
	      break;
	}

      phi += phi_step;
      vlevel++;
      hlevel = 0;
      theta = 0;
    }


}


// determines which quads should have tex maps pre-stored

void init_texture ()
{
  float oldEYE[3];
  int i,j;

  //glGenTextures (HLEVELS*VLEVELS, texName);
  
  // save the initial viewing position
  oldEYE[0] = EYE[0]; oldEYE[1] = EYE[1]; oldEYE[2] = EYE[2];

  init_box_texture (0, 0, smooth_heights->xdim-1, smooth_heights->ydim-1);

  EYE[0] = oldEYE[0]; EYE[1] = oldEYE[1]; EYE[2] = oldEYE[2];
}












