#include "cloth.h"
#include "particlesys.h"
#include "odeimpeul.h"
#include "camera.h"
#include "rcpointer.h"
#include "odeexpeul.h"
#include "odeexprk4.h"
#include "odeimpeul.h"

#include "bitmap.h"

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

using namespace std;


static int g_w = 600;
static int g_h = 500;


static RcPointer<Camera> g_cam;
static RcPointer<Cloth> g_cloth;

static double g_mv[16];
static double g_pm[16];
static int    g_vp[16];

static vector< pair<int,int> > g_sel;
static int  g_picktol = 4;
static bool g_pickdisp; // pick display
static float g_kbnudge = 0.02;

static int g_n;
static double g_st, g_sh, g_fl, g_gr, g_vi;
static double g_dt;
static char g_sid;

static bool g_bmpout = false;
static bool g_anim   = true;

static void init_cloth()
{
    // n, struct, shear, flex, grav, visc
	OdeSolver2 *solver;

	switch(g_sid)
	{
	case 'e':
	case 'E':
		solver = new OdeExpEul2();
		break;
	case 'r':
	case 'R':
		solver = new OdeExpRk42();
		break;
	case 'i':
	case 'I':
		solver = new OdeImpEul2();
		break;
	default:
		cerr << "invalid solver" << endl;
		exit(-1);
	}

    //g_cloth = new Cloth(n,	500,500,200, 49.8, 0.03, new OdeImpEul2() );
	g_cloth = new Cloth(g_n,g_st,g_sh,g_fl,g_gr,g_vi,solver);
    g_cloth->fix(0,0);
    g_cloth->fix(g_n-1,0);
	g_cloth->fix(0,g_n-1);
	g_cloth->fix(g_n-1,g_n-1);
}

static void glut_display()
{
	int i,j;
	const float ambColor[4] = { 0.2, 0.4, 0.6, 1 };
	const float clothColor[4] = { 0.4, 0.8, 1.0, 1 };
	const float floorColor[4] = { 0.1, 0.1, 0, 0.6 };
	const float selFreePtColor[4] = { 0,1,1,0.9 };
	const float selFixedPtColor[4] = { 1,0,0,0.9 };
	const float freePtColor[4] = { 0,0,1,0.5 };
	const float fixedPtColor[4] = { 1,0,0,0.5 };

	
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	glPushMatrix();
    // draw stuff
    
	glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, ambColor);
	glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, clothColor);
	glPushMatrix();
    g_cloth->draw(true);
	glPopMatrix();

	if (g_bmpout && g_anim)
	{
		unsigned char *imageBuffer = new unsigned char[3*g_w*g_h];
		
		// Tell openGL to read from the front buffer when capturing
		// out paint strokes
		glReadBuffer(GL_BACK);
		
		glPixelStorei( GL_PACK_ALIGNMENT, 1 );
		glPixelStorei( GL_PACK_ROW_LENGTH, g_w );
		
		glReadPixels( 0, 0, g_w, g_h, 
			GL_RGB, GL_UNSIGNED_BYTE, 
			imageBuffer );
		
		static int frameno;
		
		char filename[128];
		sprintf(filename, "f%04d.bmp", frameno);
		printf("writing %s\n", filename);
		frameno++;
		
		writeBMP(filename, g_w,g_h, imageBuffer);
		
		delete [] imageBuffer;
	}


	glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, floorColor);
	
	glPushMatrix();
	glTranslated(0,-3,0);
	glBegin(GL_QUADS);
	glNormal3d(0,1,0);
	glVertex3d(1,0,1);
	glVertex3d(-1,0,1);
	glVertex3d(-1,0,-1);
	glVertex3d(1,0,-1);
	glEnd();
	glPopMatrix();

	if (g_pickdisp)
	{

		Vec3d e(g_cam->getPosition());
		
		glPointSize(2*g_picktol);
		glDisable(GL_DEPTH_TEST);
		glBegin(GL_POINTS);

		for (i=0; i<(int) g_sel.size(); i++)
		{
			if (g_cloth->isFixed(g_sel[i].first, g_sel[i].second))
				glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, selFixedPtColor);
			else
				glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, selFreePtColor);
			
			Vec3d v( g_cloth->getPos(g_sel[i].first, g_sel[i].second) );
			Vec3d n(norm(e-v));
			glNormal3dv(n.Ref());
			glVertex3dv(v.Ref());
		}

		glEnd();

	

		glEnable(GL_DEPTH_TEST);
		glPointSize(4);

		glBegin(GL_POINTS);
		for (j=0; j<g_cloth->getSize(); j++)
		{
			for (i=0; i<g_cloth->getSize(); i++)
			{
				if (g_cloth->isFixed(i,j))
					glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, fixedPtColor);
				else
					glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, freePtColor);
				
				Vec3d v( g_cloth->getPos(i,j) );
				Vec3d n(norm(e-v));
				glNormal3dv(n.Ref());
				glVertex3dv(v.Ref());
			}
		}

		glEnd();
	}
	


	glPopMatrix();


    glutSwapBuffers();
}

static bool pick(int &ii, int &jj, const int x, const int y, int tol);

static void glut_mousefunc(int button, int state, int x, int y)
{
    if (state == GLUT_DOWN)
    {
		int ii,jj;

		if (glutGetModifiers() == GLUT_ACTIVE_SHIFT) 
		{
			if (button == GLUT_LEFT_BUTTON && pick(ii,jj,x,y,g_picktol))
			{
				g_sel.push_back( pair<int,int>(ii,jj) );
			}
			else
			{
				g_sel.clear();
			}

		}
		else
		{
			switch (button)
			{
			case GLUT_LEFT_BUTTON:
				g_cam->clickMouse(kActionRotate, x, g_h-y); break;
			case GLUT_MIDDLE_BUTTON:
				g_cam->clickMouse(kActionTranslate, x, g_h-y); break;
			case GLUT_RIGHT_BUTTON:
				g_cam->clickMouse(kActionZoom, x, g_h-y); break;
			}
        }
    }
    else
    {
	
		switch (button)
		{
		case GLUT_LEFT_BUTTON:
			g_cam->releaseMouse(x, g_h-y); break;
		case GLUT_MIDDLE_BUTTON:
			g_cam->releaseMouse(x, g_h-y); break;
		case GLUT_RIGHT_BUTTON:
			g_cam->releaseMouse(x, g_h-y); break;
		}
    }
}

static void glut_keyboardfunc(unsigned char key, int x, int y)
{
	int k;
	key = tolower(key);
	switch (key)
	{
	case 'f':
		{
			cout << "here!" << endl;
			for (k=0; k<(int)g_sel.size(); k++)
			{
				int i,j;
				i = g_sel[k].first;
				j = g_sel[k].second;
				if (g_cloth->isFixed(i,j))
				{
					cout << "unfix " << i << ' ' << j << endl;
					g_cloth->unfix(i,j);
				}
				else
				{
					g_cloth->fix(i,j);
				}
			}
			break;
		}
	case ' ':
		{
			g_pickdisp = !g_pickdisp;
			break;
		}
	case 'w':
	case 's':
	case 'a':
	case 'd':
		{
			Vec3d e = g_cam->getPosition();
			Vec3d l = g_cam->getLookAt();
			Vec3d u = g_cam->getUpVector();

			Vec3d el = norm(l-e);
			Vec3d r  = norm(cross(el,u));
			u        = norm(cross(r,el));

			double xmul(0), ymul(0);

			if (key == 'w')
				ymul = g_kbnudge;
			else if (key == 's')
				ymul = -g_kbnudge;
			else if (key == 'a')
				xmul = -g_kbnudge;
			else
				xmul = g_kbnudge;

			for (k=0; k<(int)g_sel.size(); k++)
			{
				int i,j;
				i = g_sel[k].first;
				j = g_sel[k].second;

				Vec3d pos = g_cloth->getPos(i,j);
				pos += xmul*r + ymul*u;

				g_cloth->setPos(i,j,pos);
			}
		}
		break;
	case 'r':
		init_cloth();
		break;
	case 'b':
		g_bmpout = !g_bmpout;
		break;
	case 'p':
		g_anim = !g_anim;
	}

}

static void glut_motionfunc(int x, int y)
{
    g_cam->dragMouse(x, g_h-y);
    
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective( 30.0, (double)g_w/g_h, 1.0, 100.0 );
	glGetDoublev(GL_PROJECTION_MATRIX, g_pm);	
    
    
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    g_cam->applyViewingTransform();
	glGetDoublev(GL_MODELVIEW_MATRIX, g_mv);


    glutPostRedisplay();
}


static void glut_timerfunc(int value)
{
    //static double time;
    //const double h = 0.013;
    //const double h = 0.003;
   // const double h = 0.002;
    //const double h = 0.022;
	//const double h= 0.010; IE
//	const double h = 0.008;
	//const double h = 0.00009; RK4 thresh
  //const double h=0.001;  
    // add iteration here
    
	//g_cloth->setPos(0,0,Vec3d(0.1*sin(t),0.2*cos(2*t),sin(t)) );
	//g_cloth->setPos(6,0,Vec3d(3.0+0.12*sin(t),0.1*cos(5*t),sin(t)) );
//	g_cloth->setPos(0,0,Vec3d(0,0,0) );
	//g_cloth->setPos(13,0,Vec3d(0.5*cos(3*t),0.1,sin(t)) );
	//t+=7*h;

	if (g_anim)
	{
          g_cloth->step(g_dt, 0.5);
	}

    glutPostRedisplay();
    glutTimerFunc(value, &glut_timerfunc, value);
}

static void glut_reshape(int width, int height)
{
    glViewport(0,0,g_w=width, g_h=height);
	glGetIntegerv(GL_VIEWPORT, g_vp);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective( 30.0, (double)g_w/g_h, 1.0, 100.0 );
	glGetDoublev(GL_PROJECTION_MATRIX, g_pm);	
	glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    g_cam->applyViewingTransform();
	glGetDoublev(GL_MODELVIEW_MATRIX, g_mv);


    glutPostRedisplay();
}


static void glut_init()
{
    static GLfloat light0_diffuse[] = {0.5, 0.5, 0.5, 1.0}; 
    static GLfloat light0_position[] = {-14.0, 13.0, 13.0, 1.0};
    static GLfloat light1_diffuse[] = {0.5, 0.5, 0.5, 1.0};
    static GLfloat light1_position[] = {15.0, 17.0, -10.0, 1.0};
    static GLfloat light2_diffuse[] = {0.5, 0.5, 0.5, 1.0};
    static GLfloat light2_position[] = {-4.0, 8.0, 4.0, 1.0};
    static GLfloat mat_ambient[] = {0.7, 0.7, 0.7, 1.0};
    
    
    glViewport(0,0,g_w, g_h);
    
    glEnable(GL_LIGHT0);
    glEnable(GL_LIGHT1);
    glEnable(GL_LIGHT2);
    glEnable(GL_LIGHTING);
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_NORMALIZE);
    //glAlphaFunc(GL_LESS, 1); 
    glEnable(GL_ALPHA_TEST); 
    glEnable (GL_BLEND);
	glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
       
    
  //  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    
    glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, mat_ambient);

	glLoadIdentity();

	int one=1;
	glLightModeliv(GL_LIGHT_MODEL_TWO_SIDE, &one);

	//glBlendFunc(GL_ONE_MINUS_SRC_ALPHA, GL_SRC_ALPHA);
    glLightfv(  GL_LIGHT0, GL_POSITION, light0_position);
    glLightfv(  GL_LIGHT1, GL_POSITION, light1_position);
    glLightfv(  GL_LIGHT2, GL_POSITION, light2_position);
    glLightfv(  GL_LIGHT0, GL_DIFFUSE, light0_diffuse);
    glLightfv(  GL_LIGHT1, GL_DIFFUSE, light1_diffuse);
    glLightfv(  GL_LIGHT2, GL_DIFFUSE, light2_diffuse);
}

int glut_main(int argc, char *argv[])
{

    g_cam = new Camera();

	if (argc < 9 || (argv[7][0]!='e' && argv[7][0]!='r' && argv[7][0]!='i'))
	{
		cerr << "\ausage: " << argv[0] << endl <<
			"[size] [structk] [sheark] [flexk] [gravk] [visck] [e|r|i] [h]" << 
			endl <<
			"e = explicit euler, r = runge-kutta order 4, i = implicit euler"
			<< endl;
		cerr << "initializing with default parameters" << endl;

		g_n  = 15;
		g_st = 100;
		g_sh = 100;
		g_fl = 75;
		g_gr = 9.8;
		g_vi = 0.01;
		g_sid = 'i';
		g_dt = 0.25;
	}
	else
	{
		g_n  = atoi(argv[1]);
		g_st = atof(argv[2]);
		g_sh = atof(argv[3]);
		g_fl = atof(argv[4]);
		g_gr = atof(argv[5]);
		g_vi = atof(argv[6]);
		g_sid = argv[7][0];
		g_dt = atof(argv[8]);
	}

	cout << "parameters" << endl;
	cout << "  size               " << g_n << endl;
	cout << "  structural spring  " << g_st << endl;
	cout << "  shear spring       " << g_sh << endl;
	cout << "  flex spring        " << g_fl << endl;
	cout << "  graviational       " << g_gr << endl;
	cout << "  viscous            " << g_vi << endl;
	cout << "  stepsize           " << g_dt << endl;

	cout << endl << "initializing cloth" << endl;
	init_cloth();

    

    glutInitWindowSize(g_w, g_h);
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
    glutCreateWindow("MATH466 Cloth Madness!");
    
    glutDisplayFunc(&glut_display);
    glutMouseFunc(&glut_mousefunc);
    glutMotionFunc(&glut_motionfunc);

	glutKeyboardFunc(&glut_keyboardfunc);
    glutTimerFunc(1, &glut_timerfunc, 1);
    
    glut_init();
    glutReshapeFunc(&glut_reshape);
    glutMainLoop();
    return 0;
}


static bool pick(int &ii, int &jj, const int x, const int y, int tol)
{
	int i,j;
	int n = g_cloth->getSize();
	
	
	Vec3d e( g_cam->getPosition() );
	double d = HUGE_VAL;
	for (j=0; j<n; j++)
	{
		for (i=0; i<n; i++)
		{
			Vec3d v( g_cloth->getPos(i,j) );
			double wx,wy,wz;
		//	cout << v << endl;

			if (GL_TRUE == gluProject(v[0],v[1],v[2], g_mv,g_pm,g_vp, &wx,&wy,&wz))
			{
			//	cout << wx <<' '<< wy << ' '<<wz << '-' << x << ' ' << g_h-y << endl;
				double dtmp = sqrlen(v-e);
//				cout << dtmp << endl;
//				cout << wx << ' ' << wy << ' ' << wz << endl;
				if (fabs(wx-x) < tol && fabs(wy-g_h+y) < tol && dtmp <= d)
				{
					ii = i;
					jj = j;
					d = dtmp;
				}
			}
		}
	}

	if (d == HUGE_VAL)
		return false;
	else
		return true;


	
	

}
