#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include "../../common/objects.h"
#include "../../common/debug.h"
#include "../../common/vector.h"
#include "intersect.h"
#include "sphere.h"
#include "cylinder.h"
#include "plane.h"
#include "disc.h"

//#include <dlfcn.h>


#include <pthread.h>
extern pthread_mutex_t mutexor;



#define USE_FUNC_POINTERS
extern scene_data* main_scene;
extern int intersects_calced;
extern int skip_object;

typedef double (*intersect_module)(point* startP, vector* projV, object* sphere, point* pntIntersect);
intersect_module intersect_functions[10];


void setup_intersect()
{
	//void* int_handles[10];
	//char* error;

	intersect_functions[3] = &intersect_cylinder;
	intersect_functions[0] = &intersect_sphere;
	intersect_functions[1] = &intersect_plane;
	intersect_functions[2] = &intersect_disc;
/*	
	int_handles[0] = dlopen("./intersect/sphere.so", RTLD_NOW);
	int_handles[1] = dlopen("./intersect/plane.so", RTLD_NOW);

	error = dlerror();
	if (error)
	{
		printd(QUIET, "%s\n", error);
		exit(1);
	}
	else
	{
		printd(ALERT, "intersect handle for: %s\n", "/somewhere/something.so");
	}

	// this horrible typecast is for c++ compliers
	intersect_functions[0] = (double(*)(point*, vector*, object*, point*))
		dlsym(int_handles[0], "intersect_object");
	intersect_functions[1] = (double(*)(point*, vector*, object*, point*))
		dlsym(int_handles[1], "intersect_object");

	error = dlerror();
	if (error)
	{
		printd(QUIET, "error loading lib: %s\n", error);
		exit(1);
	}
	else
	{
		printd(MESSAGE, "intersect function [%i]\n", 1);
	}
*/
}


#ifdef USE_FUNC_POINTERS



/*
 * searches for intersection for a point along a vector
 * calls other intersection functions for specific objects 
 */
double intersect_object(point* startP, vector* projV, object *obj, int shadow)
{
	double dis = 0;

	intersects_calced++;
	
	//FIXME: rotten hack for deferred renderer
	if(obj->id == skip_object)
		return 0;
	
	if(shadow && main_scene->models[obj->id]->no_shadow)
		return 0;
	
	dis = 0;
	
	switch (obj->obj_type) 
	{
		case SPHERE:
			dis = obj->intersect(startP, projV, obj, NULL);
			//obj->inverse_map(&intsctP, obj, &deb);
			break;
		case PLANE:
			dis = obj->intersect(startP, projV, obj, NULL);
			break;
		case DISC:
			dis = obj->intersect(startP, projV, obj, NULL);
			break;
		case BACKGROUND:
			dis = obj->intersect(startP, projV, obj, NULL);
			break;
		case CYLINDER:
			dis = obj->intersect(startP, projV, obj, NULL);
		default:
			dis = obj->intersect(startP, projV, obj, NULL);
			break;
	}

	return dis;
}



/*
 * searches for intersection for a point along a vector
 * calls other intersection functions for specific objects 
 */
object* intersect(point* startP, vector* projV, point* pHit, char shadow)  
{
	//these should be infinity
	double distanceOld =  999999998;
	double distanceNew = 999999999;
	object* oHit = NULL;
	int i;
	//point intsctP;
	
vector deb;
	intersects_calced++;


	printd(DEBUG, "{ intersect\n");

	for(i=0; main_scene->models[i] != NULL; i++)
	{
		//FIXME: rotten hack for deferred renderer
		if(i == skip_object)
			continue;

		if(shadow && main_scene->models[i]->no_shadow)
			continue;

		printd(INSANE, "intersect: object #%i\n", i);
		distanceNew = 0;

		distanceNew = intersect_object(startP, projV, main_scene->models[i], shadow);
		printd(INSANE, "intersect distance: %f\n", distanceNew);

		if(distanceNew> EPSILON)
		{
			if(distanceNew < distanceOld)
			{
				distanceOld = distanceNew;
				multiply_vector(pHit, projV, distanceNew);
				add_vectors(pHit, pHit, startP);
				oHit = main_scene->models[i];
				//main_scene->intrsct.obj_num = i;
			}
		}
	}

	//main_scene->intrsct.distance = distanceOld;
	//printd(INSANE, "intersect hit: %i\n", oHit->id);
	printd(DEBUG, "} intersect\n");
	return oHit;
}


vector* get_normal(point* p, object* obj, vector* v)
{
    printd(DEBUG, "{get_normal\n");
    printd(CRAZY, " obj num: %i\n", obj->id);

	obj->normal(p, obj, v);

    printd(DEBUG, "}get_normal\n");
    return v;
}




#else


/*
 * searches for intersection for a point along a vector
 * calls other intersection functions for specific objects 
 */
object* intersect(point* startP, vector* projV, point* pHit)  
{
	//these should be infinity
	double distanceOld =  999999998;
	double distanceNew = 999999999;
	object* oHit = NULL;
	int i;
	double dis = 0;
	point intsctP;

	printd(DEBUG, "{ intersect\n");

	for(i=0; main_scene->models[i] != NULL; i++)
	{
		printd(INSANE, "intersect: object #%i\n", i);
		dis = 0;

		switch (main_scene->models[i]->obj_type) 
		{
			case SPHERE:
				dis = intersect_sphere(startP,projV,
						main_scene->models[i],&intsctP);
			case PLANE:
				dis = intersect_plane(startP, projV,
						main_scene->models[i],&intsctP);
			case DISC:
				dis = intersect_disc(startP, projV,
						main_scene->models[i],&intsctP);
		}
		printd(INSANE, "intersect distance: %f\n", dis);

		if(dis>0)
		{
			distanceNew = distance_between(&intsctP,startP);
			if(distanceNew < distanceOld)
			{
				distanceOld = distanceNew;
				pHit->x = intsctP.x;
				pHit->y = intsctP.y;
				pHit->z = intsctP.z;
				oHit = main_scene->models[i];
				main_scene->intrsct.obj_num = i;
			}
		}
	}

	main_scene->intrsct.distance = distanceOld;
	printd(INSANE, "intersect hit: %i\n", oHit);
	printd(DEBUG, "} intersect\n");
	return oHit;
}



vector* get_normal(point* p, object* obj, vector* v)
{
    printd(DEBUG, "{get_normal\n");
    printd(INSANE, " obj num: %i\n", obj->id);
    switch(obj->obj_type)
    {
        case SPHERE:
            normal_sphere(p, obj, v);
            break;
        case PLANE:
            normal_plane(p, obj, v);
            break;
        case DISC:
            normal_disc(p, obj, v); 
            break;
    }
    printd(DEBUG, "}get_normal\n");
    return v;
}

#endif
