#include "raytracer.h"
#include <algorithm>
#include <cfloat>

//RAY controls
#define MAX_RAY_DEPTH 5

//some helper functions
inline double clamp(double x){ return x<0 ? 0 : x>1 ? 1 : x; }

inline int toInt(double x){ return int( clamp(x)*255 + .5); }


//constructor
RayTracer::RayTracer(list<object3D*>& models) : m_models(models)
{
    //find out where lights are
    m_lights.clear();
    for (list<object3D*>::iterator i = models.begin(); i != models.end(); i++)
    {
        object3D* obj = *i;
        if (obj->mat_emission.normsqr() != 0) m_lights.push_back(*i);
    }

    if (m_lights.empty())
    {
        cerr << "! Error: There is no light defined. Make sure that at least one object has non-zero emission." << endl;
        exit(0);
    }
}

inline void show_progress_bar(double ratio)
{
    // Show the load bar.
    static const int w = 50;
    int   c = (int)(ratio * w);

    cout << setw(3) << (int)(ratio * 100) << "% [";
    for (int x = 0; x<c; x++) cout << "=";
    for (int x = c; x<w; x++) cout << " ";
    cout << "]\r" << flush;
}

//render an image with "nray_per_pixel" rays created per pixel
void RayTracer::render(const Camera& cam, unsigned int img_w, unsigned int img_h, unsigned int nray_per_pixel)
{
    //create a black image of size img_w X img_h
    m_img = vector< vector< Vector3d > >(img_h, vector< Vector3d >(img_w, Vector3d(0,0,0) ));
     
    //initialize the bar
    show_progress_bar(0);

    //generate rays
    for (unsigned int y = 0; y < img_h; y++)
    {

        for (unsigned int x = 0; x < img_w; x++)
        {
            Vector3d color;
            for (unsigned int n = 0; n < nray_per_pixel; n++)
            {
				Ray r = create_a_random_ray(cam, x, y);
                Vector3d rc = raycolor(r, 0);
                color = rc + color;
            }

            m_img[y][x] = color / nray_per_pixel;
        }//end of x

        show_progress_bar(y*1.0 / img_h);

    }//end of y
    show_progress_bar(1.0f);
    cout << endl;
}

// create a ray randomly in the given pixel (x,y)
Ray RayTracer::create_a_random_ray(const Camera& cam, unsigned int x, unsigned int y)
{
    Ray r;

    //TODO: implement this using settings in cam and (x,y)
    //hint: see slides on generating rays for perspective views

    if (cam.type == Camera::ORTHOGONAL_CAM)
    {
        //setup an orthogonal cam
    }
    else if (cam.type == Camera::PERSPECTIVE_CAM)
    {
        //setup a perspective cam
    }

    return r;
}



//
//returns true if the sphere intersects ray r
//return false if no intersection is found
//x is the location of intersection if true is returned.
//
bool RayTracer::intersect(sphere& s, Ray r, Point3d& x)
{
    //
    // TODO: implement this
    // 

    return false;
}


//returns a triangle in model m that intersect ray r
//return NULL if no intersection is found
triangle * RayTracer::intersect(model& m, Ray r)
{
    for (unsigned int i = 0; i < m.t_size; i++)
    {
        Point3d x;
        if ( intersect(m, &m.tris[i], r, x) )
            return &m.tris[i];
    }

    return NULL;
}

//returns a model and triangle that intersect ray r
//return pair<NULL,NULL> if no intersection is found
pair<object3D *, triangle *> RayTracer::intersect(Ray r)
{
	double min_dist = FLT_MAX;
	object3D * closest_M = NULL; //keep track of the closest sphere
	triangle * closest_T = NULL;

	for (list<object3D*>::iterator i = m_models.begin(); i != m_models.end(); i++)
	{
		object3D* obj = *i;

		if (dynamic_cast<model*>(obj)) //this object is a mesh
		{
			cerr << "! Error: This is for your future assignment." << endl;
			exit(1);
		}
		//--------------------------------------------------------------------------
		else if (dynamic_cast<sphere*>(obj)) //this object is a sphere
		{
			sphere* ball = dynamic_cast<sphere*>(obj);
			Point3d x;
			if (intersect(*ball, r, x))
			{
				double dist = (x - r.o).normsqr();
				if (dist < min_dist)
				{
					min_dist = dist;
					closest_T = NULL;
					closest_M = ball;
				}
			}
		}//--------------------------------------------------------------------------
	}

	return make_pair(closest_M, closest_T);
}

// determine if there is an intersection between triangle t and ray r
// return true if there is an intersection and store the intersection in x
// return false otherwise and x is undefined in this case
bool RayTracer::intersect(model& m, triangle * t, Ray r, Point3d& x)
{
    cerr << "! Error: This is for your future assignment." << endl;
    exit(1);
    return false;
}


//
// determine the color of ray r
// 
Vector3d RayTracer::raycolor(const Ray& r, int depth)
{
    if (depth >= MAX_RAY_DEPTH) return Vector3d(0,0,0);

    Vector3d color;
    pair<object3D *, triangle *> X = intersect(r);

    //determine the color of this ray 
    if (X.first != NULL && X.second != NULL) //this is mesh intersection
    {
        Vector3d rc = raycolor(*((model*)X.first), X.second, r,depth);
        color = rc + color;
    }
    else if (X.first != NULL) //this is spehere intersection
    {
        Vector3d rc = raycolor(*((sphere*)X.first), r, depth);
        color = rc + color;
    }

    return color;
}

//
// determine the color of ray r, by analizing the intersection between t and r 
// 
Vector3d RayTracer::raycolor(sphere& s, const Ray& r, int depth)
{
    Vector3d color;

    //TODO: determine the direct color
    //
    //hint: (1) what is the normal direction at the intersection between s and r
    //      (2) what is the color of this sphere? 
    //      (3) where is the light?

    return color;
}


//
// determine the color of ray r, by analizing the intersection between t and r 
// 
Vector3d RayTracer::raycolor(model& m, triangle * t, const Ray& r, int depth)
{
    Vector3d color;

    cerr << "! Error: This is for your future assignment." << endl;
    exit(1);

    return color;
}

//save rendered image to file
bool RayTracer::save2file(const string& filename)
{
    FILE *f = fopen(filename.c_str(), "w");         // Write image to PPM file.

    int h = m_img.size();
    if (h == 0) return true; //nothing to save...

    int w = m_img.front().size();
    
    fprintf(f, "P3\n%d %d\n%d\n", w, h, 255);

    for (int i = 0; i < h; i++)
    {
        for (int j = 0; j < w; j++)
            fprintf(f, "%d %d %d ", toInt(m_img[i][j][0]), toInt(m_img[i][j][1]), toInt(m_img[i][j][2]));

        show_progress_bar(i*1.0 / h);
    }
    show_progress_bar(1.0f);
    fclose(f);
    
    return true;
}


