#include <math.h>
#include <stdio.h>
#include "../../common/objects.h"
#include "../../common/vector.h"
#include "../../common/debug.h"
//#include "../render/vector.c"
#include "sphere.h"
#include "../../shader/shaders.h"

//extern scene_data* main_scene;

//extern inline double dot_product(vector* a, vector* b);

double intersect_sphere(point* startP, vector* projV, object* sphere,
//double intersect_object(point* startP, vector* projV, object* sphere,
		point* pntIntersect)
{
	double distA;	
	double distB;
	double finalDist; 
	vector diffVector; 
	double dpDDiff;		
	double dpDD;
	double dpDiffDiff;
	double determinant; 
	vector normal;
	
	printd(DEBUG,"{intersect_sphere\n");

	diffVector.x = startP->x - sphere->pos.x;
	diffVector.y = startP->y - sphere->pos.y;
	diffVector.z = startP->z - sphere->pos.z;

	dpDDiff = dot_product(projV,&diffVector);
	dpDD = dot_product(projV,projV);
	dpDiffDiff = dot_product(&diffVector,&diffVector);

//	pntIntersect->x = startP->x;
//	pntIntersect->y = startP->y;
//	pntIntersect->z = startP->z;
	
	determinant = (dpDDiff * dpDDiff - dpDD *
			(dpDiffDiff - sphere->radius * sphere->radius));

	if(determinant < 0)
	{
		return 0;
	}

	distA = (-dpDDiff + sqrt(determinant)) / dpDD;
	distB = (-dpDDiff - sqrt(determinant)) / dpDD;

	if(distA < 0 && distB <0)	// Can't use negative xIntersects
	{
		return 0;
	}
	else if(distA > 0 && distB > 0)	
	{
		if(distA < distB)
		{
			finalDist = distA;
		}
		else
		{
			finalDist = distB;
		}
	}
	else if(distA > 0)  // If only one is positive, figure out which one
	{
		finalDist = distA;
	}
	else
	{
		finalDist = distB;
	}

	//pntIntersect->x = startP->x + projV->x*finalDist;
	//pntIntersect->y = startP->y + projV->y*finalDist;
	//pntIntersect->z = startP->z + projV->z*finalDist;

	
	//normal_sphere(pntIntersect, sphere, &normal);

	//hack to make refraction work
//	if( dot_product(&normal, projV) > 1)
//		return 0;
	
	
	printd(DEBUG,"}interset_sphere\n");
	return finalDist;
}


/*
Point* intersectSphere(Point* startP, Vector* projV, object* sphere,
		Point* intersectP)
{
	Vector distanceV; 
	double tca;		
	double dist2;
	double thc2;
	double intersectDis;
	double rsq;

	rsq = sqrt(sphere->radius);
	// get the distance from the sphere to the start
	distanceV.x = sphere->posX - startP->x;
	distanceV.y = sphere->posY - startP->y;
	distanceV.z = sphere->posZ - startP->z;

	// dot the sphere->start distance with the projection vector
	tca = dotProduct(projV,&distanceV);

	// if this is negative, the sphere lies behind the camera
	if( tca < 0 )
		return startP;

	// now find the distance from entry to the sphere to the hemisphere
	dist2 = dotProduct(&distanceV, &distanceV);
	//this is the sqrt of the radius - the distance from start to hemisphere
	thc2 = rsq - dist2;

	//if thc2 is negative, no intersection
	if( thc2 < 0 )
		return startP;

	intersectDis = tca - sqrt(thc2);

	intersectP->x = startP->x + projV->x*intersectDis;
	intersectP->y = startP->y + projV->y*intersectDis;
	intersectP->z = startP->z + projV->z*intersectDis;
	
	return intersectP;
}
*/

/* fix: this needs to be killed */
/*
inline double dot_product(vector* a, vector* b)
{
	    return a->x*b->x + a->y*b->y + a->z*b->z;
}
*/

/*****************************************************
 Calculates the normal vector at a point, on an object
 *****************************************************/
vector* normal_sphere(point* q, object* objectHit, vector* n)
{
	n->x = q->x - objectHit->pos.x;
	n->y = q->y - objectHit->pos.y;
	n->z = q->z - objectHit->pos.z;
	return n;
}


vector* inverse_map_sphere(point* q, object* objectHit, vector* uv)
{
	double latitude;
	double longitude;
	double u, v;
	double cross_dot_norm;
	vector normal;
	vector cross;
	
	normal_sphere(q, objectHit, &normal);
	
	latitude = angle_between(&normal, &objectHit->up);
	latitude = latitude / M_PI;
	
	longitude = angle_between(&normal, &objectHit->norm);
	longitude = longitude / sin(latitude);
	longitude = longitude / (2 * M_PI);

	cross_product(&objectHit->norm, &objectHit->up, &cross);
	cross_dot_norm = dot_product(&cross, &normal);
	
	if(cross_dot_norm <= 0)
		longitude = 1 - longitude;

	uv->x = longitude;
	uv->y = latitude;
	return uv;
}

/*
uses ideas from:
http://www.whisqu.com/per/docs/math31.htm
http://ozark.hendrix.edu/~burch/cs/490/sched/feb8/
http://local.wasp.uwa.edu.au/~pbourke/texture_colour/spheremap/index.html
http://in4k.untergrund.net/html_articles/hugi_27_-_coding_corner_polaris_sphere_tessellation_101.htm
*/
model *sphere_polygon(object *obj, int n)
{
	int i, j;
	int point_offset, tri_offset;
	int top_point, bottom_point;
	int next_top_point, next_bottom_point;
	double x, y, z;
	double dis;
	vector *v;
	model *m;
	object *o;
	vector a;
	int num_polys = 0;
	
	m = (model*)malloc(sizeof(model));

	printd(INSANE, "tessellating sphere of %i\n", n);
	printd(INSANE, "\n");
	//there are n points in each band
	//there are n-1 circles of points
	//there is a point at the top, and one at the bottom
	m->points = (vector*)malloc((n*(n-1) + 2) * sizeof(vector));
	
	//there are n triangles in the top cap
	//there are n triangles in the bottom cap
	//there are 2n triangles in each of the n-2 bands
	m->triangles = (triangle*)malloc( (n + n + 2*n*(n-2)) * sizeof(triangle));

	m->num_triangles = (n + n + 2*n*(n-2));
	m->num_points = (n*(n-1) + 2);

	v = m->points;
	
	//create the top point
	v->x = 0;
	v->y = obj->radius;
	v->z = 0;
	print_vector(INSANE,"p ", v);

	/*
	o = sphere_create();
	copy_vector(v,&o->pos);
	o->id = main_scene->num_objects;
	o->radius = 0.5;
	main_scene->models[main_scene->num_objects] = o;
	main_scene->num_objects++;*/
	
	v++;

	//create the points for each band
	//the angle between bands is pi/n
	//the y values from the top are sin(pi/2 - pi/i)
	for(j=1; j<n; j++)
	{
		y = sin(M_PI/2 - j*(M_PI/n));
		y = y * obj->radius;   //from unit sphere to desired radius
		
		dis = cos(M_PI/2 - j*(M_PI/n));  //distance of x,z vector
		
		//now create the x and z values
		for(i=0; i<n; i++)
		{
			x = sin(M_PI/2 - i*(2*M_PI/n)); //divide circle (2pi) into n chunks
			z = cos(M_PI/2 - i*(2*M_PI/n));
			x = x * obj->radius * dis;  //scale by distance and radius
			z = z * obj->radius * dis;  //scale by distance and radius
			
			v->x = x;
			v->y = y;
			v->z = z;
			
			print_vector(INSANE,"p ", v);

			/*
			o = sphere_create();
			copy_vector(v,&o->pos);
			o->id = main_scene->num_objects;
			o->radius = 0.5;
			main_scene->models[main_scene->num_objects] = o;
			main_scene->num_objects++;
*/
			v++;
		}
	}
	
	//create the bottom point
	v->x = 0;
	v->y = -obj->radius;
	v->z = 0;
	print_vector(INSANE,"p ", v);

	/*
	o = sphere_create();
	copy_vector(v,&o->pos);
	o->id = main_scene->num_objects;
	o->radius = 0.5;
	main_scene->models[main_scene->num_objects] = o;
	main_scene->num_objects++;*/
	
	v++;

	printd(12, "\n");
	//top cap triangles
	point_offset = 1;  //skip top point
	tri_offset = 0;
	for(i=0; i<n; i++)
	{
		m->triangles[tri_offset].vertices[0] = &m->points[0];
		m->triangles[tri_offset].vertices[1] = &m->points[point_offset+i];
		m->triangles[tri_offset].vertices[2] = &m->points[point_offset+i+1];
		
		if(i == n-1)
			m->triangles[tri_offset].vertices[2] = &m->points[point_offset];
		num_polys++;
		print_vector(INSANE,"t1 ",m->triangles[tri_offset].vertices[0]);
		print_vector(INSANE,"t2 ",m->triangles[tri_offset].vertices[1]);
		print_vector(INSANE,"t3 ",m->triangles[tri_offset].vertices[2]);
		tri_offset++;
	}
	
	printd(INSANE, "\n");
	//band triangles
	for(i=0; i<(n-2); i++)  //create n-2 bands
	{
		top_point = 1 + i*n;  //skip top point, go to next section
		bottom_point = top_point + n;

		for(j=0; j<n; j++)
		{
			next_top_point = top_point + j + 1;
			next_bottom_point = bottom_point + j + 1;
			
			if(next_top_point - top_point == n)
				next_top_point = top_point;

			if(next_bottom_point - bottom_point == n)
				next_bottom_point = bottom_point;

			printd(INSANE, "triangle\n");
			//top triangle in the band
			m->triangles[tri_offset].vertices[0] = &m->points[top_point+j];
			m->triangles[tri_offset].vertices[1] = &m->points[bottom_point+j];
			m->triangles[tri_offset].vertices[2] = &m->points[next_top_point];
			num_polys++;
			
			printd(INSANE, "p[%i] ", top_point+j);
			print_vector(INSANE,"",m->triangles[tri_offset].vertices[0]);
			printd(INSANE, "p[%i] ", bottom_point+j);
			print_vector(INSANE,"",m->triangles[tri_offset].vertices[1]);
			printd(INSANE, "p[%i] ", next_top_point+j);
			print_vector(INSANE,"",m->triangles[tri_offset].vertices[2]);
			tri_offset++;

			printd(INSANE, "triangle\n");			
			//bottom triangle in the band
			m->triangles[tri_offset].vertices[0] = &m->points[bottom_point+j];
			m->triangles[tri_offset].vertices[1] = &m->points[next_bottom_point];
			m->triangles[tri_offset].vertices[2] = &m->points[next_top_point];
			num_polys++;
			
			printd(INSANE, "p[%i] ", bottom_point+j);
			print_vector(INSANE,"t1 ",m->triangles[tri_offset].vertices[0]);
			printd(INSANE, "p[%i] ", next_bottom_point+j);
			print_vector(INSANE,"t2 ",m->triangles[tri_offset].vertices[1]);
			printd(INSANE, "p[%i] ", next_top_point+j);
			print_vector(INSANE,"t3 ",m->triangles[tri_offset].vertices[2]);
			tri_offset++;
		}
			printd(INSANE, "\n");
	}

	//bottom cap triangles
	point_offset = (n*(n-1) + 2) - 1 - n;  //skip to last band's points
	for(i=0; i<n; i++)
	{
		m->triangles[tri_offset].vertices[0] = &m->points[(n*(n-1) + 2)-1];
		m->triangles[tri_offset].vertices[1] = &m->points[point_offset+i];
		m->triangles[tri_offset].vertices[2] = &m->points[point_offset+i+1];
		num_polys++;
		
		if(i == n-1)
			m->triangles[tri_offset].vertices[2] = &m->points[point_offset];
		print_vector(INSANE,"t1 ",m->triangles[tri_offset].vertices[0]);
		print_vector(INSANE,"t2 ",m->triangles[tri_offset].vertices[1]);
		print_vector(INSANE,"t3 ",m->triangles[tri_offset].vertices[2]);
		tri_offset++;
	}
	
	/*
	for(i=0; i<(n + n + 2*n*(n-2)); i++)
//	for(i=0; i<3; i++)
	{
		add_vectors(&a, m->triangles[i].vertices[0], m->triangles[i].vertices[1]);
		add_vectors(&a, &a, m->triangles[i].vertices[2]);
		multiply_vector(&a, &a, 0.3333333);

		o = sphere_create();
		copy_vector(&a,&o->pos);
		o->id = main_scene->num_objects;
		o->radius = 0.5;
		make_color(&o->diff,0, 300, 0);
		main_scene->models[main_scene->num_objects] = o;
		main_scene->num_objects++;
	}*/
	/*
	m->tris[0] = 0;
	m->tris[1] = 1;
	m->tris[2] = 2;

	m->tris[3] = 0;
	m->tris[4] = 2;
	m->tris[5] = 3;
*/
	return m;
}

object *sphere_create()
{
	object *o;
	/*
	o = (object*)malloc(sizeof(object));
	o->radius = 1;
	o->pos.x = 0; o->pos.y = 0; o->pos.z = 0;
	o->up.x = 0; o->up.y = 1; o->up.z = 0;
	o->norm.x = 1; o->norm.y = 0; o->norm.x = 0;
	
	o->trans = 0;
	o->reflect = 0;
	o->refract = 0;
	
	o->amb.r = 200; o->amb.g = 200; o->amb.b = 200;
	*/
	o = make_object(SPHERE, 0);
	set_tri(&o->pos, 0, 0, 0);
	make_color(&o->amb, 100, 100, 100);
	make_color(&o->spec, 0, 0, 0);
	make_color(&o->diff, 200, 200, 200);
	set_tri(&o->norm, 1, 0, 0);
	set_tri(&o->up, 0, 1, 0);
	set_misc(o, 0, 0, 0, 1, 0);
	o->collided = 0;

	o->shader = (void* (*)(void*, color*)) general_shader;
	o->intersect =  (double (*)(point*, vector*, void*, point*)) &intersect_sphere;
	o->normal =  (vector* (*)(point*, void*, vector*)) &normal_sphere;
	o->inverse_map =  (vector* (*)(point*, void*, vector*)) &inverse_map_sphere;
	o->no_shadow = 0;
	
	return o;
}

