
#include "maths_defs.h"
#include "Matrix3.h"
#include "Vector3.h"
#include "Structures.h"
#include "GraphicalObject.h"

#include "Png.h"
#include "Cubemap.h"
#include "Xml.h"


#include <iostream>
#include <fstream>
#include <vector>
#include <list>
#include <string>
#include <cmath>

#include <cstdio>
#include <ctime>
#include <sys/time.h>
#include <unistd.h>


using namespace std;

static unsigned w = 800;
static unsigned h = 800;
static unsigned max_level=11;
static real     gamma = 0.5;
static real     exposure = 1.0;
static bool     phong = false;
static unsigned long iterations = 0;

static Vector3  camera(w/2,h/2,-10000);
static Color    fond(0.0,0.0,0.0);

class Scene
{
protected:
  string   nomScene;
  unsigned nb;
  unsigned nLight;
  unsigned nbMaterial;

  list<GraphicalObject *>   graphicalobject;
  vector<Noeud *>           noeudXml;
  list<Light *>             light;
  vector<Material *>        material;
  

public:
  Cubemap          cube;

  Scene(): nb(0),nLight(0),nbMaterial(0)  {};

  bool  Init(const string&);

  Color traceRay(Ray& ,const Color& , const real& _coef);

  void  Render();

  // Obtenir la taille des éléments
  //
  inline
  unsigned GetNbObjects() const
  {
     return graphicalobject.size();
  }
  inline
  unsigned GetNbLights() const
  {
    return light.size();
  }
  inline
  unsigned GetNbMaterials() const
  {
    return material.size();
  }

  // Obtenir chaque élément
  //
  Material* GetMaterial(const unsigned _n) const
  {
    if (_n > material.size()-1)
        return 0;
    return const_cast<Material*>(material[_n]);
  }

  void DeleteXML()
  {
    for (unsigned i=0;i<noeudXml.size();i++)
      delete  noeudXml[i];
  }

  ~Scene()
  {
    for (list<GraphicalObject*>::iterator i=graphicalobject.begin();i!=graphicalobject.end();++i)
        delete *i;
    for (list<Light*>::iterator i=light.begin();i!=light.end();++i)
        delete  *i;
    for (unsigned i=0;i<nbMaterial;i++)
        delete  material[i];
  }
};

Scene            scene;
Noeud           *n;

inline
png_byte Conv(const real& num_)
{
  real num = num_ * 255;
  if (num <= 0.0)
     return static_cast<png_byte>(0);
  else if (num >= 255.0)
     return static_cast<png_byte>(255);

  return static_cast<png_byte>(num);
}

inline
png_byte min(const real& a_, const real& b_)
{
  png_byte a = Conv(a_);
  png_byte b = Conv(b_);
  return ((a<b)?(a):(b));
}

inline
real max(const real& a, const real& b)
{
  return ((a>b)?(a):(b));
}

bool Scene::Init(const string& _nom)
{
  if (_nom.find("xml") == string::npos)
  {
     cout << "Entree: fichier XML" << endl;
     return false;
  }
  //
  nomScene = _nom;
  //                     v- verbose=f  validation=t  printInfo=f
  Xml fichierXml(nomScene);
  if (fichierXml.documentValid() == false)
  {
    cout << "Document XML non valide" << endl;
    return false;
  }
  fichierXml.makeScene(n,noeudXml);


  // on va convertir pour avoir notre graphicalobject
  for (vector<Noeud*>::const_iterator iter=noeudXml.begin(); iter!=noeudXml.end(); ++iter)
  {
    switch((*iter)->type())
    {
      case MATERIAL:{
           Material *m = reinterpret_cast<Material *>(*iter);
           material.push_back(new Material(m->col,m->ref,m->dif,m->id,m->den));
      }
      break;

      case LIGHT: {
           Light *l = reinterpret_cast<Light *>(*iter);
           light.push_back(new Light(l->pos,l->col));
      }
      break;

      case SCREEN:
           w  = (reinterpret_cast<Screen *>((*iter)))->width;
           h =  (reinterpret_cast<Screen *>((*iter)))->height;
           fond = (reinterpret_cast<Screen *>((*iter)))->fond;
      break;

      case CAMERA:
           camera  = (reinterpret_cast<Camera *>((*iter)))->pos;
      break;

      case CONF:
           max_level  = (reinterpret_cast<Conf *>((*iter)))->max_level;
           gamma      = (reinterpret_cast<Conf *>((*iter)))->gamma;
           exposure   = (reinterpret_cast<Conf *>((*iter)))->exposure;
           phong      = (reinterpret_cast<Conf *>((*iter)))->phong;
      break;

      case SPHERE:{
           Sphere *s = reinterpret_cast<Sphere *>(*iter);
           graphicalobject.push_back(new GraphicalSphere(s->pos,s->idMat,s->r));
      }
      break;

      case PLANE:{
           Plane *p = reinterpret_cast<Plane *>(*iter);
           graphicalobject.push_back(new GraphicalPlane(p->n,p->pos,p->idMat));
      }
      break;
      
      case TRIANGLE:{
           Triangle *t = reinterpret_cast<Triangle *>(*iter);
           graphicalobject.push_back(new GraphicalTriangle(t->p1,t->p2,t->p3,t->idMat));
      }
      break;

      default:
      break;
    }
  }

  // On libère un peu de mémoire, de toute facon, on s'en fou
  // maintenant de ca !
  DeleteXML();

  nb          = graphicalobject.size();
  nbMaterial  = material.size();
  nLight      = light.size();
  //
  //-------------------------------
  // Affichage des informations
  //-------------------------------
  cout << "Affichages des informations sur la scene" << endl;
  cout << nb << " objets" << endl;
  cout << nLight << " lumières" << endl;
  cout << nbMaterial << " matériaux" << endl;
  cout << "Materials: " << endl;
  for (unsigned i=0;i<nbMaterial;i++){
     cout << "\t";
     material[i]->affiche();
  }

  cout << "Objets:" << endl;
  for (list<GraphicalObject*>::const_iterator   iter =graphicalobject.begin();
                                                iter!=graphicalobject.end()  ;
                                              ++iter){
     cout << "\t";
     (*iter)->affiche();
  }

  cout << "Lumières:" << endl;
  for (list<Light*>::const_iterator   iter =light.begin();
                                      iter!=light.end()  ;
                                    ++iter){
     cout << "\t";
     (*iter)->affiche();
  }
  

  cout << "Init du cubemap" << endl;
  // Initialisation du cubemap
  bool initOK = cube.Init("./map-bois.png");
  if (!initOK)
  {
    cout << "Erreur de chargement du fichier PNG pour le cubemap" << endl;
    return false;
  }

  return true;
}


//---------------------------------------
// Lancer un rayon dans toute notre
// scène
//---------------------------------------
Color Scene::traceRay(Ray& viewRay,const Color& color, const real& _coef)
{
  real red = color.x, green = color.y, blue = color.z;
  GraphicalObject *prim = 0;
  real coef = _coef;
  unsigned level=0;

  do
  {
    ++iterations;
    prim = 0;

    // distance parcourue est le double..
    real dist = 20000.0;
    // calcul de l'intersection la plus proche
    //for (unsigned i=0;i<GetNbObjects();i++)
    for (list<GraphicalObject *>::const_iterator   iter =graphicalobject.begin();
                                                   iter!=graphicalobject.end()  ;
                                                 ++iter)
    {
      GraphicalObject *pr = *iter;
      // A-t'on une intersection entre notre objet i et
      // notre rayon viewRay ?
      if (pr->Intersect(viewRay, dist))
        prim = pr;
    }
    // si on ne trouve pas d'intersection, on sort de la boucle
    if (!prim)
    {
       Color c = cube.Read(viewRay);
       red   += (coef) * c.x;
       green += (coef) * c.y;
       blue  += (coef) * c.z;
       break;
    }
    // On repart de l'objet sachant qu'on a parcouru *dist*
    Point newStart  = viewRay.origin + dist * viewRay.direction;
    // calcule de la normale au point d'intersection
    Vector3 vNormal = newStart - prim->GetPoint();
    // On normalise n..
    real norme = vNormal.sqrNorm();
    //cout << norme << endl;
    if (norme  == 0.0)
       continue;
    vNormal *= InvSqrt(norme);

    // On choppe le material de l'objet ciblé
    Material *currentMat = GetMaterial(prim->GetMaterial()-1);
    //else
    //  cout << prim->GetMaterial() << ',';
    // Calcul de l'éclairement d'un point
    //for (unsigned j = 0; j < GetNbLights() ; j++)
    for (list<Light *>::const_iterator jter=light.begin();jter!=light.end();++jter)
    {
      Light *current  = *jter;
      Vector3 lightPosDist = current->pos - newStart;

      // On ne n'insteresse pas à ce qu'il y a derriere
      if ((vNormal * lightPosDist) <= 0.0)
       continue;

      dist = lightPosDist.sqrNorm();
      if (dist == 0.0)
         continue;
      lightPosDist *= InvSqrt(dist);

      //            origine - direction
      Ray lightRay(newStart,lightPosDist);

      // calcul des ombres
      dist = Sqrt(dist);
      bool pxlVisible = true;
      //for (unsigned i = 0; i < GetNbObjects() ; i++)
      for (list<GraphicalObject *>::const_iterator   iter =graphicalobject.begin();
                                                     iter!=graphicalobject.end()  ;
                                                   ++iter)
      {
         GraphicalObject *tr = *iter;
         if (tr->Intersect(lightRay, dist))
         {
           pxlVisible = false;
           break;
         }
      }

      if (pxlVisible)
      {
         // obtention du cosinus par le produit scalaire
         real lambert = (lightRay.direction * vNormal) * coef;
         //       coef_lambert * couleur lumière * couleur material
         red   += (lambert) * current->col.x * currentMat->col.x;
         green += (lambert) * current->col.y * currentMat->col.y;
         blue  += (lambert) * current->col.z * currentMat->col.z;

         if (phong)
         {
           Vector3 phongDir = (lightRay.direction - 2.0 * (vNormal.Norm() * viewRay.direction));
           real phongReal = max(phongDir * viewRay.direction, 0.0) ;
           phongReal = currentMat->dif * pow(phongReal, coef) * coef;
           red   += phongReal * current->col.x;
           green += phongReal * current->col.y;
           blue  += phongReal * current->col.z;
         }
      }
    }

    real reflet = (viewRay.direction * vNormal) * 2.0;
    // on itére sur la prochaine reflexion
    coef *= currentMat->ref;
    viewRay.origin = newStart;
    viewRay.direction = viewRay.direction - (reflet * vNormal);
  }
  while ((coef > 0.0) && (++level < max_level));

  return Color(red,green,blue);
}


//---------------------------------------
// Desiner toute la scène
//---------------------------------------
void Scene::Render()
{
  int result;
  const int bytes_per_pixel = 3;

  // scene générale
  png_byte **Dest;//[h][w*3];
  Dest = new png_byte* [h];
  for (unsigned i=0;i<h;i++)
      Dest[i] = new png_byte [w*bytes_per_pixel];

  cout << "Scene -- " << nomScene.c_str() << endl;
  cout << "Calcul..." << endl;
  double d1,d2;
  d1 = clock();

  const unsigned startcY = static_cast<unsigned>(h/2 - camera.y);
  const unsigned   endcY = static_cast<unsigned>(h/2 + camera.y);
  const unsigned startcX = static_cast<unsigned>(w/2 - camera.x);
  const unsigned   endcX = static_cast<unsigned>(w/2 + camera.x);

  for (unsigned y = startcY; y < endcY; y++)
  {
     for (unsigned x = startcX ; x < endcX; x++)
     {
        real  red = fond.x, green = fond.y, blue = fond.z;
        Color color(0,0,0);
        // supersampling
        // on sur échantillonge le truc, ca nous fait une espece de moyenne
        // pour les coordonnées entourantes !
     		for(real fragmentx = x; fragmentx < x + 1.0; fragmentx += 0.5)
  			for(real fragmenty = y; fragmenty < y + 1.0; fragmenty += 0.5)
  			{
          real coef = 0.25;
          // lancer de rayon
          Ray viewRay(Vector3(fragmentx,fragmenty,camera.z),Vector3(0.0, 0.0, 1.0));
          //
          color = traceRay(viewRay,color, coef);
          //
          red   = color.x;
          green = color.y;
          blue  = color.z;
          //
          if (exposure > 0.0)
          {
    				red  += coef * (1.0 - exp(red   * exposure));
    				green+= coef * (1.0 - exp(green * exposure));
    				blue += coef * (1.0 - exp(blue  * exposure));
          }
        }

        if (gamma != 1.0)
        {
          red   = static_cast<real>(pow(red  ,gamma));
          green = static_cast<real>(pow(green,gamma));
          blue  = static_cast<real>(pow(blue ,gamma));
        }
        Dest[y][x*bytes_per_pixel+0] = min(red    ,1.0); // red
        Dest[y][x*bytes_per_pixel+1] = min(green  ,1.0); // green
        Dest[y][x*bytes_per_pixel+2] = min(blue   ,1.0); // blue
     }
  }

  d2 = clock();
  cout << endl;
  double total = d2-d1;
  if (total < 1000)
    cout << "Calcul terminé en " << total << "ms ";
  else
    cout << "Calcul terminé en " << total/1000 << "s ";

  cout << "en " << iterations << " iterations" << endl;

  cout << "Conversion de l'image en png" << endl;

  char nom[256];
  sprintf(nom,"./rendu/ray-tmp.png",time(NULL));

  PNGImage image(nom, w, h);
  image.openFileWrite();
  image.initWriteStructs();
  image.writeHeader();
  png_byte **Buff = image.writeImage_Start();

  for (unsigned y=0;y<h;y++)
      for (unsigned x=0;x<w;x++)
      {
        Buff[y][x*bytes_per_pixel+0] = Dest[y][x*bytes_per_pixel+0];
        Buff[y][x*bytes_per_pixel+1] = Dest[y][x*bytes_per_pixel+1];
        Buff[y][x*bytes_per_pixel+2] = Dest[y][x*bytes_per_pixel+2];
      }

  image.writeImage_End();
  image.writeEnd();

  for (int i=0;i<h;i++)
      delete [] Dest[i];

}

//---------------------------------------
// Fonction principale du programme
//---------------------------------------
int main(int argc, char *argv[], char **env)
{
 cout << "\t---> Raytracer" << endl;
 string argument = "scene.xml";

 if (scene.Init(argument))
 {
  cout << "Init okay !" << endl;
  scene.Render();
 }
 else
 {
   cout << "Init échouée !" << endl;
   return 0;
 }
 return 1;
}




/*
 Anciennes versions

 

void Scene::Init()
{
    // construire la scene que l'on veut
    ifstream in("scene.conf");
        in >> w >> h;
        in >> max_level;
        in >> gamma >> exposure;
        cout << "Image: " << w << 'x' << h << endl;
        cout << "profondeur=" << max_level << endl;
        cout << "gamma=" << gamma << endl;
        cout << "duree d'exposition=" << exposure << endl;
        in >> nb >> nbMaterial >> nLight;
  
        // chargement des material
        graphicalobject = new GraphicalObject*  [nb+1];
  
        m = new Material [nbMaterial];
        l = new Light        [nLight];
  
           cout << "Materials: " << endl;
           for (unsigned i=0;i<nbMaterial;i++)
           {
            real a=0,b=0,c=0,d=0,e=0,f=0;
            in >> a >> b >> c >> d >> e;
             cout << a << ',' << b << ',' << c << ",ref=" << d << ',' << e << endl;
             m[i] = Material(Color(a,b,c),d,e);
           }
           int id;
           cout << "Objets:" << endl;
           for (unsigned i=0;i<nb;i++)
           {
             real a=0,b=0,c=0,d=0,e=0,f=0;
             in >> a >> b >> c >> d >> id;
             cout << a << ',' << b << ',' << c << ',' << d << ',' << id << endl;
             graphicalobject[i] = new GraphicalSphere(Vector3(a,b,c),m[id],d);
             id = i;
           }
           cout << "Lumières:" << endl;
           for (unsigned i=0;i<nLight;i++)
           {
            real a=0,b=0,c=0,d=0,e=0,f=0;
             in >> a >> b >> c >> d >> e >> f;
             cout << '(' << a << ',' << b << ',' << c << ')';
             cout << '(' << d << ',' << e << ',' << f << ')' << endl;
             l[i] = Light(Vector3(a,b,c),Color(d,e,f));
          	 light.push_back(&(l[i]));
           }
    in.close();
    ++nb;
    graphicalobject[nb] = new GraphicalPlane(Vector3(0.707,0.707,0),Vector3(300,300,0),m[0]);

    cout << "Affichages des informations sur la scene" << endl;
    cout << nb << " objets (sphères)" << endl;
    cout << nLight << " lumières" << endl;
    cout << nbMaterial << " matériaux" << endl;
}


*/
