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

#include "DataOperation.h"
#include "../DataTypes.h"
#include "../DataStructures.h"
#include "../SceneFile.h"
#include "../../Scene/scene_nodes.h"
#include "../../Material/Material.h"

BEGIN_TEG_NAMESPACE();

// DataOperation constructor.
DataOperation::DataOperation(
    const SmartPointer<IData>& op1_,
    const SmartPointer<IData>& op2_,
    std::string op_) : IData(DATA_OPERATION) {
  op1 = op1_; op2 = op2_; op = op_;
}

// DataOperation destructor.
DataOperation::~DataOperation() { 
  op1 = NULL; op2 = NULL;
}

// Function to try to convert a data in an operation.
SmartPointer<DataOperation> castToOperation(spDATA d) {
  return SmartPointer<DataOperation>(dynamic_cast<DataOperation*>((IData*) d));
}

#define UNARY_OPERATOR_ERORR(op, type) \
  PRINT_LOG( \
      TYPE_ERROR, \
      _T("error: unary \'%s\' : \'%s\' does not define this operator or a") \
          _T("conversion to a type acceptable to the predefined operator"), \
      op, \
      type)

#define BINARY_OPERATOR_ERORR(op, type1, type2) \
  PRINT_LOG( \
      TYPE_ERROR, \
      _T("error: binary \'%s\' : no operator found which takes type \'%s\' ") \
          _T("and \'%s\'(or there is no acceptable conversion)"), \
      op, \
      type1, \
      type2)

// Function to make an int unary operation.
SmartPointer<IData> makeOp(
    const SmartPointer<DataInt>& d1, const std::string& typeOp) {
  if (typeOp == "-") return newData(-d1->value);
  if (typeOp == "!") return newData(!d1->value);
  if (typeOp == "++") return newData(d1->value + 1);
  if (typeOp == "--") return newData(d1->value - 1);
  UNARY_OPERATOR_ERORR(typeOp.c_str(), _T("int"));
  return NULL;
}

// Function to make a bool unary operation.
SmartPointer<IData> makeOp(
    const SmartPointer<DataBool>& d1, const std::string& typeOp) {
  if (typeOp == "!") return newData(!d1->value);
  UNARY_OPERATOR_ERORR(typeOp.c_str(), _T("bool"));
  return NULL;
}

// Function to make a float unary operation.
SmartPointer<IData> makeOp(
    const SmartPointer<DataFloat>& d1, const std::string& typeOp) {
  if (typeOp == "-") return newData(-d1->value);
  if (typeOp == "!") return newData(!d1->value);
  if (typeOp == "++") return newData(d1->value + 1.0f);
  if (typeOp == "--") return newData(d1->value - 1.0f);
  UNARY_OPERATOR_ERORR(typeOp.c_str(), _T("float"));
  return NULL;
}

// Function to make a string unary operation.
SmartPointer<IData> makeOp(
    const SmartPointer<DataString>& d1, const std::string& typeOp) {
  UNARY_OPERATOR_ERORR(typeOp.c_str(), _T("string"));
  return NULL;
}

// Function to make an unary operation.
SmartPointer<IData> makeOp(SmartPointer<IData>& d1, const std::string& typeOp) {
  if (d1->type == IData::DATA_INT) return makeOp(castToInt(d1), typeOp);
  if (d1->type == IData::DATA_BOOL) return makeOp(castToBool(d1), typeOp);
  if (d1->type == IData::DATA_FLOAT) return makeOp(castToFloat(d1), typeOp);
  if (d1->type == IData::DATA_STRING) return makeOp(castToString(d1), typeOp);
  std::vector<SmartPointer<IData>> param; param.push_back(d1);
  SmartPointer<IData> tmp = TYPE(param);
  UNARY_OPERATOR_ERORR(typeOp.c_str(), castToString(tmp)->value.c_str());
  return NULL;
}

// Function to make an int binary operation.
SmartPointer<IData> makeOp(
    const SmartPointer<DataInt>& d1,
    const SmartPointer<DataInt>& d2,
    const std::string& typeOp) {
  // Check if is an arithmetic operator.
  if (typeOp == "+") return newData(d1->value + d2->value);
  if (typeOp == "-") return newData(d1->value - d2->value);
  if (typeOp == "*") return newData(d1->value * d2->value);
  if (typeOp == "/") return newData(d1->value / d2->value);
  if (typeOp == "%") return newData(d1->value % d2->value);

  // Check if is a boolean operator.
  if (typeOp == "<") return newData(d1->value < d2->value);
  if (typeOp == ">") return newData(d1->value > d2->value);
  if (typeOp == "<=") return newData(d1->value <= d2->value);
  if (typeOp == ">=") return newData(d1->value >= d2->value);
  if (typeOp == "==") return newData(d1->value == d2->value);
  if (typeOp == "!=") return newData(d1->value != d2->value);
  if (typeOp == "&&") return newData(d1->value && d2->value);
  if (typeOp == "||") return newData(d1->value || d2->value);
  BINARY_OPERATOR_ERORR(typeOp.c_str(), _T("int"), _T("int"));
  return NULL;
}

// Function to make a bool binary operation.
SmartPointer<IData> makeOp(
     const SmartPointer<DataBool>& d1,
     const SmartPointer<DataBool>& d2,
     const std::string& typeOp) {
  // Check if is a boolean Operator.
  if (typeOp == "<") return newData(d1->value < d2->value);
  if (typeOp == ">") return newData(d1->value > d2->value);
  if (typeOp == "<=") return newData(d1->value <= d2->value);
  if (typeOp == ">=") return newData(d1->value >= d2->value);
  if (typeOp == "==") return newData(d1->value == d2->value);
  if (typeOp == "!=") return newData(d1->value != d2->value);
  if (typeOp == "&&") return newData(d1->value && d2->value);
  if (typeOp == "||") return newData(d1->value || d2->value);
  BINARY_OPERATOR_ERORR(typeOp.c_str(), _T("bool"), _T("bool"));
  return NULL;
}

// Function to make a float binary operation.
SmartPointer<IData> makeOp(
    const SmartPointer<DataFloat>& d1,
    const SmartPointer<DataFloat>& d2,
    const std::string& typeOp ) {
  // Check if is an arithmetic operator.
  if (typeOp == "+") return newData(d1->value + d2->value);
  if (typeOp == "-") return newData(d1->value - d2->value);
  if (typeOp == "*") return newData(d1->value * d2->value);
  if (typeOp == "/") return newData(d1->value / d2->value);

  // Check if is a boolean Operator.
  if (typeOp == "<") return newData(d1->value < d2->value);
  if (typeOp == ">") return newData(d1->value > d2->value);
  if (typeOp == "<=") return newData(d1->value <= d2->value);
  if (typeOp == ">=") return newData(d1->value >= d2->value);
  if (typeOp == "==") return newData(d1->value == d2->value);
  if (typeOp == "!=") return newData(d1->value != d2->value);
  if (typeOp == "&&") return newData(d1->value && d2->value);
  if (typeOp == "||") return newData(d1->value || d2->value);
  BINARY_OPERATOR_ERORR(typeOp.c_str(), _T("float"), _T("float"));
  return NULL;
}

// Function to make a string binary operation.
SmartPointer<IData> makeOp(
    const SmartPointer<DataString>& d1,
    const SmartPointer<DataString>& d2,
    const std::string& typeOp) {
  if (typeOp == "+") return new DataString(d1->value + d2->value);
  BINARY_OPERATOR_ERORR(typeOp.c_str(), _T("string"), _T("string"));
  return NULL;
}

// Function to make a vector binary operation.
SmartPointer<IData> makeOp(
    const SmartPointer<DataVector>& d1,
    const SmartPointer<DataVector>& d2,
    const std::string& typeOp) {
  const int size1 = static_cast<int> (d1->value.size());
  const int size2 = static_cast<int> (d2->value.size());

  if (typeOp == "==") {
    bool equals = true;
	if (size1 != size2) equals = false;
	else {
		for (int i = 0; i < size1; ++i) {
			if (!castToBool(makeOp(d1->value[i], d2->value[i], typeOp))->value) {
			  equals = false; break;
			}
        }
	}
    return newData(equals);
  }

  BINARY_OPERATOR_ERORR(typeOp.c_str(), _T("List"), _T("List"));
  return NULL;
}

// Function to make a material binary operation.
SmartPointer<IData> makeOp(
    const SmartPointer<DataMaterial>& d1,
    const SmartPointer<DataMaterial>& d2,
    const std::string& typeOp) {

  if (typeOp == _T("==")) {
    return newData(d1->getValue()->name == d2->getValue()->name);
  }

  BINARY_OPERATOR_ERORR(typeOp.c_str(), _T("Material"), _T("Material"));
  return NULL;
}

// Function to make a binary operation.
SmartPointer<IData> makeOp(
    SmartPointer<IData>& d1,
    const SmartPointer<IData>& d2,
    const std::string& typeOp) {
  // Check if is valid make an operation, else report an error!
  if (d1->type <= IData::DATA_STRING && d2->type <= IData::DATA_STRING) {
    if (d1->type == IData::DATA_FLOAT || d2->type == IData::DATA_FLOAT)
      return makeOp(castToFloat(d1), castToFloat(d2), typeOp);
    if (d1->type == IData::DATA_INT || d2->type == IData::DATA_INT)
      return makeOp(castToInt(d1), castToInt(d2), typeOp);
    if (d1->type == IData::DATA_BOOL || d2->type == IData::DATA_BOOL)
      return makeOp(castToBool(d1), castToBool(d2), typeOp);
    if (d1->type == IData::DATA_STRING || d2->type == IData::DATA_STRING)
      return makeOp(castToString(d1), castToString(d2), typeOp);
  }

  if (d1->type == IData::DATA_VECTOR) {
    const SmartPointer<DataVector> tmp = castToDataVector(d1);
    if (tmp->value.size() == 1) return makeOp(tmp->value[0], d2, typeOp);
  }
  if (d2->type == IData::DATA_VECTOR) {
    const SmartPointer<DataVector> tmp = castToDataVector(d2);
    if (tmp->value.size() == 1) return makeOp(d1, tmp->value[0], typeOp);
  }

  if (d1->type <= IData::DATA_VECTOR && d2->type <= IData::DATA_VECTOR) {
    return makeOp(castToDataVector(d1), castToDataVector(d2), typeOp);
  }

  if (d1->type <= IData::DATA_MATERIAL && d2->type <= IData::DATA_MATERIAL) {
    return makeOp(castToDataMaterial(d1), castToDataMaterial(d2), typeOp);
  }


  std::vector<SmartPointer<IData>> param(1);
  param[0] = d1; const SmartPointer<DataString> tmp1 = castToString(TYPE(param));
  param[0] = d2; const SmartPointer<DataString> tmp2 = castToString(TYPE(param));
  BINARY_OPERATOR_ERORR(typeOp.c_str(), tmp1->value.c_str(), tmp2->value.c_str());
  return NULL;
}

// Function to free the memory of the contariners.
void freeContainer(
    SmartPointer<DataID> d,
    std::map<std::string, SmartPointer<IData>>& variables) {
  SmartPointer<IData> tmp;
  if (variables.find(d->value) != variables.end())
    tmp = variables[d->value];
  else
    tmp = GET_ID(d->value);

  if (tmp == NULL) return;
  if (tmp->type == IData::DATA_VECTOR) {
    SmartPointer<DataVector> container = castToDataVector(tmp);
	//container->value.clear();
	container->freePtrs();
  }
  else if (tmp->type == IData::DATA_SCENE_NODE) {
    SmartPointer<DataSceneNode> container = castToDataSceneNode(tmp);
	if (container->type == ISceneNode::GROUP_NODE)
      dynamic_cast<GroupNode*>((ISceneNode*)container->value)->ClearSceneNodes();
  }
}



// Method to execute the expresion.
SmartPointer<IData> DataOperation::resolve(
    std::map<std::string, SmartPointer<IData>>& variables) {

  if (op == "=") {	
    if (op1->type != DATA_ID) {
      return NULL; 
    }

    SmartPointer<IData> tmp2;
    if (op2 != NULL) tmp2 = op2->resolve(variables);
	
	// se verifica si existe una variable local con ese nombre
    if (variables.find(castToDataID(op1)->value) != variables.end())
      variables[castToDataID(op1)->value] = tmp2;
    else {
      // Buscar la variable en todos los archivos. 
      bool foundAsGlobalVar = false;
      std::list<SceneFile>::iterator it = SceneFileManagement::GetInstance().sceneFiles.begin();
      std::list<SceneFile>::iterator end = SceneFileManagement::GetInstance().sceneFiles.end();
      for (; it != end; ++it) {
        if (it->variables.find(castToDataID(op1)->value) != it->variables.end()) {
          it->variables[castToDataID(op1)->value] = tmp2;
          foundAsGlobalVar = true;
        }
      }
      if (!foundAsGlobalVar)
        variables[castToDataID(op1)->value] = tmp2;
    }
    return tmp2;
  }

  if (op == "+=" || op == "-=" || op == "*=" || op == "/=" || op == "%=") {
    if (op1->type != DATA_ID) return NULL;

    const int pos = SceneFileManagement::GetInstance().proccessingFile;

    SmartPointer<IData> tmp1 = op1->resolve(variables);
    SmartPointer<IData> tmp2;
    if (op2 != NULL) tmp2 = op2->resolve(variables);

	const SmartPointer<DataID> id = castToDataID(op1);

    if (variables.find(castToDataID(op1)->value) != variables.end()) {
      variables[castToDataID(op1)->value] = makeOp(tmp1, tmp2, op.substr(0, 1));
      return variables[castToDataID(op1)->value];
    } else {
	  // Search the value in the files.
      std::list<SceneFile>::iterator it = SceneFileManagement::GetInstance().sceneFiles.begin();
      std::list<SceneFile>::iterator end = SceneFileManagement::GetInstance().sceneFiles.end();
	  for (; it != end; ++it) {
	    if(it->variables.find(id->value) != it->variables.end()) {
          it->variables[id->value] = makeOp(tmp1, tmp2, op.substr(0, 1));
          return it->variables[id->value];
        }
	  }
	}
    return NULL; // Report in the log that the variable is not found ...
  }

  if (op == ".") {
    SmartPointer<IData> tmp1 = op1->resolve(variables);
    if (tmp1 == NULL) return NULL;
    if (tmp1->type != DATA_SCENE_NODE && tmp1->type != DATA_TIME &&
        tmp1->type != DATA_MATERIAL && tmp1->type != DATA_CAMERA && tmp1->type != DATA_VECTOR) return NULL;
    if (op2->type == DATA_ID)
      return tmp1->getAttribute(castToDataID(op2)->value);
    if (op2->type == DATA_CALL_FUNCTION) {
      SmartPointer<IData> method = tmp1->getMethod(op2, variables);
      return method;
    }
    return NULL;	// The variable is not defined ...
  }

  if (op == "[]") {
    if (op1->type != DATA_ID) return NULL; 
    // Report in the log: the variable must be a identifier

    const int pos = SceneFileManagement::GetInstance().proccessingFile;

    // Get the iterator.
    std::list<SceneFile>::iterator it = SceneFileManagement::GetInstance().sceneFiles.begin();
    std::list<SceneFile>::iterator end = SceneFileManagement::GetInstance().sceneFiles.end();
    for (int i = 0; i < pos; ++i) ++it;

    SmartPointer<IData> tmp2;
    if (op2 != NULL) tmp2 = op2->resolve(variables);

    if (variables.find(castToDataID(op1)->value) != variables.end()) {
      return variables[castToDataID(op1)->value]->getElement(tmp2);
    }
    else if(it->variables.find(castToDataID(op1)->value) != it->variables.end()) {
      return it->variables[castToDataID(op1)->value]->getElement(tmp2);
    }
    return NULL;	// The variable is not defined ...
  }

  if (op == "++" || op == "--") {
    if (op1->type != DATA_ID) return NULL; 
	
    const int pos = SceneFileManagement::GetInstance().proccessingFile;

    // Get the iterator.
    SmartPointer<IData> tmp1 = op1->resolve(variables);

	const SmartPointer<DataID> id = castToDataID(op1);

    // Check which variables will be updated
    if (variables.find(id->value) != variables.end()) {
      variables[id->value] = makeOp(tmp1, op);
      return variables[id->value];
    }
	else {
	  // Search the value in the files.
      std::list<SceneFile>::iterator it = SceneFileManagement::GetInstance().sceneFiles.begin();
      std::list<SceneFile>::iterator end = SceneFileManagement::GetInstance().sceneFiles.end();
	  for (; it != end; ++it) {
	    if(it->variables.find(id->value) != it->variables.end()) {
          it->variables[id->value] = makeOp(tmp1, op);
          return it->variables[id->value];
        }
	  }
	}
  
  }

  SmartPointer<IData> tmp1 = op1->resolve(variables);
  SmartPointer<IData> tmp2;
  if (op2 != NULL) tmp2 = op2->resolve(variables);

  if (tmp1 == NULL) return NULL;
  else if (tmp2 == NULL) return makeOp(tmp1, op);		// An unary operation
  return makeOp(tmp1, tmp2, op);						// A binary operation
}

END_TEG_NAMESPACE();
