//////////////////////////////////////////////////////////////////////////////////////////////
// 
// Vizualization of DT
// 
//////////////////////////////////////////////////////////////////////////////////////////////


#include <cassert>
#include <iostream>
#include <fstream>
#include <sstream>
#include <iterator>
#include <vector>
#include <list>
#include <exception>


#define VMATH_NAMESPACE vam
#include "vmath.h"
#include "structs.h"
#include "texture.h"
#include "mytime.h"
#include "utils.h"


#include <GL/freeglut.h>

namespace vam
{

	using namespace std;

	vector<Vector2f> points;


	istream& operator>>(istream& istr, Vector2f& v)
	{
		istr >> v.x >> v.y;
		
		return istr;
	}

	istream& operator>> (istream& istr, Triangle*& t)
	{
		int v1,v2,v3;
		istr >> v1 >> v2 >> v3;
		t = new Triangle(v1,v2,v3);
		return istr;
	}

	class Vizualization
	{
		list<Triangle*> triangles;
		vector< list<Triangle*> > v2tTable;
		vector<Vector3f> e3Points;
		vector<Vector3f> e3Normals;
		vector<Vector2f> e3TexCoords;

		Texture tex;
		double lastTime;

		float getAvgRadius(unsigned i)
		{
			float ret = 0;
			unsigned count = 0;
			for (list<Triangle*>::iterator it = v2tTable[i].begin(); it != v2tTable[i].end(); it++)
			{
				Triangle* t = *it;
				ret += t->getCircle().radius;
				count++;
			}
			return ret/(float)count;
		}

		float getAvgArea(unsigned i)
		{
			float ret = 0;
			unsigned count = 0;
			for (list<Triangle*>::iterator it = v2tTable[i].begin(); it != v2tTable[i].end(); it++)
			{
				Triangle* t = *it;
				Vector2f v0 = points[t->v[0]];
				Vector2f v1 = points[t->v[1]];
				Vector2f v2 = points[t->v[2]];

				Vector3f w0(v0.x, 0, v0.y);
				Vector3f w1(v1.x, 0, v1.y);
				Vector3f w2(v2.x, 0, v2.y);

				Vector3f d1 = w0 - w1;
				Vector3f d2 = w0 - w2;

				ret += d1.crossProduct(d2).size();
				count++;
			}
			return ret/(float)count;
		}

		float getAvgO(unsigned i)
		{
			float ret = 0;
			unsigned count = 0;
			for (list<Triangle*>::iterator it = v2tTable[i].begin(); it != v2tTable[i].end(); it++)
			{
				Triangle* t = *it;
				Vector2f v0 = points[t->v[0]];
				Vector2f v1 = points[t->v[1]];
				Vector2f v2 = points[t->v[2]];
				Vector2f d1 = v1 - v0;
				Vector2f d2 = v2 - v1;
				Vector2f d3 = v0 - v2;

				ret += d1.size() + d2.size() + d3.size();
				count++;
			}
			return ret/(float)count;
		}

		Vector3f getAvgNormal(unsigned i)
		{
			Vector3f ret(0,0,0);
			//unsigned count = 0;

			for (list<Triangle*>::iterator it = v2tTable[i].begin(); it != v2tTable[i].end(); it++)
			{

				Triangle* t = *it;
				Vector3f v1 = e3Points[t->v[0]];
				Vector3f v2 = e3Points[t->v[1]];
				Vector3f v3 = e3Points[t->v[2]];

				Vector3f d1 = v2 - v1;
				Vector3f d2 = v3 - v1;
				ret += d1.crossProduct(d2);
				//count++;
			}
			//ret *= 1/(float)count;
			ret.normalize();
			return ret;
		}

		/**
		 * Interpolation on triangle
		 * @param x X-coordinate in world E2 space
		 * @param y Y-coordiante in world E2 space (mapped to z in E3)
		 */
		float interpolate(float x, float y, const Triangle* t)
		{
			Vector3f v0 = e3Points[t->v[0]];
			Vector3f v1 = e3Points[t->v[1]];
			Vector3f v2 = e3Points[t->v[2]];


			Vector3f d1 = v1 - v0;
			Vector3f d2 = v2 - v0;
		
			float u, v = u = 0;

			// point X in triangle space (u,v) can be expressed as X = v0 + d1 * u + d2 * v
			// so get (u,v) coordinates from (x,y) in world space
			if (d1.x != 0)
			{
				v = - (y - (v0.z + (d1.z * (x - v0.x)) / d1.x)) / ((d1.z * d2.x) / d1.x - d2.z);
				u = (x - (v0.x + d2.x * v)) / d1.x;
			}
			else
			{
				u = - (y - (v0.z + (d2.z * (x - v0.x)) / d2.x)) / ((d1.z * d1.x) / d2.x - d1.z);
				v = (y - (v0.x + d1.x * u)) / d2.x;
			}

			// calculate point p on triangle
			Vector3f p = v0 + d1 * u + d2 * v;
			return p.y;

		}

		/**
		 * Interpolate color value
		 */
		unsigned char interpolate(float r, unsigned char v1, unsigned char v2)
		{
			return (unsigned char)(v1 + r * (v2 - v1));
		}

		unsigned getColorValue(TGAImg& img, unsigned x, unsigned y, unsigned i)
		{
			unsigned w = img.getWidth();
			unsigned h = img.getHeight();
			unsigned rx = x % w;
			unsigned ry = y % h;
			return img.getImg()[(ry * w + rx) * 3 + i];
		}

	public:
		Vector3f aabbMax, aabbMin;
		Vizualization():lastTime(-1){}
		~Vizualization()
		{
			for_each(triangles.begin(), triangles.end(), Destroy<Triangle>());
		}

		friend istream& operator>>(istream& istr, Vizualization& app)
		{
			unsigned nPts, nTris;

			istr >> nPts;
			//points.resize(nPts);
			for (unsigned i = 0; i < nPts; i++)
			{
				Vector2f v;
				istr >> v;
				points.push_back(v);
			}

			istr >> nTris;
			for (unsigned i = 0; i < nTris; i++)
			{
				Triangle* t = 0;
				istr >> t;
				assert(t != 0);
				app.triangles.push_back(t);
			}

			return istr;
		}


		void buildV2T()
		{
			v2tTable.resize(points.size());
			for (list<Triangle*>::iterator it = triangles.begin(); it != triangles.end(); it++)
			{
				Triangle* t = *it;
				for (unsigned i = 0; i < 3; i++)
				{
					v2tTable[t->v[i]].push_back(t);
				}
			}
		}

		void buildE3Points()
		{
			for (unsigned i = 0; i < points.size(); i++)
			{
				float avgRadius = getAvgRadius(i);
				//float avgArea = getAvgArea(i);
				//float avgO = getAvgO(i);
				Vector2f e2 = points[i];
				Vector3f v(e2.x, 40*log10(5/avgRadius + 1), e2.y);
				//Vector3f v(e2.x, 90/avgO, e2.y);
				e3Points.push_back(v);
			}
		}

		void buildE3Normals()
		{
			for (unsigned i = 0; i < points.size(); i++)
			{
				e3Normals.push_back(getAvgNormal(i));
			}
		}

		#define TEX_W 128
		#define TEX_H 128
		void buildTexture(vector<string>& texs)
		{

			
			// get AABB
			Vector3f mn, mx = mn = e3Points[0];
			for (unsigned i = 1; i < e3Points.size(); i++)
			{
				const Vector3f& p = e3Points[i];
				if (mx.x < p.x)
				{
					mx.x = p.x;
				}
				if (mn.x > p.x)
				{
					mn.x = p.x;
				}
				if (mx.y < p.y)
				{
					mx.y = p.y;
				}
				if (mn.y > p.y)
				{
					mn.y = p.y;
				}
				if (mn.z > p.z)
				{
					mn.z = p.z;
				}
				if (mx.z < p.z)
				{
					mx.z = p.z;
				}
			}
			aabbMin = mn;
			aabbMax = mx;

			// calculate texCoords
			Vector3f dim = mx - mn;
			for (unsigned i = 0; i < points.size(); i++)
			{
				const Vector3f& p = e3Points[i];
				Vector2f tc((p.x - mn.x) / dim.x, (p.z - mn.z) / dim.z);
				//e3TexCoords.push_back(Vector2f((p.x - mn.x) / dim.x, (p.y - mn.y) / dim.y));
				e3TexCoords.push_back(tc);
			}

			// load textures images
			std::vector<TGAImg> images;
			images.resize(texs.size());
			for (unsigned i = 0; i < texs.size(); i++)
			{
				images[i].loadFile(texs[i]);
			}

			// build texture
			unsigned int n = texs.size();
			unsigned char pix[TEX_W * TEX_H * 3];
			Triangle* triCache = 0;
			for (unsigned x = 0; x < TEX_W; x++)
			{
				float wsx = ((x/(float)(TEX_W - 1)) * dim.x) + mn.x;
				for (unsigned y = 0; y < TEX_H; y++)
				{
					float wsy = ((y/(float)(TEX_H - 1)) * dim.z) + mn.z;

					// find triangle
					Triangle* t = 0;
					if (triCache != 0)
					{
						if (triCache->isPointInside(points, Vector2f(wsx, wsy)) != Triangle::Outside)
						{
							t = triCache;
						}
					}
					if (t == 0)
					{
						
						for (list<Triangle*>::iterator it = triangles.begin(); it != triangles.end(); it++)
						{
							Triangle* tt = *it;
							if (tt->isPointInside(points, Vector2f(wsx, wsy)) != Triangle::Outside)
							{
								t = tt;
								break;
							}
						}
						triCache = t;
					}

					if (t != 0)
					{
					
						float height = interpolate(wsx, wsy, t);
	
						// ratio in <0,1>
						float ratio = (height - mn.y) / dim.y;
						if (ratio > 1)
							ratio = 0;
						else if (ratio < 0)
							ratio = 0;
	
						// lower image number compute
						float fract = ratio * (n-1);
						unsigned imN = (unsigned) fract;
						// now fract in <0,1>
						fract = fract - imN;
	
						for (unsigned i = 0; i < 3; i++)
						{
							pix[(y * TEX_W + x) * 3 + i] = interpolate(fract, getColorValue(images[imN],x,y,i), getColorValue(images[imN+1],x,y,i));
						}
					}
					else
					{
						for (unsigned i = 0; i < 3; i++)
						{
							pix[(y * TEX_W + x) * 3 + i] = 0;
						}
					}


				}
			}
			tex = Texture::fromData(TEX_W, TEX_H, pix);


		}

		void loadTexture(string fileName)
		{
			tex = Texture::fromFile(fileName);
		}

		void draw()
		{
			float rot = 0;
			double now = myTime();
			if (lastTime > 0)
			{
				rot = now - lastTime;
				rot *= 360/15.0f;
			}
			lastTime = now;
			//Vector3f dim = aabbMax - aabbMin;
			glRotatef(rot,0,1,0);
			//glPushMatrix();
			//glScalef(40/dim.x, 1, 40/dim.z);
			glColor3f(0, 0.5, 0);
			tex.bind();
			glBegin(GL_TRIANGLES);
			for (list<Triangle*>::iterator it = triangles.begin(); it != triangles.end(); it++)
			{
				//glBegin(GL_LINE_LOOP);
				const Triangle* t = *it;
				for (unsigned i = 0; i < 3; i++)
				{
					glNormal3fv(e3Normals[t->v[i]]);
					glTexCoord2fv(e3TexCoords[t->v[i]]);
					glVertex3fv(e3Points[t->v[i]]);
				}
				//glEnd();
			}
			glEnd();
			//glPopMatrix();
		}

		void reportTexCoords()
		{
			for (unsigned i = 0; i < e3TexCoords.size(); i++)
			{
				const Vector2f& v = e3TexCoords[i];
				cerr << "(" << v.x << "," << v.y << ")";
			}
			cerr << endl;
		}

	};

	struct FileNotFoundException: public exception
	{
		virtual const char* what() const throw()
		{
			return "File not found";

		}
	};

	
}

vam::Vizualization viz;

void initGL()
{

	glShadeModel(GL_SMOOTH);
	glClearColor(0,0,0,1);
	glClearDepth(1.0);
	glEnable(GL_DEPTH_TEST);
	glDepthFunc(GL_LEQUAL);

	glEnable(GL_TEXTURE_2D);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
}

void initLight()
{
	//vam::Vector4f position(100, viz.aabbMax.y + 400, 100, 1);
	vam::Vector4f position(100, 300, 100, 1);
	vam::Vector4f diffuse(1,1,1,1);
	vam::Vector4f ambient(0.2, 0.2, 0.2, 1);

	glLightfv(GL_LIGHT1, GL_POSITION, position);
	glLightfv(GL_LIGHT1, GL_AMBIENT, ambient);
	glLightfv(GL_LIGHT1, GL_DIFFUSE, diffuse);
	glEnable(GL_LIGHT1);
	glEnable(GL_LIGHTING);
}

void render()
{
	//cerr << "Render" << endl << flush;
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	viz.draw();
	glutSwapBuffers();
	//cerr << glGetError() << endl;
}

void reshape(int w, int h)
{
	if (h == 0)
	{
		h = 1;
	}
	float aspect = w/(float)h;

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glViewport(0,0,w,h);
	//glOrtho(-100, 100, -100, 100, 0.01, 8000);
	gluPerspective(45.0, aspect, 0.1, 1000.0);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	gluLookAt(0,viz.aabbMax.y + 100,250, 0,0,0, 0,1,0);
}


void idle()
{
	render();
}



int main (int argc, char** argv)
{
	try
	{
	
		std::ifstream fp(argv[1], std::ios::in);
		if (!fp.good())
			throw vam::FileNotFoundException();
		fp >> viz;
		fp.close();

		double startTime = vam::myTime();
		viz.buildV2T();
		viz.buildE3Points();
		viz.buildE3Normals();


		glutInit(&argc,argv);
		
		glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGB);
		glutCreateWindow("VAM - Delaunay triangulation");
		glutDisplayFunc(render);
		glutReshapeFunc(reshape);
		glutIdleFunc(idle);
		
		std::vector<std::string> texs;
		for (int i = 2; i < argc; i++)
		{
			texs.push_back(argv[i]);
		}
		initGL();
		viz.buildTexture(texs);
		initLight();
		double endTime = vam::myTime();
		std::cout << "Building terrain resources: " << endTime - startTime << " seconds" << std::endl;
	
		glutMainLoop();

		
	}
	catch(std::exception& e)
	{
		std::cerr << "Program failed:" << std::endl;
		std::cerr << e.what() << std::endl;
	}

	return 0;
}


