#include "includes.h"

#include "Scene.h"

#define PIXEL_STEP 1

SDL_Surface* screen;
SDL_Surface* image;

SDL_Thread* thread;

Scene* scene;

bool finished = false;

//Some linear algebra functions ripped right out of my library
template <typename type_float> type_float length(type_float vec[], int vec_length) {
	type_float squared_distance = 0.0;
	for (int i=0;i<vec_length;++i) { squared_distance += vec[i]*vec[i]; }
	return pow(squared_distance,(type_float)0.5);
}
template <typename type_float> bool normalize(type_float vec[], int vec_length) {
	type_float l = length(vec,vec_length);
	if (l!=0.0) {
		for (int i=0;i<vec_length;++i) {
			vec[i] = vec[i]/l;
		}
		return true;
	}
	for (int i=0;i<vec_length;++i) { vec[i]=0.0; }
	return false;
}
template <typename type_float> bool normalize(type_float vec[], type_float result[], int vec_length) {
	type_float l = length(vec,vec_length);
	if (l!=0.0) {
		for (int i=0;i<vec_length;++i) {
			result[i] = vec[i]/l;
		}
		return true;
	}
	for (int i=0;i<vec_length;++i) { result[i]=0.0; }
	return false;
}
template <typename type_float> type_float dot(type_float vec1[], type_float vec2[], int vec_length) {
	type_float result = 0.0;
	for (int i=0;i<vec_length;++i) {
		result += vec1[i]*vec2[i];
	}
	return result;
}
template <typename type_float> void cross(type_float vec1[3], type_float vec2[3], type_float result[3]) {
	result[0] = vec1[1]*vec2[2]-vec1[2]*vec2[1];
	result[1] = vec1[2]*vec2[0]-vec1[0]*vec2[2];
	result[2] = vec1[0]*vec2[1]-vec1[1]*vec2[0];
}
template <typename type_num> void scale(type_num sc, type_num vec[], int vec_length) {
	for (int i=0;i<vec_length;++i) { vec[i]=sc*vec[i]; }
}
template <typename type_num> void scale(type_num sc, type_num vec[], type_num result[], int vec_length) {
	for (int i=0;i<vec_length;++i) { result[i]=sc*vec[i]; }
}
template <typename type_num> void add(type_num vec1[], type_num vec2[], type_num result[], int vec_length) {
	for (int i=0;i<vec_length;++i) { result[i]=vec1[i]+vec2[i]; }
}
template <typename type_num> void subtract(type_num vec1[], type_num vec2[], type_num result[], int vec_length) {
	for (int i=0;i<vec_length;++i) { result[i]=vec1[i]-vec2[i]; }
}
template <typename type_num1, typename type_num2, typename type_num3> void multiply(type_num1 vec1[], type_num2 vec2[], type_num3 result[], int vec_length) {
	for (int i=0;i<vec_length;++i) { result[i]=vec1[i]*vec2[i]; }
}
template <typename type_num1, typename type_num2, typename type_num3> void divide(type_num1 vec1[], type_num2 vec2[], type_num3 result[], int vec_length) {
	for (int i=0;i<vec_length;++i) { result[i]=vec1[i]/vec2[i]; }
}

void surface_pixel_set(SDL_Surface* surface, int x, int y, Uint32 pixel) {
	int bpp = surface->format->BytesPerPixel;

	//Here p is the address to the pixel we want to set
	Uint8 *p = (Uint8*)surface->pixels + y * surface->pitch + x * bpp;

	switch(bpp) {
		case 1:
			*p = pixel;
			break;
		case 2:
			*(Uint16 *)p = pixel;
			break;
		case 3:
			if(SDL_BYTEORDER == SDL_BIG_ENDIAN) {
				p[0] = (pixel >> 16) & 0xff;
				p[1] = (pixel >> 8) & 0xff;
				p[2] = pixel & 0xff;
			}
			else {
				p[0] = pixel & 0xff;
				p[1] = (pixel >> 8) & 0xff;
				p[2] = (pixel >> 16) & 0xff;
			}
			break;
		case 4:
			*(Uint32 *)p = pixel;
			break;
	}
}
double intersect_sphere(double ray_direction[3], double ray_origin[3], double sphere_center[3], double sphere_radius) {
	double delta[3];
	double a = dot(ray_direction,ray_direction,3);

	subtract(ray_origin,sphere_center,delta,3);
	double b = 2.0*dot(ray_direction,delta,3);

	double c = dot(delta,delta,3) - sphere_radius*sphere_radius;

	double radicand = b*b - 4*a*c;
	if (radicand<0) return 0.0;

	return (-b-pow(radicand,0.5))/(2.0*a);
}
Uint32 trace_ray(double ray[3], double origin[3]) {
	list<Object*>* objs = &scene->objects;
	double closest = MAX_DOUBLE;
	bool hit = false;
	for (list<Object*>::iterator iter=objs->begin(); iter!=objs->end(); ++iter) {
		Sphere* sphere = (Sphere*)(*iter);

		double intersect = intersect_sphere(ray,origin,sphere->position,sphere->radius);
		if (intersect==0.0) continue;
		closest = min(closest,intersect);
		hit = true;
	}
	if (hit) {
		double near = 4.5;
		double far = 11.5;
		int value = (int)(255.0*(closest-near)/(far-near));
		if      (value>255) return SDL_MapRGB(image->format,0,0,255);
		else if (value<  0) return SDL_MapRGB(image->format,255,0,0);
		else                return SDL_MapRGB(image->format,0,value,0);
	}
	return SDL_MapRGB(image->format,64,64,64);
}
int render(void* data) {
	double x[3],v[3],y[3];
	subtract(scene->camera.center,scene->camera.position,v,3);
	normalize(v,3);
	cross(v,scene->camera.up,x);
	normalize(x,3);
	cross(v,x,y);
	scale(-1.0,y,3);

	int w = scene->image.width;
	int h = scene->image.height;
	double aspect = (double)w / (double)h;

	for (int pix_x=0;pix_x<w;pix_x+=PIXEL_STEP) {
		for (int pix_y=0;pix_y<h;pix_y+=PIXEL_STEP) {
			double x_part = ((double)(pix_x)+0.5) / (double)w * 2.0 - 1.0;
			double y_part = ((double)(pix_y)+0.5) / (double)h * 2.0 - 1.0;
			y_part /= aspect;

			double temp1[3],temp2[3],img_plane_vec[3],z_vec[3],direction[3];
			scale(x_part,x,temp1,3);
			scale(y_part,y,temp2,3);
			add(temp1,temp2,img_plane_vec,3);
			scale(scene->camera.image_plane,v,z_vec,3);
			add(img_plane_vec,z_vec,direction,3);
			normalize(direction,3);

			surface_pixel_set(image,pix_x,h-pix_y-1,trace_ray(direction,scene->camera.position));
		}
	}

	finished = true;

	return 1;
}

bool input(void) {
	SDL_Event event;
	while (SDL_PollEvent(&event)) {
		switch (event.type) {
			case SDL_KEYDOWN:
				switch (event.key.keysym.sym) {
					case SDLK_ESCAPE:
						return false;
						break;
					case SDLK_s:
						if (finished) {
							printf("Saving image as \"ray-traced-image.bmp\".\n");
							SDL_SaveBMP(image,"ray-traced-image.bmp");
						}
				}
				break;
			case SDL_QUIT:
				return false;
				break;
			default:
				break;
		}
	}
	return true;
}
void draw(void) {
	SDL_BlitSurface(image,NULL,screen,NULL);
	SDL_Flip(screen);
}
void quit(int status=EXIT_SUCCESS) {
	#ifdef CRT
	#ifdef WIN32
	int leaks = _CrtDumpMemoryLeaks();
	if (leaks) throw "Memory leak detected!";
	#endif
	#endif

	exit(status);
}
int main(int argc, char **argv) {
	#ifdef CRT
	#ifdef WIN32
	_CrtSetDbgFlag(0xFFFFFFFF);
	//_crtBreakAlloc = ;
	#endif
	#endif

	scene = new Scene("scene.txt");
	if (scene->valid) {
		SDL_Init(SDL_INIT_EVERYTHING);

		SDL_WM_SetCaption("CS 413 HW 2 (Sphere Ray Tracer) Ian Mallett 2011",NULL); 
		screen = SDL_SetVideoMode(scene->image.width,scene->image.height,32,SDL_DOUBLEBUF);

		image = SDL_CreateRGBSurface(SDL_SWSURFACE,scene->image.width,scene->image.height,24,0,0,0,0);

		thread = SDL_CreateThread(render,NULL);

		while (true) {
			if (input()==false) break;
			draw();
		}

		SDL_KillThread(thread);

		SDL_FreeSurface(image);

		SDL_FreeSurface(screen);

		SDL_Quit();
	}
	else {
		printf("There was an error loading the scene:\n");
		printf("  \"%s\"\n",scene->error.c_str());
		printf("Press ENTER to exit.\n");
		getchar();
	}
	delete scene;

	quit();

	return 0;
}