#include "radiosity.h"
#include "mesh.h"
#include "face.h"
#include <GL/gl.h>
#include "glCanvas.h"

// ================================================================
// CONSTRUCTOR & DESTRUCTOR
// ================================================================
Radiosity::Radiosity(Mesh *m, ArgParser *a) {
  mesh = m;
  args = a;
  Initialize();
}

Radiosity::~Radiosity() {
  Cleanup();
}

void Radiosity::Reset() {
  printf ("Reset Radiosity Solution\n");
  Cleanup();
  Initialize();
}

void Radiosity::Initialize() {
  // create and fill the data structures
  n = mesh->numFaces();
  patches = new Face*[n];
  formfactors = new float[n*n];
  area = new float[n];
  undistributed = new Vec3f[n];
  absorbed = new Vec3f[n];
  radiance = new Vec3f[n];

  // number the patches, store them in an array (since the bag is unordered)
  Iterator<Face*> *iter = mesh->getFaces()->StartIteration();
  int i = 0;
  while (Face *f = iter->GetNext()) {
    patches[i] = f;
    f->setRadiosityPatchIndex(i);
    setArea(i,patches[i]->getArea());
    i++;
  }
  mesh->getFaces()->EndIteration(iter);

  // compute the form factors and initialize the energies
  for (i = 0; i < n; i++) {
    computeFormFactors(i);
    Vec3f emit = getPatch(i)->getEmit();
    setUndistributed(i,emit);
    setAbsorbed(i,Vec3f(0,0,0));
    setRadiance(i,emit);
  }

  // find the patch with the most undistributed energy
  findMaxUndistributed();
}

void Radiosity::Cleanup() {
  delete [] patches;
  delete [] formfactors;
  delete [] area;
  delete [] undistributed;
  delete [] absorbed;
  delete [] radiance;
}

// ================================================================
// ================================================================

void Radiosity::findMaxUndistributed() {
  // find the patch with the most undistributed energy 
  // don't forget that the patches may have different sizes!
  max_undistributed_patch = -1;
  total_undistributed = 0;
  total_area = 0;
  float max = -1;
  for (int i = 0; i < n; i++) {
    float m = getUndistributed(i).Length() * getArea(i);
    total_undistributed += m;
    total_area += getArea(i);
    if (max < m) {
      max = m;
      max_undistributed_patch = i;
    }
  }
  assert (max_undistributed_patch >= 0 && max_undistributed_patch < n);
}

// =======================================================================
// =======================================================================

bool Radiosity::Iterate() {

  printf ("Iterate\n");

  // compute the radiosity solution!
  
  // return true when the solution error is < epsilon
  // so the animation loop can stop

  return false;
}


void Radiosity::computeFormFactors(int i) {

  // compute the form factors

  // if your method for determining the form factors is only an
  // estimate, you may need to normalize something so you don't
  // gain/lose energy in the system

}

// =======================================================================
// PAINT
// =======================================================================

Vec3f ComputeNormal(const Vec3f &p1, const Vec3f &p2, const Vec3f &p3) {
  Vec3f v12 = p2;
  v12 -= p1;
  Vec3f v23 = p3;
  v23 -= p2;
  Vec3f normal;
  Vec3f::Cross3(normal,v12,v23);
  normal.Normalize();
  return normal;
}

Vec3f ComputeNormal(Face *f) {
  return ComputeNormal((*f)[0]->get(),(*f)[1]->get(),(*f)[2]->get());
}

inline float tone_func(float x) {
  assert (x >= 0);
  // a tone mapping hack to map [0,oo) -> [0,1]
  // there's nothing special or physically based about this function
  
  float answer = -exp(-3*x)+1; 
  assert (answer >= 0 && answer <= 1);
  return answer;
}

void Radiosity::insertColor(Vec3f v) {
  if (args->tone_map) {
    float r = tone_func(v.x());
    float g = tone_func(v.y());
    float b = tone_func(v.z());
    glColor3f(r,g,b);
  } else {
    glColor3f(v.x(),v.y(),v.z());
  }
}

void insertNormal(const Vec3f &p1, const Vec3f &p2, const Vec3f &p3) {
  Vec3f normal = ComputeNormal(p1,p2,p3);
  glNormal3f(normal.x(), normal.y(), normal.z());
}

Vec3f Radiosity::whichVisualization(enum RENDER_MODE mode, Face *f, int i) {
  assert (getPatch(i) == f);
  assert (i >= 0 && i < n);
  if (mode == RENDER_LIGHTS) {
    return f->getEmit();
  } else if (mode == RENDER_UNDISTRIBUTED) { 
    return getUndistributed(i);
  } else if (mode == RENDER_ABSORBED) {
    return getAbsorbed(i);
  } else if (mode == RENDER_RADIANCE) {
    return getRadiance(i);
  } else if (mode == RENDER_FORM_FACTORS) {
    float scale = 0.2 * total_area/getArea(i);
    float factor = scale * getFormFactor(max_undistributed_patch,i);
    return Vec3f(factor,factor,factor);
  } else {
    assert(0);
  }
}

void CollectFacesWithVertex(Vertex *have, Face *f, Array<Face*> *array) {
  if (array->Member(f)) return;
  if (have != (*f)[0] && have != (*f)[1] && have != (*f)[2] && have != (*f)[3]) return;
  array->Add(f);
  for (int i = 0; i < 4; i++) {
    Edge *ea = f->getEdge()->getOpposite();
    Edge *eb = f->getEdge()->getNext()->getOpposite();
    Edge *ec = f->getEdge()->getNext()->getNext()->getOpposite();
    Edge *ed = f->getEdge()->getNext()->getNext()->getNext()->getOpposite();
    if (ea != NULL) CollectFacesWithVertex(have,ea->getFace(),array);
    if (eb != NULL) CollectFacesWithVertex(have,eb->getFace(),array);
    if (ec != NULL) CollectFacesWithVertex(have,ec->getFace(),array);
    if (ed != NULL) CollectFacesWithVertex(have,ed->getFace(),array);
  }
}

void Radiosity::insertInterpolatedColor(int index, Face *f, Vertex *v) {
  Array<Face*> faces = Array<Face*>(10);
  CollectFacesWithVertex(v,f,&faces);
  float total = 0;
  Vec3f color = Vec3f(0,0,0);
  Vec3f normal = ComputeNormal(f);
  for (int i = 0; i < faces.Count(); i++) {
    Vec3f normal2 = ComputeNormal(faces[i]);
    if (normal.Dot3(normal2) < 0.9) continue;
    total += faces[i]->getArea();
    color += faces[i]->getArea() * whichVisualization(RENDER_RADIANCE,faces[i],faces[i]->getRadiosityPatchIndex());
  }
  assert (total > 0);
  color /= total;
  insertColor(color);
}

void Radiosity::Paint(ArgParser *args) {

  // scale it so it fits in the window
  Vec3f center; mesh->getBoundingBox()->getCenter(center);
  float s = 1/mesh->getBoundingBox()->maxDim();
  glScalef(s,s,s);
  glTranslatef(-center.x(),-center.y(),-center.z());

  // this offset prevents "z-fighting" bewteen the edges and faces
  // the edges will always win.
  glEnable(GL_POLYGON_OFFSET_FILL);
  glPolygonOffset(1.1,4.0);

  if (args->render_mode == RENDER_MATERIALS) {
    // draw the faces with OpenGL lighting, just to understand the geometry
    // (the GL light has nothing to do with the surfaces that emit light!)
    glBegin (GL_QUADS);
    for (int i = 0; i < n; i++) {
      Face *f = patches[i];
      Vec3f color = f->getColor();
      insertColor(color);
      Vec3f a = (*f)[0]->get();
      Vec3f b = (*f)[1]->get();
      Vec3f c = (*f)[2]->get();
      Vec3f d = (*f)[3]->get();
      insertNormal(a,b,c); 
      glVertex3f(a.x(),a.y(),a.z());
      glVertex3f(b.x(),b.y(),b.z());
      glVertex3f(c.x(),c.y(),c.z());
      glVertex3f(d.x(),d.y(),d.z());
    }
    glEnd();
  } else if (args->render_mode == RENDER_RADIANCE && args->interpolate == true) {
    // interpolate the radiance values with neighboring faces having the same normal
    glDisable(GL_LIGHTING);
    glBegin (GL_QUADS);
    for (int i = 0; i < n; i++) {
      Face *f = patches[i];
      Vec3f a = (*f)[0]->get();
      Vec3f b = (*f)[1]->get();
      Vec3f c = (*f)[2]->get();
      Vec3f d = (*f)[3]->get();
      insertInterpolatedColor(i,f,(*f)[0]);
      glVertex3f(a.x(),a.y(),a.z());
      insertInterpolatedColor(i,f,(*f)[1]);
      glVertex3f(b.x(),b.y(),b.z());
      insertInterpolatedColor(i,f,(*f)[2]);
      glVertex3f(c.x(),c.y(),c.z());
      insertInterpolatedColor(i,f,(*f)[3]);
      glVertex3f(d.x(),d.y(),d.z());
    }
    glEnd();
    glEnable(GL_LIGHTING);
  } else {
    // for all other visualizations, just render the patch in a uniform color
    glDisable(GL_LIGHTING);
    glBegin (GL_QUADS);
    for (int i = 0; i < n; i++) {
      Face *f = patches[i];
      Vec3f color = whichVisualization(args->render_mode,f,i);
      insertColor(color);
      Vec3f a = (*f)[0]->get();
      Vec3f b = (*f)[1]->get();
      Vec3f c = (*f)[2]->get();
      Vec3f d = (*f)[3]->get();
      glVertex3f(a.x(),a.y(),a.z());
      glVertex3f(b.x(),b.y(),b.z());
      glVertex3f(c.x(),c.y(),c.z());
      glVertex3f(d.x(),d.y(),d.z());
    }
    glEnd();
    glEnable(GL_LIGHTING);
  }

  if (args->render_mode == RENDER_FORM_FACTORS) {
    // render the form factors of the patch with the most undistributed light
    glDisable(GL_LIGHTING);
    glColor3f(1,0,0);
    Face *t = getPatch(max_undistributed_patch);
    glLineWidth(3);
    glBegin(GL_LINES);
    Vec3f a = (*t)[0]->get();
    Vec3f b = (*t)[1]->get();
    Vec3f c = (*t)[2]->get();
    Vec3f d = (*t)[3]->get();
    glVertex3f(a.x(),a.y(),a.z());
    glVertex3f(b.x(),b.y(),b.z());
    glVertex3f(b.x(),b.y(),b.z());
    glVertex3f(c.x(),c.y(),c.z());    
    glVertex3f(c.x(),c.y(),c.z());    
    glVertex3f(d.x(),d.y(),d.z());    
    glVertex3f(d.x(),d.y(),d.z());    
    glVertex3f(a.x(),a.y(),a.z());
    glEnd();
    glEnable(GL_LIGHTING);
  }

  glDisable(GL_POLYGON_OFFSET_FILL); 
  HandleGLError(); 
  
  if (args->wireframe) {
    mesh->PaintWireframe(); 
  }
}

// =======================================================================
// =======================================================================



