// __author__ = Juan Carlos De Abreu (jcabreur@gmail.com)

#include "DataSystemFunctions.h"

#include "../Scene/scene_nodes.h"
#include "../Model/ModelManagement.h"
#include "../Util/Time/TimeManagement.h"

#include "DataTypes.h"
#include "DataStructures.h"
#include "../app.h"

#include "../input/device/isotrak/isotrak.h"

#include "../Math/Random/Random.h"

#include "../collision_detection/aabb_tree_collision.h"

#include "../Material/MaterialManagement.h"

#include "Checks.h"

#include "SceneFile.h"
#include "../Camera/Camera.h"

#include "../Scene Parser/SceneDriver.h"

BEGIN_TEG_NAMESPACE();


SmartPointer<IData> model(std::vector<SmartPointer<IData>> params) {
  if (!CHECK_PARAMETERS(1, params.size())) return NULL;
  SmartPointer<DataString> tmp = castToString(params[0]);
  if (!CHECK_TYPE(tmp, 1, _T("string"))) return NULL;
	
  // Obtain the file that is proccesing.
  const int prevProcessFile = SceneFileManagement::GetInstance().proccessingFile;
  // Obtain the path of the file.
  std::tstring path = GetAPathFromOne(SceneFileManagement::GetInstance().pathFiles[prevProcessFile], tmp->value);

  const SmartPointer<IModel>& model = LOAD_MODEL(path.c_str());
  if (!model) return NULL;
  SmartPointer<IData> geometry = new DataSceneNode(new GeometryNode(model, true, true));
  return geometry;
}

SmartPointer<IData> Light(std::vector<SmartPointer<IData>> params) {
  if (!CHECK_PARAMETERS(0, params.size())) return NULL;
    SmartPointer<IData> tmp = new DataSceneNode(new LightNode());
  return tmp;
}

SmartPointer<IData> group(std::vector<SmartPointer<IData>> params) {
  const int nParams = static_cast<int> (params.size());
  std::vector<SmartPointer<ISceneNode>> paramTmp;
  for (int i = 0; i < nParams; ++i) {
    const SmartPointer<ISceneNode> tmp = castToSceneNode(params[i]);
    if (tmp == NULL) return NULL;
    paramTmp.push_back(tmp);
  }
  return new DataSceneNode(new GroupNode(paramTmp));
}

SmartPointer<IData> rotate(std::vector<SmartPointer<IData>> params) {
  // Check if the number of parameters is equal to the neeed for the function
  if (!CHECK_PARAMETERS(4, params.size())) return NULL;

  return new DataSceneNode(new RotateNode(
      castToFloat(params[0])->value,
      castToFloat(params[1])->value,
      castToFloat(params[2])->value,
      castToFloat(params[3])->value));
}

SmartPointer<IData> scale(std::vector<SmartPointer<IData>> params) {
  // Check if the number of parameters is equal to the neeed for the function
  if (!CHECK_PARAMETERS(3, params.size())) return NULL;

  return new DataSceneNode(new ScaleNode(
      castToFloat(params[0])->value,
      castToFloat(params[1])->value,
      castToFloat(params[2])->value));
}

// Function for manage the situation where the call functions is translate
SmartPointer<IData> translate(std::vector<SmartPointer<IData>> params) {
  // Check if the number of parameters is equal to the neeed for the function
  if (!CHECK_PARAMETERS(3, params.size())) return NULL;

  return new DataSceneNode(new TranslateNode(
      castToFloat(params[0])->value,
      castToFloat(params[1])->value,
      castToFloat(params[2])->value));
}

// Method to create a camera.
SmartPointer<IData> camera(std::vector<SmartPointer<IData>> params) {
  const int nParams = static_cast<int> (params.size());
  if (!CHECK_PARAMETERS(0, nParams)) return NULL;
  return new DataCamera(new Camera());
}

SmartPointer<IData> timer(std::vector<SmartPointer<IData>> params) {
  if (!CHECK_PARAMETERS(4, params.size())) return NULL;
  SmartPointer<DataTime> tmp = new DataTime(params);
  ADD_TIMER(tmp);
  return castToDATA(tmp);
}

// System function to stop all the timers in the app.
SmartPointer<IData> stopAllTimers(std::vector<SmartPointer<IData>> params) {
  // Check that the number of parameters be equals to 0.
  if (!CHECK_PARAMETERS(0, params.size())) return NULL;
  TimeManagement::GetInstance().stopAllTimers();
  return NULL;
}

// System function to pause all the timers in the app.
SmartPointer<IData> pauseAllTimers(std::vector<SmartPointer<IData>> params) {
  if (!CHECK_PARAMETERS(0, params.size())) return NULL;
  TimeManagement::GetInstance().pauseAllTimers();
  return NULL;
}

// System function to start all the timers in the app.
SmartPointer<IData> startAllTimers(std::vector<SmartPointer<IData>> params) {
  // Check that the number of parameters be equals to 0.
  if (!CHECK_PARAMETERS(0, params.size())) return NULL;
  TimeManagement::GetInstance().startAllTimers();
  return NULL;
}

SmartPointer<IData> getMousePos(std::vector<SmartPointer<IData>> params) {
  const int nParams = static_cast<int> (params.size());
  if (!CHECK_PARAMETERS(0, nParams)) return NULL;
  SmartPointer<DataVector> pos = new DataVector();
  pos->value.push_back(newData(MOUSE_X));
  pos->value.push_back(newData(MOUSE_Y));
  return castToDATA(pos);
}

// Function to get all the functions defined by the system.
SmartPointer<IData> help(std::vector<SmartPointer<IData>> params) {
  const int nParams = static_cast<int> (params.size());
  if (!CHECK_PARAMETERS(0, nParams)) return NULL;
  
  // 
  std::map<std::string, FunctorGlobal1<SmartPointer<IData>, std::vector<SmartPointer<IData>>> >::iterator it = SceneFileManagement::GetInstance().DATAfunctions.begin();
  std::map<std::string, FunctorGlobal1<SmartPointer<IData>, std::vector<SmartPointer<IData>>> >::iterator end = SceneFileManagement::GetInstance().DATAfunctions.end();
	
  std::vector<std::tstring> functions; std::tstring s;
  for (; it != end; ++it) functions.push_back(it->first);
  sort(functions.begin(), functions.end());
  const int nFunctions = static_cast<int> (functions.size());
  for (int i = 0; i < nFunctions; ++i) s += functions[i] + _T(" ");
  PRINT_LOG(TYPE_MESSAGE, "%s", s.c_str());
  return NULL;
}

// Function to know the version of the app.
SmartPointer<IData> version(std::vector<SmartPointer<IData>> params) {
  PRINT_LOG(TYPE_MESSAGE, _T("BanFa Version 1.0 (%s UCV-LCG)"), __DATE__);
  return NULL;
}

// System function to set the a key function.
SmartPointer<IData> setKeyFunc(std::vector<SmartPointer<IData>> params) {
  if (!CHECK_PARAMETERS(2, params.size())) return NULL;

  int key = 0;
  if (params[0]->type == IData::DATA_STRING) {
    SmartPointer<DataString> tmp = castToString(params[0]);
    if (tmp->value == _T("UP")) key = VK_UP;
    else if (tmp->value == _T("DOWN")) key = VK_DOWN;
    else if (tmp->value == _T("LEFT")) key = VK_LEFT;
    else if (tmp->value == _T("RIGHT")) key = VK_RIGHT;
    else if (tmp->value[0] >= 'a' && tmp->value[0] <= 'z') key = tmp->value[0] - 'a' + 'A';
    else key = tmp->value[0];
  }
  else {
    SmartPointer<DataInt> tmp = castToInt(params[0]);
    key = tmp->value;
  }

  SmartPointer<DataCallFunction> func = castToDataCallFunction(params[1]);

  // Add the key function.
  DataKey::GetInstance().addKeyFunc(key, func);

  return NULL;
}

// System function to initialize the use of the raster.
SmartPointer<IData> InitializeRaster(std::vector< SmartPointer<IData> > params) {
  if (!CHECK_PARAMETERS(0, params.size())) return NULL;
  // Get the raster settings.
  FILE *raster_settings_file = fopen("raster_settings.txt", "r");
  if (raster_settings_file) {
    char port_name[100]; int rate;
    fscanf(raster_settings_file, "%s %d", port_name, &rate);
    fclose(raster_settings_file);
    Isotrak::GetInstance().Connect(port_name, rate);  
  } else {
    PRINT_LOG(TYPE_ERROR, "Can not open raster_settings.txt");
  }  
  return NULL;
}

// System function to set the raster button function.
SmartPointer<IData> SetRasterButtonFunc(std::vector< SmartPointer<IData> > params) {
  if (!CHECK_PARAMETERS(1, params.size())) return NULL;

  // Set the raster button function.
  SmartPointer<DataCallFunction> function = castToDataCallFunction(params[0]);
  DataRaster::GetInstance().set_button_function(function);
  return NULL;
}

SmartPointer<IData> IsRasterButtonPressed(std::vector< SmartPointer<IData> > params) {
  if (!CHECK_PARAMETERS(0, params.size())) return NULL;
  return newData(Isotrak::GetInstance().receiver(0).buttonPressed());
}

// System function to get the raster orientation.
SmartPointer<IData> GetRasterOrientation(std::vector< SmartPointer<IData> > params) {
  if (!CHECK_PARAMETERS(0, params.size())) return NULL;
  const Vec3f orientation = Isotrak::GetInstance().receiver(0).orientation();
  SmartPointer<DataVector> orientation_vector = new DataVector();
  orientation_vector->value.push_back(newData(orientation.x));
  orientation_vector->value.push_back(newData(orientation.y));
  orientation_vector->value.push_back(newData(orientation.z));
  return castToDATA(orientation_vector);
}

// System function to get the raster position.
SmartPointer<IData> GetRasterPosition(std::vector< SmartPointer<IData> > params) {
  if (!CHECK_PARAMETERS(0, params.size())) return NULL;
  const Vec3f position = Isotrak::GetInstance().receiver(0).position();
  SmartPointer<DataVector> position_vector = new DataVector();
  position_vector->value.push_back(newData(position.x));
  position_vector->value.push_back(newData(position.y));
  position_vector->value.push_back(newData(position.z));
  return castToDATA(position_vector);
}

SmartPointer<IData> include(std::vector<SmartPointer<IData>> params) {
  if (!CHECK_PARAMETERS(1, params.size())) return NULL;

  // Get the parameter
  SmartPointer<DataString> tmp = castToString(params[0]);

  // Verificar que sea un string.
  if (!CHECK_TYPE(tmp, 1, _T("string"))) return NULL;

  // Obtain the file that is proccesing.
  const int prevProcessFile = SceneFileManagement::GetInstance().proccessingFile;

  // Obtain the path of the file.
  std::tstring path = GetAPathFromOne(SceneFileManagement::GetInstance().pathFiles[prevProcessFile], tmp->value);

  // Check if the file is allready load.
  const int nFiles = static_cast<int> (SceneFileManagement::GetInstance().pathFiles.size());
  for (int i = 0; i < nFiles; ++i) {
    if(SceneFileManagement::GetInstance().pathFiles[i].empty()) continue;
    if (SceneFileManagement::GetInstance().pathFiles[i] == path) {
      PRINT_LOG(TYPE_MESSAGE, "\'%s\' : is already in memory", path.c_str());
      return NULL; // The file is allready in memory.
    }
  }

  if (!fileExist(path)) {
    PRINT_LOG(TYPE_ERROR, "error: Cannot open include file: \'%s\': No such file or directory.", tmp->value.c_str());
    return NULL;
  }

  SceneFileManagement::GetInstance().add(path);
  SceneDriver driver;
  driver.parse(path);

  // Set the file index that is proceessing.
  SceneFileManagement::GetInstance().proccessingFile = nFiles;

  // Process the global isnt in the file.
  std::list<SceneFile>::iterator it = SceneFileManagement::GetInstance().sceneFiles.begin();
  for (int i = 0; i < nFiles; ++i) ++it;
  it->apply();

  // Restore the file that was proccessing.
  SceneFileManagement::GetInstance().proccessingFile = prevProcessFile;

  return NULL;
}

// Function to load a material file.
SmartPointer<IData> material(std::vector<SmartPointer<IData>> params) {
  // Check that the number of parameters be equals to 1.
  if (!CHECK_PARAMETERS(1, params.size())) return NULL;

  // Get the parameter and check if is a string
  const SmartPointer<DataString> tmp = castToString(params[0]);
  if (!CHECK_TYPE(tmp, 1, _T("string"))) return NULL;

  // Obtain the path of the file and loaded.
  const int proccessingFile = SceneFileManagement::GetInstance().proccessingFile;
  const std::tstring path = GetAPathFromOne(SceneFileManagement::GetInstance().pathFiles[proccessingFile], tmp->value);
  LOAD_MATERIALS(path);
  return NULL;
}

// Function to get a material in the app.
//   The function return the material if is founded otherwise return the
//   default material for the app.
SmartPointer<IData> getMaterial(std::vector<SmartPointer<IData>> params) {
  // Check if the number and types of parameters are correct.
  if (!CHECK_PARAMETERS(1, params.size())) return NULL;
  const SmartPointer<DataString> materialName = castToString(params[0]);
  if (!CHECK_TYPE(materialName, 1, _T("string"))) return NULL;
  
  // Return the pointer to the material.
  const SmartPointer<DataMaterial> mat = new DataMaterial(MATERIAL(materialName->value));
  return castToDATA(mat);
}

SmartPointer<IData> TYPE(std::vector<SmartPointer<IData>> params) {
  if (!CHECK_PARAMETERS(1, params.size())) return NULL;
  if (!CHECK_TYPE(params[0], 1, _T("any"))) return NULL;
  switch(params[0]->type) {
	case IData::DATA_INT: return new DataString(_T("int")); break;
    case IData::DATA_BOOL: return new DataString(_T("bool")); break;
    case IData::DATA_FLOAT: return new DataString(_T("float")); break;
    case IData::DATA_STRING: return new DataString(_T("string")); break;
    case IData::DATA_VOID: return new DataString(_T("void")); break;
    case IData::DATA_MATERIAL: return new DataString(_T("Material")); break;
    case IData::DATA_TIME: return new DataString(_T("Timer")); break;
    case IData::DATA_CAMERA: return new DataString(_T("Camera")); break;
    case IData::DATA_VECTOR: return new DataString(_T("List")); break;
    case IData::DATA_SCENE_NODE: return new DataString(_T("SceneNode")); break; // TODO: specialize...
  }
  return NULL;
}


SmartPointer<IData> Reset(std::vector<SmartPointer<IData>> params) {
  //TimeManagement::GetInstance().release();
  //SceneFileManagement::GetInstance().release();
  SceneFileManagement::GetInstance().set_reset(true);
  return NULL;
}

// Math functions
SmartPointer<IData> sin(std::vector<SmartPointer<IData>> params) {
  if (!CHECK_PARAMETERS(1, params.size())) return NULL;
  SmartPointer<DataFloat> tmp = castToFloat(params[0]);
  if (!CHECK_TYPE(tmp, 1, _T("float"))) return NULL;
  return newData((float)::sin((double)tmp->value));
}

SmartPointer<IData> cos(std::vector<SmartPointer<IData>> params) {
  // Check that the number of parameters be equals to 1.
  if (!CHECK_PARAMETERS(1, params.size())) return NULL;
  SmartPointer<DataFloat> tmp = castToFloat(params[0]);
  if (!CHECK_TYPE(tmp, 1, _T("float"))) return NULL;
  return newData((float)::cos((double)tmp->value));
}

SmartPointer<IData> tan(std::vector<SmartPointer<IData>> params) {
  // Check that the number of parameters be equals to 1.
  if (!CHECK_PARAMETERS(1, params.size())) return NULL;
  SmartPointer<DataFloat> tmp = castToFloat(params[0]);
  if (!CHECK_TYPE(tmp, 1, _T("float"))) return NULL;
  return newData((float)::tan((double)tmp->value));
}

SmartPointer<IData> log(std::vector<SmartPointer<IData>> params) {
  // Check that the number of parameters be equals to 1.
  if (!CHECK_PARAMETERS(1, params.size())) return NULL;
  SmartPointer<DataFloat> tmp = castToFloat(params[0]);
  if (!CHECK_TYPE(tmp, 1, _T("float"))) return NULL;
  return newData((float)::log((double)tmp->value));
}

SmartPointer<IData> exp(std::vector<SmartPointer<IData>> params) {
  if (!CHECK_PARAMETERS(1, params.size())) return NULL;
  SmartPointer<DataFloat> tmp = castToFloat(params[0]);
  if (!CHECK_TYPE(tmp, 1, _T("float"))) return NULL;
  return newData((float)::exp((double)tmp->value));
}

SmartPointer<IData> abs(std::vector<SmartPointer<IData>> params) {
  // Check that the number of parameters be equals to 1.
  if (!CHECK_PARAMETERS(1, params.size())) return NULL;
  SmartPointer<DataFloat> tmp = castToFloat(params[0]);
  if (!CHECK_TYPE(tmp, 1, _T("float"))) return NULL;
  return newData((float)::fabs((double)tmp->value));
}

SmartPointer<IData> sqrt(std::vector<SmartPointer<IData>> params) {
  // Check that the number of parameters be equals to 1.
  if (!CHECK_PARAMETERS(1, params.size())) return NULL;
  SmartPointer<DataFloat> tmp = castToFloat(params[0]);
  if (!CHECK_TYPE(tmp, 1, _T("float"))) return NULL;
  return newData((float)::sqrt((double)tmp->value));
}

SmartPointer<IData> rand(std::vector<SmartPointer<IData>> params) {
  // Check that the number of parameters be equals to 2.
  if (!CHECK_PARAMETERS(2, params.size())) return NULL;

  // Get the values of parameters.
  SmartPointer<DataFloat> rangeMin = castToFloat(params[0]);
  SmartPointer<DataFloat> rangeMax = castToFloat(params[1]);
	
  // Check if are valid the parameters.
  if (!CHECK_TYPE(rangeMin, 1, _T("float"))) return NULL;
  if (!CHECK_TYPE(rangeMax, 2, _T("float"))) return NULL;

  // Generate the number.
  return newData((float)RAND(rangeMin->value, rangeMax->value));
}

// Utility functions
SmartPointer<IData> print(std::vector<SmartPointer<IData>> params) {
  const int nParams = static_cast<int> (params.size());
  for(int i = 0; i < nParams; ++i) {
    if (!CHECK_TYPE(params[i], i + 1, _T("any"))) return NULL;
    params[i]->print();
  }
  return NULL;
}

SmartPointer<IData> clone(std::vector<SmartPointer<IData>> params) {
  // Check that the number of parameters be equals to 1.
  if (!CHECK_PARAMETERS(1, params.size())) return NULL;
	
  // Check if is a SceneNode.
  if (params[0]->type == IData::DATA_SCENE_NODE) {
    SmartPointer<ISceneNode> node = castToSceneNode(params[0]);
    if (!CHECK_TYPE(node, 1, _T("SceneNode"))) return NULL;

    SmartPointer<IData> tmp = new DataSceneNode(node->copy());
    return tmp;
  }

  if (params[0]->type == IData::DATA_MATERIAL) {
    SmartPointer<DataMaterial> mat = castToDataMaterial(params[0]);
    if (!CHECK_TYPE(mat, 1, _T("material"))) return NULL;
    SmartPointer<IData> tmp = new DataMaterial(mat->getValue()->copy());
    return tmp;
  }
  return NULL;
}

SmartPointer<IData> exit(std::vector<SmartPointer<IData>> params) {
  if (!CHECK_PARAMETERS(0, params.size())) return NULL;
  AppWindow::GetInstance().exit_();
  return NULL;
}

// System function to detect collision between two GeometryNodes.
SmartPointer<IData> collisionDetection(std::vector<SmartPointer<IData>> params) {
  if (!CHECK_PARAMETERS(2, params.size())) return NULL;

  if (params[0] == NULL || params[1] == NULL) return NULL;

  // Check the type of the parameters.
  if (params[0]->type != IData::DATA_SCENE_NODE || params[1]->type != IData::DATA_SCENE_NODE) {
    PRINT_LOG(TYPE_ERROR, _T("Error: 'collisionDetection' : cannot convert parameter %d to 'Model'"), params[0]->type != IData::DATA_SCENE_NODE ? 1 : 2);
    return NULL;
  }

  SmartPointer<ISceneNode> sceneNode1 = castToSceneNode(params[0]);
  SmartPointer<ISceneNode> sceneNode2 = castToSceneNode(params[1]);

  // Check the node type.
  if (sceneNode1->type() != ISceneNode::GEOMETRY_NODE || sceneNode2->type() != ISceneNode::GEOMETRY_NODE) {
    PRINT_LOG(TYPE_ERROR, _T("Error: 'collisionDetection' : cannot convert parameter %d to 'Model'"), params[0]->type != IData::DATA_SCENE_NODE ? 1 : 2);
    return NULL;
  }

  // Cast to GeometryNodes.
  ISceneNode* pNode1 = sceneNode1;
  ISceneNode* pNode2 = sceneNode2;
  const SmartPointer<GeometryNode> geometry1 = dynamic_cast<GeometryNode*> (pNode1);
  const SmartPointer<GeometryNode> geometry2 = dynamic_cast<GeometryNode*> (pNode2);

  SmartPointer<IData> tmp = newData(AABBTreeCollision(geometry1, geometry2).Detect());
  return tmp;
}

// Functions to cast a value to a specific type.
SmartPointer<IData> toInt(std::vector<SmartPointer<IData>> params) {
  // Check that the number of parameters be equals to 1.
  if (!CHECK_PARAMETERS(1, params.size())) return NULL;
  return castToDATA(castToInt(params[0]));
}

SmartPointer<IData> toBool(std::vector<SmartPointer<IData>> params) {
  // Check that the number of parameters be equals to 1.
  if (!CHECK_PARAMETERS(1, params.size())) return NULL;
  return castToDATA(castToBool(params[0]));
}

SmartPointer<IData> toFloat(std::vector<SmartPointer<IData>> params) {
  // Check that the number of parameters be equals to 1.
  if (!CHECK_PARAMETERS(1, params.size())) return NULL;
  return castToDATA(castToFloat(params[0]));
}

SmartPointer<IData> toString(std::vector<SmartPointer<IData>> params) {
  // Check that the number of parameters be equals to 1.
  if (!CHECK_PARAMETERS(1, params.size())) return NULL;
  return castToDATA(castToString(params[0]));
}

END_TEG_NAMESPACE();
