/*
    This file was created in a ray tracer tutorial. The main() and draw() functions were almost unchanged except 
	to make the view point dynamically change and some other small changes.


	*/


#include <iostream>
#include <fstream>
#include <vector>
#include <cmath>
#include <limits>
#include <algorithm>
using namespace std;


#include "Raytrace.h"
#include "Scene.h"
#include "Srgb.h"
#include "point3D.h"
#include "vector3D.h"
#include "matrix4x4.h"

#define PI 	(double)3.14159265358979323846

point normalize (point p)
{
	float len = sqrt(p.x*p.x + p.y+p.y + p.z*p.z);
	if(len != 0){
		point p2 = {p.x / len, p.y/len, p.z/len};
		return p2;
	}
	else{
		return p;
	}
}
vecteur normalize (vecteur p)
{
	float len = sqrt(p.x*p.x + p.y+p.y + p.z*p.z);
	if(len != 0){
		vecteur p2 = {p.x / len, p.y/len, p.z/len};
		return p2;
	}
	else{
		return p;
	}
}
point crossProduct(point a, point b)
{
	point result;
	result.x = a.y*b.z-a.z*b.y;
	result.y = -1*(a.x*b.z-a.z*b.x);
	result.z = a.x*b.y-a.y*b.x;
	return result;
}
vecteur crossProduct(vecteur a, vecteur b)
{
	vecteur result;
	result.x = a.y*b.z-a.z*b.y;
	result.y = -1*(a.x*b.z-a.z*b.x);
	result.z = a.x*b.y-a.y*b.x;
	return result;
}
int same_direction(point pt1, point pt2, point pt3, point norm)
{  
   float testi, testj, testk;
   float dotprod;
   // normal of trinagle
   testi = (((pt2.y - pt1.y)*(pt3.z - pt1.z)) - ((pt3.y - pt1.y)*(pt2.z - pt1.z)));
   testj = (((pt2.z - pt1.z)*(pt3.x - pt1.x)) - ((pt3.z - pt1.z)*(pt2.x - pt1.x)));
   testk = (((pt2.x - pt1.x)*(pt3.y - pt1.y)) - ((pt3.x - pt1.x)*(pt2.y - pt1.y)));

   // Dot product with triangle normal
   dotprod = testi*norm.x + testj*norm.y + testk*norm.z;

   //answer
   if(dotprod < 0) return 0;
   else return 1;
}
bool hitPlane(const ray &ray, const plane& p, float &t)
{	
	if (t <= 0.0f)
		return false;
	point norm;
	float dotprod;
	float t0;
	point i;
	
	point n1,n2;
	n1.x = p.v0.x-p.v1.x;
	n1.y = p.v0.y-p.v1.y;
	n1.z = p.v0.z-p.v1.z;

	n2.x = p.v0.x - p.v2.x;
	n2.y = p.v0.y - p.v2.y;
	n2.z = p.v0.z - p.v2.z;

	n1 = crossProduct(n1,n2);
	norm = normalize(n1);
	dotprod = norm.x*ray.dir.x + norm.y*ray.dir.y + norm.z*ray.dir.z;
	dotprod *= -1;
	if(dotprod < 0)
	{
		t0 = -(norm.x*(ray.start.x-p.v0.x)+norm.y*(ray.start.y-p.v0.y)+norm.z*(ray.start.z-p.v0.z))/
             (norm.x*ray.dir.x+norm.y*ray.dir.y+norm.z*ray.dir.z);
		//printf("t0 = %f\n", t0);
		if((t0 < 0.0f) || (t0 > t))
			return false;
		i.x = ray.start.x + ray.dir.x * t0;
		i.y = ray.start.y + ray.dir.y * t0;
		i.z = ray.start.z + ray.dir.z * t0;

		/*vecteur ai,bi,ci;
		vecteur at, bt, ct;
		ai = i - p.v0;
		bi = i - p.v1;
		bi = i - p.v2;
		at = p.v1 - p.v0;
		bt = p.v2 - p.v1;
		ct = p.v0 - p.v2;*/
		if((i.x > -400) && (i.x < 400)){
			if((i.y > -400) && (i.y < 400)){
				t = t0;
				return true;
			}
		}

	}
	return false;
}
bool hitSphere(const ray &r, const sphere& s, float &t)
{
	// From Ray Trace tutorial
	//
	vecteur dist = s.pos - r.start;
	float B = (r.dir.x * dist.x + r.dir.y * dist.y + r.dir.z * dist.z);
	float D = B*B - dist*dist + s.size * s.size;
	if (D < 0.0f) return false;
	float t0 = B - sqrtf(D);
	float t1 = B + sqrtf(D);
	if ((t0 > 0.7f ) && (t0 < t))
	{
		t = t0;
		return true;
	}
	if ((t1 > 0.7f ) && (t1 < t))
	{
		t = t1;
		return true;
	}
	return false;
}
color addRay(ray viewRay, scene &myScene)
{
	// Started from ray trace tutorial but changed heavily
	//
	color output = {0.0f, 0.0f, 0.0f}; 
	float coef = 1;
	int level = 0;
	while( (coef > 0.0f) && (level < RLEVELS) )
	{
		vecteur vNormal;
		point ptHitPoint;
		int currentObjectGroup = -1;
		int currentObject = -1;
		float t = 4000.0f;
		for(unsigned int i=0; i< myScene.sphereContainer.size(); i++){
			if (hitSphere(viewRay, myScene.sphereContainer[i], t))
			{
				currentObject = i;
				currentObjectGroup = SPHERE;
			}
		}
		for(unsigned int i=0; i<myScene.planeContainer.size(); i++){
			if (hitPlane(viewRay, myScene.planeContainer[i], t))
			{
				currentObject = i;
				currentObjectGroup = PLANE;
			}
		}
		if ((currentObject == -1)||(currentObjectGroup == -1))
		    break;	
	    ptHitPoint  = viewRay.start + t * viewRay.dir;
        material currentMat;
		if(currentObjectGroup == SPHERE){
			vNormal = ptHitPoint - myScene.sphereContainer[currentObject].pos;
			float temp = vNormal * vNormal;
			if (temp == 0.0f)
				break;
			temp = 1.0f / sqrtf(temp);
			vNormal = temp * vNormal;
			currentMat = myScene.materialContainer[myScene.sphereContainer[currentObject].materialId];
		} else if(currentObjectGroup == PLANE)
		{
			//vNormal = ptHitPoint - myScene.planeContainer[currentObject].pos;
			vNormal.x = 0.0f;
			vNormal.y = 0.0f;
			vNormal.z = 1.0f;
			currentMat = myScene.materialContainer[myScene.planeContainer[currentObject].materialId];
		}
		ray lightRay;
		lightRay.start = ptHitPoint;
		for(unsigned int i=0; i<myScene.lightContainer.size(); i++){
			float fLightProjection;
			float lightDist;
			float t;
			light currentLight = myScene.lightContainer[i];
			lightRay.dir = currentLight.pos - ptHitPoint;
			if(currentObjectGroup == SPHERE)
			{
				fLightProjection = lightRay.dir * vNormal;
				if ( fLightProjection <= 0.0f )
					continue;
				lightDist = lightRay.dir * lightRay.dir;	
				if ( lightDist == 0.0f )
					continue;
				float temp = lightDist;
				temp = invsqrtf(temp);
				lightRay.dir = temp * lightRay.dir;
				fLightProjection = temp * fLightProjection;				
				t = lightDist;
			} else {
				fLightProjection = lightRay.dir * vNormal * -1;
				//if ( fLightProjection <= 0.0f )
				//	continue;
				lightDist = lightRay.dir * lightRay.dir;	
				//if ( lightDist == 0.0f )
				//	continue;
				//float temp = lightDist;
				//temp = invsqrtf(temp);
				//lightRay.dir = temp * lightRay.dir;
				//fLightProjection = temp * fLightProjection;				
				t = lightDist;
			}
			
			bool inShadow = false;
		    for (unsigned int i=0; i<myScene.sphereContainer.size() ; i++)
		    {
				float oldt = t;
			    if (hitSphere(lightRay, myScene.sphereContainer[i], t))
			    {
					if(currentObjectGroup == PLANE)
					{
						// Problems with this function always returning true for planes. 
						// I had to comment out. 
					}else
					inShadow = true;	
				}
		    }
			for(unsigned int i=0; i<myScene.planeContainer.size(); i++)
			{
				if (hitPlane(lightRay, myScene.planeContainer[i], t))
			    {
				    inShadow = true;
			    }
		    }
			if(inShadow == false)
			{
				float lambert = (lightRay.dir * vNormal) * coef;
				output.red += lambert * currentLight.intensity.red * currentMat.diffuse.red;
				output.green += lambert * currentLight.intensity.green * currentMat.diffuse.green;
				output.blue += lambert * currentLight.intensity.blue * currentMat.diffuse.blue;
			}
		} // end lighting for loop
		coef *= currentMat.reflection;
		float reflet = 2.0f * (viewRay.dir * vNormal);
        viewRay.start = ptHitPoint;
		viewRay.dir = viewRay.dir - reflet * vNormal;
		level++;
	} // end-while loop
	return output;
}

bool draw(char* outputName, scene &myScene, float angle)
{
	int x, y;
	ofstream imageFile(outputName,ios_base::binary);
	if (!imageFile)
	{
		printf("Bad Image File\n");
		return false;
	}
    // Addition of the TGA header
	imageFile.put(0).put(0);
    imageFile.put(2);        // RGB not compressed 
	imageFile.put(0).put(0);
	imageFile.put(0).put(0);
	imageFile.put(0);
    imageFile.put(0).put(0); // origin X 
    imageFile.put(0).put(0); // origin Y 
    imageFile.put((unsigned char)(myScene.sizex & 0x00FF)).put((unsigned char)((myScene.sizex & 0xFF00) / 256));
    imageFile.put((unsigned char)(myScene.sizey & 0x00FF)).put((unsigned char)((myScene.sizey & 0xFF00) / 256));
	imageFile.put(24);                 // 24 bit bitmap
	imageFile.put(0);
    // end of the TGA header 
    // Scanning 
	/*for (y = myScene.sizey/-2; y < myScene.sizey/2; ++y){
		for (x = myScene.sizex/-2 ; x < myScene.sizex/2; ++x){      
			color output = {0.0f, 0.0f, 0.0f};
			
			point3D viewPoint(float(x), float(y), -1000.0f);
			point3D normal(0.0f, 0.0f, 1.0f);

			matrix4x4 R,T;
			//T.initMatrixTranslation((float)x, float(y), 0.0f);
			R.initMatrixXYZRotation(0.0f, (float)(angle* PI /180) ,0.0f);
			point3D vn = R * viewPoint;
			point3D nn = R * normal;

			ray viewRay = { {vn[0], vn[1], vn[2]}, {nn[0], nn[1], nn[2]}};
			color temp = addRay (viewRay, myScene);
			output = temp;
		    // gamma correction
		    output.blue = srgbEncode(output.blue);
		    output.red = srgbEncode(output.red);
		    output.green = srgbEncode(output.green);
            imageFile.put((unsigned char)min(output.blue*255.0f,255.0f)).put((unsigned char)min(output.green*255.0f, 255.0f)).put((unsigned char)min(output.red*255.0f, 255.0f));
        }
	}*/
	for (y = myScene.sizey/-2; y < myScene.sizey/2; ++y){
		for (x = myScene.sizex/-2 ; x < myScene.sizex/2; ++x){      
			color output = {0.0f, 0.0f, 0.0f};		
			for (float fragmentx = float(x) ; fragmentx < x + 1.0f; fragmentx += 0.5f )
		    for (float fragmenty = float(y) ; fragmenty < y + 1.0f; fragmenty += 0.5f )
		    {
				point3D viewPoint(float(fragmentx)*2, float(fragmenty)*2, -1000.0f);
				point3D normal(0.0f, 0.0f, 1.0f);
				matrix4x4 R,T;
				T.initMatrixTranslation((float)x, float(y), 0.0f);
				R.initMatrixXYZRotation(0.0f, (float)(angle* PI /180) ,0.0f);
				point3D vn = R * viewPoint;
				point3D nn = R * normal;
				//
			    float sampleRatio=0.25f;
			    ray viewRay = { {vn[0], vn[1], vn[2]}, {nn[0], nn[1], nn[2]}};

                color temp = addRay (viewRay, myScene);
                // pseudo photo exposure
                float exposure = -1.00f; // random exposure value. TODO : determine a good value automatically
	            temp.blue = (1.0f - expf(temp.blue * exposure));
	            temp.red =  (1.0f - expf(temp.red * exposure));
	            temp.green = (1.0f - expf(temp.green * exposure));
	            output += sampleRatio * temp;
		    }	
		    // gamma correction
		    output.blue = srgbEncode(output.blue);
		    output.red = srgbEncode(output.red);
		    output.green = srgbEncode(output.green);
            imageFile.put((unsigned char)min(output.blue*255.0f,255.0f)).put((unsigned char)min(output.green*255.0f, 255.0f)).put((unsigned char)min(output.red*255.0f, 255.0f));
        }
	}
	return true;
}

int __cdecl main(int argc, char* argv[])
{
    if (argc < 3)
    {
        cout << "Usage : ray.exe Scene.txt Outputfile.tga" << endl;
        return -1;
    }
    scene myScene;
    if (!init(argv[1], myScene))
    {
        cout << "Failure when reading the Scene file." << endl;
        return -1;
    }
	int imageCount = 0;
	int i = 0;
	//for(int i=0; i<360; i+= 7){
		if ( !draw(argv[2], myScene, (float)0) )
		{
			cout << "Failure when creating the image file." << endl;
			return -1;
		}
		printf("Frame %d\n", imageCount);
		imageCount++;
	//}
    return 0;
	cout << "Done." << endl;
}

