// Author: Juan Carlos De Abreu (jcabreur@gmail.com)

#include "scene.h"
#include "../Shadows/ShadowManagement.h"
#include "../Camera/Camera.h"

///Prueba //////////
#include "../GUI/window_management.h"
///////////

#include "scene_nodes.h"
#include "../cull/frustum_culling.h"
#include "../Base/LogManagement.h"
#include "../Math/Matrix.h"
#include "../Material/MaterialManagement.h"
#include "../Model/ModelManagement.h"
#include "../collision_detection/OBB.h"
#include "../collision_detection/aabb_tree_collision.h"
#include "../Scene Parser/SceneFile.h"
#include "../Scene Parser/SceneDriver.h"

BEGIN_TEG_NAMESPACE();

Scene::Scene() {
  // Set the file that contain the initial scene description.
  path_name_ = _T("Data/Scenes/initial.txt");
}

Scene::~Scene() {}

// Method to load the settings for the scene
void Scene::LoadSettings(const std::tstring& path) {
  // Try to open the file
  FILE *f = fopen(path.c_str(), "r");
  if (f == NULL) {
    PRINT_LOG(TYPE_ERROR, "The file that contain the settings for the scene not was find");
    return;
  }

  char str[100];		// To process each line in the file of the scene

  // 
  while (true) {
    fgets(str, sizeof(str), f);	
    str[strlen(str) - 1] = '\0';

    char lineType[100];	// TODO: sustituir todos los tamaņos por alguna variable global... colocarla en mi string
    sscanf(str, "%s", lineType);

    if (strcmp(lineType, "end") == 0) break;

    // If the scene will manage objects with blend
    if (strcmp(lineType, "sceneBlend") == 0) scene_blend_ = true;
    if (strcmp(lineType, "sceneDontBlend") == 0) scene_blend_ = false;

    // All the Shadows in the scene ...
    if (strcmp(lineType, "sceneCastShadows") == 0) scene_cast_shadows_ = true;
    if (strcmp(lineType, "sceneDontCastShadows") == 0) scene_cast_shadows_ = false;
  }

  fclose(f);
}

// Method to load the description of the scene
void Scene::Load() {
  // Load the setinngs for the scene
  LoadSettings(_T("Data/Scenes/Settings.txt"));

  // Initialize the MaterialManagement.
  MaterialManagement::GetInstance().init();


  // Prueba para el SceneFileManagement //
  SceneFileManagement::GetInstance().add(path_name_);
  SceneFileManagement::GetInstance().init();
  //

  SceneDriver driver;
  driver.parse(path_name_);

  SceneFileManagement::GetInstance().apply();

  // Enable console if the initial file is empty or disable if it's not.
  gui::WindowManagement::GetInstance().set_is_console_enable(SceneFileManagement::GetInstance().sceneFiles.begin()->IsEmpty());

  // Initialize the shadows ...
  ShadowManagement::GetInstance().init();

  // Set the default camera.
  if (camera_ == NULL) camera_ = new Camera();
}

void Scene::Release() {
  root_ = NULL;
  camera_ = NULL;

  ModelManagement::GetInstance().release();
  ShadowManagement::GetInstance().release();
}

// Functor to sort the geometries from far to near. This functor is helpful 
// for tasks like draw the translucent geometries in the right way. 
struct FarToNear {
  bool operator () (const SmartPointer<GeometryNode>& g1,
                    const SmartPointer<GeometryNode>& g2) {
      return g1->dist() > g2->dist();
  }
};

void Scene::Render() {
  // Enable a default light.
  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);

  // Set the active camera.
  if (camera_) camera_->lookAt();

  // Store the actual modelView and projection matrix.
  Mat4f model_view, projection;
  glGetFloatv(GL_MODELVIEW_MATRIX, model_view.m);
  glGetFloatv(GL_PROJECTION_MATRIX, projection.m);

  // Get the actual scene description.
  //root_ = castToSceneNode(GET_ID(_T("scene")));

  // Update the model view matrix of each geometry.
  glPushMatrix();    
    if (root_ != NULL) root_->apply();
  glPopMatrix();

  // Sort the geometries in the scene to display the translucent geometries in
  // the right way.
  std::sort(scene_geometries_.begin(), scene_geometries_.end(), FarToNear());  

  // Enable all the lights active in the scene.
  glPushMatrix();
    glLoadIdentity();
    const int kNumberOfLights = static_cast<int> (scene_lights_.size());
    for (int i = 0; i < kNumberOfLights && i < GL_MAX_LIGHTS; ++i) {
      BEGIN_LIGHT(scene_lights_[i], i);
    }
  glPopMatrix();
  
  // Display all the opaque geometries in the scene description.
  FrustumCulling frustum;
  const int kNumberOfGeometries = static_cast<int> (scene_geometries_.size());
  for(int i = kNumberOfGeometries - 1; i >= 0; --i) {
    // Calculate the viewing frustum and check if the geometries is out of
	// this.
    frustum.CalculateFrustum(scene_geometries_[i]->model_view(), projection);
    if (frustum.IsBoxVisiable(Vec3f(0, 0, 0), 0.5))
      scene_geometries_[i]->render(Group::OPAQUE_ | Group::MATERIAL | Group::TEXTURE);
  }

  // Check if the scene will render the geometries shadows.
  if (scene_cast_shadows_) {
    // For all geometries in the scene cast it's shadow with each light in the
	// scene.
    glDisable(GL_LIGHTING);
      for (int i = 0; i < kNumberOfLights && i < GL_MAX_LIGHTS; ++i)
        ShadowManagement::GetInstance().render(scene_lights_[i],
		                                       scene_geometries_);
    glEnable(GL_LIGHTING);
  }

  // Check if the scene will render the translucent geometries as translucent.
  if (scene_blend_) {
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glEnable(GL_BLEND);
  }

  // Display all the translucent geometries in the scene description.
  for(int i = 0; i < kNumberOfGeometries; ++i) {
    // Calculate the viewing frustum and check if the geometries is out of
	// this.
    frustum.CalculateFrustum(scene_geometries_[i]->model_view(), projection);
    if (frustum.IsBoxVisiable(Vec3f(0, 0, 0), 0.5))
      scene_geometries_[i]->render(Group::TRANSLUCENT | Group::MATERIAL | Group::TEXTURE);
  }

  // Disable the render of translucent geometries.
  if (scene_blend_) glDisable(GL_BLEND);

  // Disable all the lights in the scene.
  for (int i = 0; i < kNumberOfLights && i < GL_MAX_LIGHTS; ++i) END_LIGHT(i);
  glDisable(GL_LIGHTING);

	
  ///// Test to render the geometries AABB ///
  //for(int i = 0; i < kNumberOfGeometries; ++i) {
  // // glColor3f(0,0,0);
  //  Vec4f bounding_box_material;
  //  if (scene_geometries_[i]->use_default_material())
  //    bounding_box_material = 
  //        scene_geometries_[i]->model()->groupList[0].mat[0]->diffuse;
  //  else
  //    bounding_box_material = 
  //        scene_geometries_[i]->custom_material()->diffuse;
  //  glColor3f(bounding_box_material.x,
	 //         bounding_box_material.y,
  //            bounding_box_material.z);
  //  glLoadMatrixf(scene_geometries_[i]->model_view().m);
  //  scene_geometries_[i]->model()->bb.Render(5);
  //}
  ////////////////////////////////////////////

  // Restore the modelview matrix.
  glLoadMatrixf(model_view.m);

  // Initialize the geometries and lights in the scene description for the next
  // render call.
  scene_geometries_.clear();
  scene_lights_.clear();
}

END_TEG_NAMESPACE();
