//------------------------------------------------------------------------------
//  Copyright 2007-2014 by Jyh-Ming Lien and George Mason University
//  See the file "LICENSE" for more information
//------------------------------------------------------------------------------


#pragma once

#include "objReader.h"
#include "model.h"
#include "camera.h"

#include <list>
#include <float.h>
#include <algorithm>
using namespace std;

//-----------------------------------------------------------------------------
// INPUTS

//configuraton file
string rt_filename; //an rt (raytracing) file defines models and their transformation and material properties

//image
unsigned int image_w = 100, image_h = 100; //image size
unsigned int n_ray = 10;
unsigned int max_ray_depth = 5;

//light
float ambient_light[3] = { 0, 0, 0 }; // global ambient light
unsigned int  light_sample_size = 500; //number of points that are sampled from the area light surface, for each light

//your virtual camera
Camera camera;

//-----------------------------------------------------------------------------
// Intermediate data
list<object3D *> models;

float R=0;       //radius
Point3d COM;     //center of mass
double BOX[6];   //bounding box

//-----------------------------------------------------------------------------
//read models from file
bool readfromfile();
void computeCOM_R();
void printUsage(char * name);

//-----------------------------------------------------------------------------
bool parseArg(int argc, char ** argv)
{
    for(int i=1;i<argc;i++){
        if(argv[i][0]=='-')
        {
            string tmp(argv[i]);
            return false; //unknown
        }
        else{
            rt_filename=argv[i];
        }
    }

    if (rt_filename.empty())
    {
        cerr << "! Error: No RT (Raytracing) file is given." << endl;
        printUsage(argv[0]);
        return false;
    }

    if (rt_filename.find(".rt") == string::npos)
    {
        cerr << "! Error: the given file ("<< rt_filename<<") is not an RT(Raytracing) file." << endl;
        printUsage(argv[0]);
        return false;
    }

    return true;
}

void printUsage(char * name)
{
    //int offset=20;
    cerr << "Usage: " << name << " *.rt \n";
    cerr<<"\n-- Report bugs to: Jyh-Ming Lien jmlien@gmu.edu"<<endl;
}

//-----------------------------------------------------------------------------

void computeCOM_R()
{
    //compute a bbox
    double box[6] = { FLT_MAX, -FLT_MAX, FLT_MAX, -FLT_MAX, FLT_MAX, -FLT_MAX };
    //-------------------------------------------------------------------------
    for (list<object3D*>::iterator i = models.begin(); i != models.end(); i++)
    {
        object3D* obj = *i;
        
        //this obj is a mesh
        if (dynamic_cast<model*>(obj))
        {
            model* mesh = dynamic_cast<model*>(obj);
            for (unsigned int j = 0; j < mesh->v_size; j++){ //this is a sphere
                Point3d& p = mesh->vertices[j].p;
                if (p[0]<box[0]) box[0] = p[0];
                if (p[0]>box[1]) box[1] = p[0];
                if (p[1]<box[2]) box[2] = p[1];
                if (p[1]>box[3]) box[3] = p[1];
                if (p[2]<box[4]) box[4] = p[2];
                if (p[2]>box[5]) box[5] = p[2];
            }//j
        }
        else if (dynamic_cast<sphere*>(obj)) //this is a ball
        {
            sphere* ball = dynamic_cast<sphere*>(obj);
            double sbox[6] = { ball->center[0] - ball->radius, ball->center[0] + ball->radius, 
                               ball->center[1] - ball->radius, ball->center[1] + ball->radius, 
                               ball->center[2] - ball->radius, ball->center[2] + ball->radius };

            if (sbox[0] < box[0]) box[0] = sbox[0];
            if (sbox[1] > box[1]) box[1] = sbox[1];
            if (sbox[2] < box[2]) box[2] = sbox[2];
            if (sbox[3] > box[3]) box[3] = sbox[3];
            if (sbox[4] < box[4]) box[4] = sbox[4];
            if (sbox[5] > box[5]) box[5] = sbox[5];
        }
    }//i

    //-------------------------------------------------------------------------
    // compute center of mass and R...
    COM.set((box[1] + box[0]) / 2, (box[3] + box[2]) / 2, (box[5] + box[4]) / 2);

    //scale the twice box first
    box[0] = max((box[0] - COM[0]) * 10 + COM[0], box[0] - 30);
    box[1] = min((box[1] - COM[0]) * 10 + COM[0], box[1] + 30);
    box[3] = box[3] + 0.01;
    box[4] = max((box[4] - COM[2]) * 10 + COM[2], box[4] - 30);
    box[5] = min((box[5] - COM[2]) * 10 + COM[2], box[5] + 30);

    COM.set((box[1] + box[0]) / 2, (box[3] + box[2]) / 2, (box[5] + box[4]) / 2);

    //-------------------------------------------------------------------------
    R = (float)(COM - Point3d(box[0], box[2], box[4])).norm();

    //remember the box
    for (int i = 0; i < 6; i++) BOX[i] = box[i];
}


//-----------------------------------------------------------------------------
//
//
//
//  parsing objects
//
//
//-----------------------------------------------------------------------------

//convert a string to a list of tokens
inline list<string> tokenize(char * tmp, const char * ignore)
{
    list<string> tokens;
    char * tok = strtok(tmp, ignore);
    while (tok != NULL)
    {
        tokens.push_back(tok);
        tok = strtok(NULL, ignore);
    }
    return tokens;
}


inline bool findImageSize(istream& in)
{
    const int size = 1024;
    char * tmp = new char[size];
    in.getline(tmp, size);//read lines from a file into strings
    list<string> tok = tokenize(tmp, " =\t[]()<>,");
    if (tok.size() == 0) return false;			//empty line
    if (tok.front()[0] == '#') return false; 	//commented out

    for (list<string>::iterator i = tok.begin(); i != tok.end(); i++){
        string& t = *i;
        if (t[0] == '#') break;
        if (t == "width") image_w = (unsigned int)atoi((++i)->c_str());
        else if (t == "height") image_h = (unsigned int)atoi((++i)->c_str());
        else if (t == "n") n_ray = (unsigned int)atoi((++i)->c_str());
		else if (t == "max_ray_depth") max_ray_depth = (unsigned int)atoi((++i)->c_str());
    }

    delete[] tmp;
    return true;
}

inline bool createModel(istream& in)
{
    const int size = 1024;
    char * tmp = new char[size];
    in.getline(tmp, size);//read lines from a file into strings
    list<string> tok = tokenize(tmp, " =\t[]()<>,");
    if (tok.size() == 0) return false;			//empty line
    if (tok.front()[0] == '#') return false; 	//commented out

    Vector3d m_local_rot(0, 0, 0);
    Vector3d m_pos(0, 0, 0);
    double s=1; //scale
    string filename;
    Vector3d mat_color = Vector3d(1, 1, 1);
    Vector3d mat_specular = Vector3d(1, 1, 1);
    Vector3d mat_emission = Vector3d(0, 0, 0);
    float    mat_shininess = 128;

    for (list<string>::iterator i = tok.begin(); i != tok.end(); i++){
        string& t = *i;
        if (t[0] == '#') break;
        if (t == "tx") m_pos[0] = (float)atof((++i)->c_str());
        else if (t == "ty") m_pos[1] = (float)atof((++i)->c_str());
        else if (t == "tz") m_pos[2] = (float)atof((++i)->c_str());
        else if (t == "rx") m_local_rot[0] = (float)atof((++i)->c_str());
        else if (t == "ry") m_local_rot[1] = (float)atof((++i)->c_str());
        else if (t == "rz") m_local_rot[2] = (float)atof((++i)->c_str());
        else if (t == "scale") s = (float)atof((++i)->c_str());
        //difuss color
        else if (t == "cr") mat_color[0] = (float)atof((++i)->c_str());
        else if (t == "cg") mat_color[1] = (float)atof((++i)->c_str());
        else if (t == "cb") mat_color[2] = (float)atof((++i)->c_str());
        //specular color
        else if (t == "sr") mat_specular[0] = (float)atof((++i)->c_str());
        else if (t == "sg") mat_specular[1] = (float)atof((++i)->c_str());
        else if (t == "sb") mat_specular[2] = (float)atof((++i)->c_str());
        //emission color
        else if (t == "er") mat_emission[0] = (float)atof((++i)->c_str());
        else if (t == "eg") mat_emission[1] = (float)atof((++i)->c_str());
        else if (t == "eb") mat_emission[2] = (float)atof((++i)->c_str());
        else if (t == "shininess") mat_shininess = (float)atof((++i)->c_str());
        else filename = t;
    }

    delete[] tmp;

    Quaternion rot = Quaternion(m_local_rot.get()); //rz*ry*rx;

    model * m=new model();
    assert(m);
    if (!m->build(filename)) return false;

    m->transform(m_pos, rot.getMatrix(), s);
    m->mat_color = mat_color;
    m->mat_specular = mat_specular;
    m->mat_emission = mat_emission;
    m->mat_shininess = mat_shininess;

    models.push_back(m);

    return true;
}


inline bool createSphere(istream& in)
{
    const int size = 1024;
    char * tmp = new char[size];
    in.getline(tmp, size);//read lines from a file into strings
    list<string> tok = tokenize(tmp, " =\t[]()<>,");
    if (tok.size() == 0) return false;			//empty line
    if (tok.front()[0] == '#') return false; 	//commented out

    Vector3d m_pos(0, 0, 0);
    double radius = 1; //radius

    Vector3d mat_color = Vector3d(1, 1, 1);
    Vector3d mat_specular = Vector3d(1, 1, 1);
    Vector3d mat_emission = Vector3d(0, 0, 0);
    double    mat_shininess = 128;

    Vector3d transparency = Vector3d(0, 0, 0);  //how transparent this object is
    Vector3d reflectiveness = Vector3d(0, 0, 0); //how mirrow like this object is
    double refractive_index = 1;

    for (list<string>::iterator i = tok.begin(); i != tok.end(); i++){
        string& t = *i;
        if (t[0] == '#') break;
        if (t == "tx") m_pos[0] = (float)atof((++i)->c_str());
        else if (t == "ty") m_pos[1] = (float)atof((++i)->c_str());
        else if (t == "tz") m_pos[2] = (float)atof((++i)->c_str());
        else if (t == "r") radius = (float)atof((++i)->c_str());
        //difuss color
        else if (t == "cr") mat_color[0] = (float)atof((++i)->c_str());
        else if (t == "cg") mat_color[1] = (float)atof((++i)->c_str());
        else if (t == "cb") mat_color[2] = (float)atof((++i)->c_str());
        //specular color
        else if (t == "sr") mat_specular[0] = (float)atof((++i)->c_str());
        else if (t == "sg") mat_specular[1] = (float)atof((++i)->c_str());
        else if (t == "sb") mat_specular[2] = (float)atof((++i)->c_str());
        //emission color
        else if (t == "er") mat_emission[0] = (float)atof((++i)->c_str());
        else if (t == "eg") mat_emission[1] = (float)atof((++i)->c_str());
        else if (t == "eb") mat_emission[2] = (float)atof((++i)->c_str());
        else if (t == "shininess") mat_shininess = (float)atof((++i)->c_str());
        //transparency
        else if (t == "Tr") transparency[0] = (float)atof((++i)->c_str());
        else if (t == "Tg") transparency[1] = (float)atof((++i)->c_str());
        else if (t == "Tb") transparency[2] = (float)atof((++i)->c_str());
        //relectiveness (how mirrow-like this object is)
        else if (t == "Rr") reflectiveness[0] = (float)atof((++i)->c_str());
        else if (t == "Rg") reflectiveness[1] = (float)atof((++i)->c_str());
        else if (t == "Rb") reflectiveness[2] = (float)atof((++i)->c_str());
        //refractive index
        else if (t == "RI") refractive_index = (float)atof((++i)->c_str());
    }

    delete[] tmp;

    sphere * m = new sphere();
    assert(m);
    m->center = m_pos;
    m->radius = radius;
    m->mat_color = mat_color;
    m->mat_specular = mat_specular;
    m->mat_emission = mat_emission;
    m->mat_shininess = (float)mat_shininess;
    m->transparency = transparency;
    m->reflectiveness = reflectiveness;
    m->refractive_index = (float)refractive_index;

    models.push_back(m);

    return true;
}

inline bool initCamera(istream& in)
{
    const int size = 1024;
    char * tmp = new char[size];
    in.getline(tmp, size);//read lines from a file into strings
    list<string> tok = tokenize(tmp, " =\t[]()<>,");
    if (tok.size() == 0) return false;			//empty line
    if (tok.front()[0] == '#') return false; 	//commented out

    for (list<string>::iterator i = tok.begin(); i != tok.end(); i++){
        string& t = *i;
        if (t[0] == '#') break;
        if (t == "type")
        {
            string type = *(++i);
            camera.type = (type == "orthogonal") ? Camera::ORTHOGONAL_CAM : Camera::PERSPECTIVE_CAM;
        }
        else if (t == "tx") camera.pos[0] = (float)atof((++i)->c_str());
        else if (t == "ty") camera.pos[1] = (float)atof((++i)->c_str());
        else if (t == "tz") camera.pos[2] = (float)atof((++i)->c_str());
        else if (t == "lookatx") camera.lookat[0] = (float)atof((++i)->c_str());
        else if (t == "lookaty") camera.lookat[1] = (float)atof((++i)->c_str());
        else if (t == "lookatz") camera.lookat[2] = (float)atof((++i)->c_str());

        //up vector
        else if (t == "upx") camera.up[0] = (float)atof((++i)->c_str());
        else if (t == "upy") camera.up[1] = (float)atof((++i)->c_str());
        else if (t == "upz") camera.up[2] = (float)atof((++i)->c_str());

        //view port
        else if (t == "viewx") camera.viewport[0] = atoi((++i)->c_str());
        else if (t == "viewy") camera.viewport[1] = atoi((++i)->c_str());
        else if (t == "vieww") camera.viewport[2] = atoi((++i)->c_str());
        else if (t == "viewh") camera.viewport[3] = atoi((++i)->c_str());

        //near/far plane
        else if (t == "near") camera.near = (float)atof((++i)->c_str());
        else if (t == "far")  camera.far = (float)atof((++i)->c_str());
    }

    //init after all info gathered
    camera.init();

    delete[] tmp;

    return true;
}

inline bool initLights(istream& in)
{
	const int size = 1024;
	char * tmp = new char[size];
	in.getline(tmp, size);//read lines from a file into strings
	list<string> tok = tokenize(tmp, " =\t[]()<>,");
	if (tok.size() == 0) return false;			//empty line
	if (tok.front()[0] == '#') return false; 	//commented out

	for (list<string>::iterator i = tok.begin(); i != tok.end(); i++)
	{
		string& t = *i;
		if (t[0] == '#') break;

		if (t == "ar") ambient_light[0] = (float)atof((++i)->c_str());
		else if (t == "ag") ambient_light[1] = (float)atof((++i)->c_str());
		else if (t == "ab") ambient_light[2] = (float)atof((++i)->c_str());
		else if (t == "sample_size") light_sample_size = (unsigned int)atoi((++i)->c_str());
	}

	delete[] tmp;

	return true;
}



inline bool initialize(istream& in)
{
    const int size = 1024;
    char * tmp = new char[size];

    while (!in.eof())
    {
        in.getline(tmp, size);
        list<string> tok = tokenize(tmp, " \t\r[]()<>,=");
        if (tok.empty()) continue;

        string label = tok.front();
        tok.pop_front();
        if (label[0] == '#') continue;
        
        //initialize image
        if (label == "image" || label == "Image" || label == "IMAGE")
        {
            findImageSize(in);
        }//end if image

		//initialize light properties
		else if (label == "light" || label == "Light" || label == "LIGHT")
		{
			initLights(in);
		}//end if obst

        //initialize camera
        else if (label == "camera" || label == "Camera" || label == "CAMERA")
        {
            initCamera(in);
        }//end if camera

        //initialize meshes
        else if (label == "model" || label == "Model" || label == "MODEL")
        {
            int m_size = atoi(tok.front().c_str());
            for (int i = 0; i<m_size; i++)
            {
                createModel(in);
            }//end outer for
        }//end if meshes


        //initialize sphere
        else if (label == "sphere" || label == "Sphere" || label == "SPHERE")
        {
            int sphere_size = atoi(tok.front().c_str());
            for (int i = 0; i<sphere_size; i++)
            {
                createSphere(in);
            }//end outer for
        }//end if sphere

        //Unknown
        else{
            cerr << "! Warning: Unknown value: (" << tmp <<")"<< endl;
            continue;
        }
    }

    delete[] tmp;

    return true;
}

inline bool initialize(const string& filename)
{
    ifstream fin(filename.c_str());

    if (fin.good() == false) return false;

    if (initialize(fin) == false) return false;

    fin.close();

    computeCOM_R();

    return true;
}



