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

#ifndef __APPLE__
#  include <GL/glut.h>
#else
#  include <GLUT/glut.h>
#endif
#include <AR/gsub.h>
#include <AR/param.h>
#include <AR/ar.h>
#include <AR/video.h>
#include "glm.h"
#include "object.h"
#include "bitmap.h"

#define COLLIDE_DIST 10000.0

/* Object Data */
char            *model_name = "Data/object_data2";
ObjectData_T    *object;
int             objectnum;

int             xsize, ysize;
int				thresh = 100;
int             count = 0;

/*Globals for bitmap texturing*/
BITMAPINFO *TexInfo; /* Texture bitmap information */
GLubyte    *TexBits; /* Texture bitmap pixel bits */


/* set up the video format globals */
#ifdef _WIN32
char			*vconf = "Data\\WDM_camera_flipV.xml";
#else
char			*vconf = "";
#endif

char           *cparam_name = "Data/camera_para.dat";
ARParam         cparam;

GLMmodel* mModels[4];
GLboolean lampLight = GL_FALSE;
GLboolean textureC = GL_FALSE;

GLint textureID0 = 0;
GLint textureID1 = 1;
GLint textureIDs[2];

static void   init(void);
static void   cleanup(void);
static void   keyEvent( unsigned char key, int x, int y);
static void   mainLoop(void);
static int checkCollisions( ObjectData_T object0, ObjectData_T object1, float collide_dist);
static int draw( ObjectData_T *object, int objectnum );
static int  draw_object( int obj_id, double gl_para[16], int collide_flag );
void text(GLuint x, GLuint y, char *format, ...);
static void drawQuad(void);

int main(int argc, char **argv)
{
	//initialize applications
	glutInit(&argc, argv);
    init();
	
	arVideoCapStart();
	
	// Load in the texture map
	glGenTextures(1, &textureID0);
    TexBits = LoadDIBitmap("Data/myModels/s.bmp", &TexInfo);
	glBindTexture (GL_TEXTURE_2D, textureID0);// apply textureID
	//different texture parameters
	/*glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexImage2D(GL_TEXTURE_2D, 0, 3, TexInfo->bmiHeader.biWidth, TexInfo->bmiHeader.biHeight, 0, GL_BGR_EXT, GL_UNSIGNED_BYTE, TexBits);
	*/glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);//linear texture
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR);
	gluBuild2DMipmaps(GL_TEXTURE_2D, 3, TexInfo->bmiHeader.biWidth, TexInfo->bmiHeader.biHeight, GL_BGR_EXT, GL_UNSIGNED_BYTE, TexBits); 
	free(TexInfo);
	free(TexBits);
	
	glGenTextures(1, &textureID1);
    TexBits = LoadDIBitmap("Data/myModels/1.bmp", &TexInfo);
	glBindTexture (GL_TEXTURE_2D, textureID1);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR);
	gluBuild2DMipmaps(GL_TEXTURE_2D, 3, TexInfo->bmiHeader.biWidth, TexInfo->bmiHeader.biHeight, GL_BGR_EXT, GL_UNSIGNED_BYTE, TexBits);
	free(TexInfo);
	free(TexBits);

	//start the main event loop
	int i;
	for(i=0;i<objectnum;i++){
		object[i].collide = 0;
		}
    argMainLoop( NULL, keyEvent, mainLoop );

	return 0;
}

static void   keyEvent( unsigned char key, int x, int y)   
{
	switch(key){
		case 0x1b://ESC
			cleanup();
			exit(0);
			break;
		case 'a':
			lampLight = !lampLight;//turn on/off light
			break;
		case 'm':
			textureC = !textureC;//switch between texture0 and 1
			break;
    }

}

/* main loop */
static void mainLoop(void)
{
    ARUint8         *dataPtr;
    ARMarkerInfo    *marker_info;
    int             marker_num;
    int             i,j,k;

    /* grab a video frame */
    if( (dataPtr = (ARUint8 *)arVideoGetImage()) == NULL ) {
        arUtilSleep(2);
        return;
    }
	
    if( count == 0 ) arUtilTimerReset();  
    count++;

	//draw video
    argDrawMode2D();
    argDispImage( dataPtr, 0,0 );

	//glColor3f( 1.0, 0.0, 0.0 );
	glLineWidth(6.0);

	/* detect the markers in the video frame */ 
	if(arDetectMarker(dataPtr, thresh, 
		&marker_info, &marker_num) < 0 ) {
		cleanup(); 
		exit(0);
	}

	/* check for known patterns */
    for( i = 0; i < objectnum; i++ ) {
		k = -1;
		for( j = 0; j < marker_num; j++ ) {
	        if( object[i].id == marker_info[j].id) {

				if( k == -1 ) k = j;
		        else /* make sure you have the best pattern (highest confidence factor) */
					if( marker_info[k].cf < marker_info[j].cf ) k = j;
			}
		}
		if( k == -1 ) {
			object[i].visible = 0;
			continue;
		}
		
		/* calculate the transform for each marker */
		if( object[i].visible == 0 ) {
            arGetTransMat(&marker_info[k],
                          object[i].marker_center, object[i].marker_width,
                          object[i].trans);
        }
        else {
            arGetTransMatCont(&marker_info[k], object[i].trans,
                          object[i].marker_center, object[i].marker_width,
                          object[i].trans);
        }
        object[i].visible = 1;
	}
	
	/*check for collision*/
	int co;
	for(co = 1; co<objectnum; co++){ 
		if(object[0].visible && object[co].visible){
			if(checkCollisions(object[0],object[co],COLLIDE_DIST)){
				object[0].collide = 1;
				object[co].collide = 1;
			}
			else{
				object[0].collide = 0;
				object[co].collide = 0;
			}
		}
	}
	
	arVideoCapNext();

	/* draw the AR graphics */
    draw( object, objectnum );

	/*swap the graphics buffers*/
	argSwapBuffers();
}

static void init( void )
{
	ARParam  wparam;

    /* open the video path */
    if( arVideoOpen( vconf ) < 0 ) exit(0);
    /* find the size of the window */
    if( arVideoInqSize(&xsize, &ysize) < 0 ) exit(0);
    printf("Image size (x,y) = (%d,%d)\n", xsize, ysize);

    /* set the initial camera parameters */
    if( arParamLoad(cparam_name, 1, &wparam) < 0 ) {
        printf("Camera parameter load error !!\n");
        exit(0);
    }
    arParamChangeSize( &wparam, xsize, ysize, &cparam );
    arInitCparam( &cparam );
    printf("*** Camera Parameter ***\n");
    arParamDisp( &cparam );

	/* load in the object data - trained markers and associated bitmap files */
    if( (object=read_ObjData(model_name, &objectnum)) == NULL ) exit(0);
    printf("Objectfile num = %d\n", objectnum);
	
	/*load model*/
	mModels[0] = glmReadOBJ("/myModels/desk1.obj");
	
	mModels[1] = glmReadOBJ("Data/myModels/castle.obj");  //castle has no texcoord no normals
	glmFacetNormals(mModels[1]);//apply facet normals
	glmVertexNormals(mModels[1], 90);//apply vertex normals, vertical to facet
   
	mModels[2] = glmReadOBJ("Data/myModels/bed.obj");
	
	mModels[3] = glmReadOBJ("Data/myModels/lamp.obj"); //lamp no texcoord

    /* open the graphics window */
    argInit( &cparam, 2.0, 0, 0, 0, 0 );
}

/* cleanup function called when program exits */
static void cleanup(void)
{
	arVideoCapStop();
    arVideoClose();
    argCleanup();
}

/* draw the the AR objects */
static int draw( ObjectData_T *object, int objectnum )
{
    int     i;
    double  gl_para[16];
       
	glClearDepth( 1.0 );
    glClear(GL_DEPTH_BUFFER_BIT);
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LEQUAL);
    glEnable(GL_LIGHTING);
	glEnable(GL_BLEND);

    /* calculate the viewing parameters - gl_para */
    for( i = 0; i < objectnum; i++ ) {
        if( object[i].visible == 0 ) continue;
        argConvGlpara(object[i].trans, gl_para);
        draw_object( object[i].id, gl_para, object[i].collide);
    }
     
	glDisable( GL_LIGHTING );
    glDisable( GL_DEPTH_TEST );
	
    return(0);
}

/* draw user object */
static int  draw_object( int obj_id, double gl_para[16], int collide_flag)
{
    GLfloat   mat_flash_shiny[] = {50.0};
    GLfloat   light_position[]  = {100.0,100.0,500.0,1.0};
    GLfloat   ambi[]            = {0.3, 0.3, 0.3, 1};
    GLfloat   lightZeroColor[]  = {0.8, 0.8, 0.8, 1.0};
	
	GLfloat   lamp_ambi[] = {0.1, 0.1, 0.1, 0.1};
	GLfloat   lamp_diffuse[] = {0.8, 0.8, 0.8, 1.0};
	GLfloat   lamp_position[] = {50, 100, 0, 1.0};
	GLfloat   lamp_emission[] = { 1.0, 1.0, 0.0, 0.3 };
	

	//float l[] = { 20.0,  80.0, 0.0 }; // light source
	float n[] = { 0.0,  -1.0, 0.0 }; // Normal vector for the plane
	float e[] = { 0.0, 0.0, 0.0 }; // Point of the plane
 
    argDrawMode3D();
    argDraw3dCamera( 0, 0 );
    glMatrixMode(GL_MODELVIEW);
    glLoadMatrixd( gl_para );

 	/* set the material */
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);//light0 is a greenish directional light.
    glLightfv(GL_LIGHT0, GL_POSITION, light_position);
    glLightfv(GL_LIGHT0, GL_AMBIENT, ambi);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, lightZeroColor);
	
	glLightfv(GL_LIGHT1, GL_POSITION, lamp_position);
	glLightfv(GL_LIGHT1, GL_AMBIENT, lamp_ambi);
	glLightfv(GL_LIGHT1, GL_DIFFUSE, lamp_diffuse);

    glMaterialfv(GL_FRONT, GL_SHININESS, mat_flash_shiny);	
	
	GLfloat cubeXform[4][4];
	
	glRotatef(90, 1, 0, 0);

	/*draw 4 models*/
	switch(obj_id){
		case 0:
			glPushMatrix();
			//glTranslatef(0,20,0);
			glmDraw(mModels[0], GLM_SMOOTH | GLM_MATERIAL);
			glPopMatrix();
			
			/*shadow*/
			glPushMatrix();
			castShadow(lamp_position,e,n);  
			glDisable(GL_LIGHTING);
			glColor3f(0.4,0.4,0.4);
			glmDraw(mModels[0], GLM_FLAT);
			glPopMatrix();
			break;
		case 1:
			/*floor*/
			glPushMatrix();
			glColor3f(0.8,0.8,0.8);
			glBegin(GL_QUADS);
			glNormal3f(0.0,1.0,0.0);
			glScalef(30,30,30);
			glVertex3f(-100.0,e[1]-0.1, 100.0);
			glVertex3f( 100.0,e[1]-0.1, 100.0);
			glVertex3f( 100.0,e[1]-0.1,-100.0);
			glVertex3f(-100.0,e[1]-0.1,-100.0);  
			glEnd();
			glPopMatrix();
	
			glPushMatrix();
			glEnable(GL_LIGHTING);
			glTranslatef(0,10,0);
			glmDraw(mModels[1], GLM_SMOOTH | GLM_MATERIAL | GLM_TEXTURE);//apply texture on mModels[1], the castle
			glPopMatrix();

			/*shadow*/
			glPushMatrix();
			castShadow(lamp_position,e,n);  
			glDisable(GL_LIGHTING);
			glColor3f(0.4,0.4,0.4);
			glmDraw(mModels[1], GLM_FLAT);
			glPopMatrix();
			break;
		case 2:
			glPushMatrix();
			//glTranslatef( 0.0, 0.0, 60.0 );
			if(collide_flag){
				text(320, 240, "info for sample1. this is a bed");
			}
			//glBlendFunc(GL_SRC_COLOR, GL_DST_COLOR);//GL_DST_COLOR,GL_SRC_COLOR ghost effect more ghost if reverse
			if(textureC){
				glBindTexture(GL_TEXTURE_2D, textureID1);
				}
			if(!textureC){
				glBindTexture(GL_TEXTURE_2D, textureID0);
				}
			glEnable(GL_TEXTURE_2D);
			glmDraw(mModels[2], GLM_SMOOTH | GLM_MATERIAL | GLM_TEXTURE);
			glDisable(GL_TEXTURE_2D);
			glDisable(GL_BLEND);
			glPopMatrix();
			
			/*shadow*/
			glPushMatrix();
			castShadow(lamp_position,e,n);  
			glDisable(GL_LIGHTING);
			glColor3f(0.4,0.4,0.4);
			glmDraw(mModels[2], GLM_FLAT);
			glPopMatrix();
			break;
		case 3:
			glPushMatrix();
			glTranslatef( 0.0, 0.0, 0.0 );
			glPushMatrix();
			glScalef(0.5, 0.5, 0.5);
			/*if collide, display object info*/
			if(collide_flag){
				text(320, 240, "info for sample2. this is a lamp");
			}
			glmDraw(mModels[3], GLM_SMOOTH | GLM_MATERIAL);
			glPopMatrix();
			glTranslatef(0,50,0);
			/*turn on/off light*/
			if(lampLight){
				glMaterialfv(GL_FRONT, GL_EMISSION, lamp_emission);
				glEnable(GL_LIGHT1);
			}
			if(!lampLight){glDisable(GL_LIGHT1);}
			glutSolidSphere(10,16,16);//bulb
			GLfloat planet_emission[] = { 0.0, 0.0, 0.0, 1.0 };
		    glMaterialfv( GL_FRONT, GL_EMISSION, planet_emission );
			glPopMatrix();
			break;
		}
    argDrawMode2D();

    return 0;
}

/*gl bitmap text*/
void text(GLuint x, GLuint y, char* format, ...)
{
  va_list args;
  char buffer[255], *p;

  vsprintf(buffer, format, args);

  glMatrixMode(GL_PROJECTION);
  glPushMatrix();
  glLoadIdentity();
  gluOrtho2D(0, glutGet(GLUT_WINDOW_WIDTH), 0, glutGet(GLUT_WINDOW_HEIGHT));

  glMatrixMode(GL_MODELVIEW);
  glPushMatrix();
  glLoadIdentity();

  glPushAttrib(GL_ENABLE_BIT);
  glDisable(GL_LIGHTING);
  //glDisable(GL_TEXTURE_2D);
  //glDisable(GL_DEPTH_TEST);
  glTranslatef(x, y, 0.0);

  glRasterPos2f(0, 0);
  for(p = buffer; *p; p++){
    //glutStrokeCharacter(GLUT_STROKE_MONO_ROMAN, *p);
	glutBitmapCharacter(GLUT_BITMAP_9_BY_15, *p);
	/*"GLUT_BITMAP_8_BY_13",
      "GLUT_BITMAP_TIMES_ROMAN_10",
      "GLUT_BITMAP_TIMES_ROMAN_24",
      "GLUT_BITMAP_HELVETICA_10",
      "GLUT_BITMAP_HELVETICA_12",
      "GLUT_BITMAP_HELVETICA_18"*/
	}
  
  glPopAttrib();

  glPopMatrix();
  glMatrixMode(GL_PROJECTION);
  glPopMatrix();
  glMatrixMode(GL_MODELVIEW);
}

/* check collision between two markers */
static int checkCollisions( ObjectData_T object0, ObjectData_T object1, float collide_dist)
{
	float x1,y1,z1;
	float x2,y2,z2;
	float dist;

	x1 = object0.trans[0][3];
	y1 = object0.trans[1][3];
	z1 = object0.trans[2][3];

	x2 = object1.trans[0][3];
	y2 = object1.trans[1][3];
	z2 = object1.trans[2][3];

	dist = (x1-x2)*(x1-x2)+(y1-y2)*(y1-y2)+(z1-z2)*(z1-z2);

	printf("Dist = %3.2f\n",dist);

	if(dist < collide_dist)
		return 1;
	else 
		return 0;
}

/*Calculate shadowl is light direction
e is a point on within the plane on which the shadow is to be projected.  
n is the normal vector of the plane.*/
void castShadow(float * l, float * e, float * n)
{
  float d, c;
  float mat[16];

  // These are c and d (corresponding to the tutorial)  
  d = n[0]*l[0] + n[1]*l[1] + n[2]*l[2];
  c = e[0]*n[0] + e[1]*n[1] + e[2]*n[2] - d;

  // Create the matrix. OpenGL uses column by column
  mat[0]  = l[0]*n[0]+c; 
  mat[4]  = n[1]*l[0]; 
  mat[8]  = n[2]*l[0]; 
  mat[12] = -l[0]*c-l[0]*d;
  
  mat[1]  = n[0]*l[1];        
  mat[5]  = l[1]*n[1]+c;
  mat[9]  = n[2]*l[1]; 
  mat[13] = -l[1]*c-l[1]*d;
  
  mat[2]  = n[0]*l[2];        
  mat[6]  = n[1]*l[2]; 
  mat[10] = l[2]*n[2]+c; 
  mat[14] = -l[2]*c-l[2]*d;
  
  mat[3]  = n[0];        
  mat[7]  = n[1]; 
  mat[11] = n[2]; 
  mat[15] = -d;

  /*multiply the matrices */
  glMultMatrixf(mat);
}
