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

#include "DataSceneNode.h"
#include "../Checks.h"
#include "../../Scene/scene_nodes.h"
#include "../DataStructures/DataCallFunction.h"
#include "../../Material/MaterialManagement.h"
#include "../DataTypes.h"
#include "../../Model/Model.h"

BEGIN_TEG_NAMESPACE();

// DataSceneNode constructor.
DataSceneNode::DataSceneNode(SmartPointer<ISceneNode> _value)
    : IData(DATA_SCENE_NODE), value(_value) { }

// DataSceneNode destructor.
DataSceneNode::~DataSceneNode() { value = NULL; }

SmartPointer<IData> DataSceneNode::resolve(
    std::map<std::string, SmartPointer<IData>>& variables) {
  return NULL;
}

// Prueba para modeficar el arbol de escena
// for the "." operator
SmartPointer<IData> DataSceneNode::getAttribute(std::string attrName) {
  return NULL;
}

SmartPointer<DataSceneNode> castToDataSceneNode(spDATA d) {
  if (d == NULL) return NULL;
  if (d->type != IData::DATA_SCENE_NODE) return NULL;
  return SmartPointer<DataSceneNode> (dynamic_cast<DataSceneNode*> ((IData*) d));
}

SmartPointer<ISceneNode> castToSceneNode(SmartPointer<IData> d) {
  // Check if is valid conversion
  if (d == NULL) return NULL;
  if (d->type != IData::DATA_SCENE_NODE)  {
    //PRINT_LOG(TYPE_ERROR, "TODO");
    return NULL;
  }
  return (ISceneNode*) castToDataSceneNode(d)->value;
}

#define CHECK_LIGHT_METHOD_PARAM(nParamNeeded, methodExpected) \
    checkMethodParameters( \
        nParamNeeded, \
        nParams, \
        _T("LightNode"), \
        methodExpected, \
        method->name.c_str())

#define CHECK_GROUP_METHOD_PARAM(nParamNeeded, methodExpected) \
    checkMethodParameters( \
        nParamNeeded, \
        nParams, \
        _T("GroupNode"), \
        methodExpected, \
        method->name.c_str())

#define CHECK_TRANSLATE_METHOD_PARAM(nParamNeeded, methodExpected) \
    checkMethodParameters( \
        nParamNeeded, \
        nParams, \
        _T("TranslateNode"), \
        methodExpected, \
        method->name.c_str())

#define CHECK_SCALE_METHOD_PARAM(nParamNeeded, methodExpected) \
    checkMethodParameters( \
        nParamNeeded, \
        nParams, \
        _T("ScaleNode"), \
        methodExpected, \
        method->name.c_str())

#define CHECK_ROTATE_METHOD_PARAM(nParamNeeded, methodExpected) \
    checkMethodParameters( \
        nParamNeeded, \
        nParams, \
        _T("RotateNode"), \
        methodExpected, \
        method->name.c_str())

#define CHECK_GEOMETRY_METHOD_PARAM(nParamNeeded, methodExpected) \
    checkMethodParameters( \
        nParamNeeded, \
        nParams, \
        _T("GeometryNode"), \
        methodExpected, \
        method->name.c_str())

#define CHECK_LIGHT_METHOD_TYPE(param, paramIndex, typeExpected) \
    checkMethodParameterType( \
        param, \
        paramIndex, \
        typeExpected, \
        _T("LightNode"), \
        method->name.c_str())

#define CHECK_GROUP_METHOD_TYPE(param, paramIndex, typeExpected) \
    checkMethodParameterType( \
        param, \
        paramIndex, \
        typeExpected, \
        _T("GroupNode"), \
        method->name.c_str())

#define CHECK_TRANSLATE_METHOD_TYPE(param, paramIndex, typeExpected) \
    checkMethodParameterType( \
        param, \
        paramIndex, \
        typeExpected, \
        _T("TranslateNode"), \
        method->name.c_str())

#define CHECK_SCALE_METHOD_TYPE(param, paramIndex, typeExpected) \
    checkMethodParameterType( \
        param, \
        paramIndex, \
        typeExpected, \
        _T("ScaleNode"), \
        method->name.c_str())

#define CHECK_ROTATE_METHOD_TYPE(param, paramIndex, typeExpected) \
    checkMethodParameterType( \
        param, \
        paramIndex, \
        typeExpected, \
        _T("RotateNode"), \
        method->name.c_str())

#define CHECK_GEOMETRY_METHOD_TYPE(param, paramIndex, typeExpected) \
    checkMethodParameterType( \
        param, \
        paramIndex, \
        typeExpected, \
        _T("GeometryNode"), \
        method->name.c_str())


SmartPointer<IData> DataSceneNode::getMethod(SmartPointer<IData> elem, std::map<std::string, SmartPointer<IData>>& variables) {
  // Cast the value to ISceneNode.
  SmartPointer<ISceneNode> node = (ISceneNode*) value;
  ISceneNode* pNode = node;

  // Check if is a valid pointer.
  if (node == NULL) return NULL;

  // Check if the elem is a method call.
  if (elem->type != DATA_CALL_FUNCTION) return NULL;
  const SmartPointer<DataCallFunction> method = castToDataCallFunction(elem);
  const int nParams = static_cast<int> (method->param.size());
  std::vector<SmartPointer<IData>> params(nParams);
  for (int i = 0; i < (int)method->param.size(); ++i) {
	params[i] = method->param[i]->resolve(variables);
  }

  // Check the attributes and functions that can be called.
  switch(node->type()) {
	case ISceneNode::GROUP_NODE:
      {
        const SmartPointer<GroupNode> group = dynamic_cast<GroupNode*>(pNode);
        const int nElem = static_cast<int> (group->nodes().size());
        if (CHECK_GROUP_METHOD_PARAM(0, _T("size"))) {
          return newData(nElem);
        }
        if (CHECK_GROUP_METHOD_PARAM(1, _T("get"))) {
          const SmartPointer<DataInt> p0 = castToInt(params[0]);
	      if (!CHECK_GROUP_METHOD_TYPE(p0, 1, _T("int"))) return NULL;
          if (nElem == 0) {
            PRINT_LOG(TYPE_ERROR, _T("error: accessing empty GroupNode"));
            return NULL;
          }
		  if (p0->value < 0) return new DataSceneNode(group->GetSceneNode((nElem + (p0->value % nElem)) % nElem));
		    return new DataSceneNode(group->GetSceneNode(p0->value % nElem));
        }
        if (CHECK_GROUP_METHOD_PARAM(1, _T("add"))) {
          SmartPointer<DataSceneNode> p0 = castToDataSceneNode(params[0]);
          if (!CHECK_GROUP_METHOD_TYPE(p0, 1, _T("SceneNode"))) return NULL;
		    group->AddSceneNode(p0->value);
	      return NULL;
        }
        if (CHECK_GROUP_METHOD_PARAM(1, _T("remove"))) {
          const SmartPointer<DataInt> p0 = castToInt(params[0]);
          if (!CHECK_GROUP_METHOD_TYPE(p0, 1, _T("int"))) return NULL;
          if (nElem == 0) {
            PRINT_LOG(TYPE_ERROR, _T("error: remove element in an empty GroupNode"));
            return NULL;
          }
		  if (p0->value < 0) group->EraseSceneNode((nElem + (p0->value % nElem)) % nElem);
		  else group->EraseSceneNode(p0->value % nElem);
		  return NULL;
        }
	  } break;
	
    case ISceneNode::SCALE_NODE:
    {
      const SmartPointer<ScaleNode> scale = dynamic_cast<ScaleNode*>(pNode);
	  if (CHECK_SCALE_METHOD_PARAM(0, _T("getX"))) {
        return newData(scale->x());
	  }
      if (CHECK_SCALE_METHOD_PARAM(0, _T("getY"))) {
        return newData(scale->y());
	  }
      if (CHECK_SCALE_METHOD_PARAM(0, _T("getZ"))) {
        return newData(scale->z());
	  }
      if (CHECK_SCALE_METHOD_PARAM(1, _T("setX"))) {
        const SmartPointer<DataFloat> p0 = castToFloat(params[0]);
        if (CHECK_SCALE_METHOD_TYPE(p0, 1, _T("float")))
          scale->set_x(p0->value);
      }
	  if (CHECK_SCALE_METHOD_PARAM(1, _T("setY"))) {
        const SmartPointer<DataFloat> p0 = castToFloat(params[0]);
        if (CHECK_SCALE_METHOD_TYPE(p0, 1, _T("float")))
          scale->set_y(p0->value);
      }
	  if (CHECK_SCALE_METHOD_PARAM(1, _T("setZ"))) {
        const SmartPointer<DataFloat> p0 = castToFloat(params[0]);
        if (CHECK_SCALE_METHOD_TYPE(p0, 1, _T("float")))
          scale->set_z(p0->value);
      }
    } break;

    case ISceneNode::TRANSLATE_NODE:
    {
      const SmartPointer<TranslateNode> translate = dynamic_cast<TranslateNode*>(pNode);
	  if (CHECK_TRANSLATE_METHOD_PARAM(0, _T("getX"))) {
        return newData(translate->x());
	  }
      if (CHECK_TRANSLATE_METHOD_PARAM(0, _T("getY"))) {
        return newData(translate->y());
	  }
      if (CHECK_TRANSLATE_METHOD_PARAM(0, _T("getZ"))) {
        return newData(translate->z());
	  }
      if (CHECK_TRANSLATE_METHOD_PARAM(1, _T("setX"))) {
        const SmartPointer<DataFloat> p0 = castToFloat(params[0]);
        if (CHECK_TRANSLATE_METHOD_TYPE(p0, 1, _T("float")))
          translate->set_x(p0->value);
      }
	  if (CHECK_TRANSLATE_METHOD_PARAM(1, _T("setY"))) {
        const SmartPointer<DataFloat> p0 = castToFloat(params[0]);
        if (CHECK_TRANSLATE_METHOD_TYPE(p0, 1, _T("float")))
          translate->set_y(p0->value);
      }
	  if (CHECK_TRANSLATE_METHOD_PARAM(1, _T("setZ"))) {
        const SmartPointer<DataFloat> p0 = castToFloat(params[0]);
        if (CHECK_TRANSLATE_METHOD_TYPE(p0, 1, _T("float")))
          translate->set_z(p0->value);
      }
    } break;

	case ISceneNode::ROTATE_NODE:
    {
      const SmartPointer<RotateNode> rotate = dynamic_cast<RotateNode*>(pNode);
      if (CHECK_ROTATE_METHOD_PARAM(0, _T("getX"))) {
        return newData(rotate->x());
	  }
      if (CHECK_ROTATE_METHOD_PARAM(0, _T("getY"))) {
        return newData(rotate->y());
	  }
      if (CHECK_ROTATE_METHOD_PARAM(0, _T("getZ"))) {
        return newData(rotate->z());
	  }
	  if (CHECK_ROTATE_METHOD_PARAM(0, _T("getAngle"))) {
        return newData(rotate->angle());
	  }
      if (CHECK_ROTATE_METHOD_PARAM(1, _T("setX"))) {
        const SmartPointer<DataFloat> p0 = castToFloat(params[0]);
        if (CHECK_ROTATE_METHOD_TYPE(p0, 1, _T("float")))
          rotate->set_x(p0->value);
      }
	  if (CHECK_ROTATE_METHOD_PARAM(1, _T("setY"))) {
        const SmartPointer<DataFloat> p0 = castToFloat(params[0]);
        if (CHECK_ROTATE_METHOD_TYPE(p0, 1, _T("float")))
          rotate->set_y(p0->value);
      }
	  if (CHECK_ROTATE_METHOD_PARAM(1, _T("setZ"))) {
        const SmartPointer<DataFloat> p0 = castToFloat(params[0]);
        if (CHECK_ROTATE_METHOD_TYPE(p0, 1, _T("float")))
          rotate->set_z(p0->value);
      }
	  if (CHECK_ROTATE_METHOD_PARAM(1, _T("setAngle"))) {
        const SmartPointer<DataFloat> p0 = castToFloat(params[0]);
        if (CHECK_ROTATE_METHOD_TYPE(p0, 1, _T("float")))
          rotate->set_angle(p0->value);
      }
	} break;

    case ISceneNode::GEOMETRY_NODE:
      {
        const SmartPointer<GeometryNode> geometry = dynamic_cast<GeometryNode*>(pNode);
		if (CHECK_GEOMETRY_METHOD_PARAM(0, _T("setDefaultMaterial"))) {
		  geometry->set_use_default_material(true); return NULL;
		}
		if (CHECK_GEOMETRY_METHOD_PARAM(0, _T("isEnable"))) {
		  return newData(geometry->draw());
		}
		if (CHECK_GEOMETRY_METHOD_PARAM(1, _T("castShadow"))) {
		  const SmartPointer<DataBool> p0 = castToBool(params[0]);
          if (CHECK_GEOMETRY_METHOD_TYPE(p0, 1, _T("bool")))
			geometry->set_cast_shadows(p0->value);
		  return NULL;
		}
        if (CHECK_GEOMETRY_METHOD_PARAM(1, _T("draw"))) {
          const SmartPointer<DataBool> p0 = castToBool(params[0]);
          if (CHECK_GEOMETRY_METHOD_TYPE(p0, 1, _T("bool")))
			geometry->set_draw(p0->value);
          return NULL;
        }
        if (CHECK_GEOMETRY_METHOD_PARAM(0, _T("getMaterial"))) {
		  if (!geometry->use_default_material() && geometry->custom_material() != NULL) {
			const SmartPointer<DataMaterial> tmp = new DataMaterial(geometry->custom_material());
            return castToDATA(tmp);
		  }
		  else {
			const SmartPointer<DataMaterial> tmp = new DataMaterial(geometry->model()->groupList[0].mat[0]);
            return castToDATA(tmp);
		  }
        }
        if (CHECK_GEOMETRY_METHOD_PARAM(1, _T("setMaterial"))) {
		  const SmartPointer<DataMaterial> mat = castToDataMaterial(params[0]);
		  if (CHECK_GEOMETRY_METHOD_TYPE(mat, 1, _T("Material"))) {
		    geometry->set_custom_material(mat->getValue());
            geometry->set_use_default_material(false);
			return NULL;
		  }
		}
      } break;

    case ISceneNode::LIGHT_NODE:
      {
        const SmartPointer<LightNode> light = dynamic_cast<LightNode*>(pNode);
  
        if (CHECK_LIGHT_METHOD_PARAM(0, _T("getDiffuseX"))) {
          return newData(light->diffuse().x);
        }
        if (CHECK_LIGHT_METHOD_PARAM(0, _T("getDiffuseY"))) {
          return newData(light->diffuse().y);
        }
        if (CHECK_LIGHT_METHOD_PARAM(0, _T("getDiffuseZ"))) {
          return newData(light->diffuse().z);
        }
        if (CHECK_LIGHT_METHOD_PARAM(0, _T("getAmbientX"))) {
          return newData(light->ambient().x);
        }
        if (CHECK_LIGHT_METHOD_PARAM(0, _T("getAmbientY"))) {
          return newData(light->ambient().y);
        }
        if (CHECK_LIGHT_METHOD_PARAM(0, _T("getAmbientZ"))) {
          return newData(light->ambient().z);
        }
        if (CHECK_LIGHT_METHOD_PARAM(0, _T("getSpecularX"))) {
          return newData(light->specular().x);
        }
        if (CHECK_LIGHT_METHOD_PARAM(0, _T("getSpecularY"))) {
          return newData(light->specular().y);
        }
        if (CHECK_LIGHT_METHOD_PARAM(0, _T("getSpecularZ"))) {
          return newData(light->specular().z);
        }
        if (CHECK_LIGHT_METHOD_PARAM(1, _T("setDiffuseX"))) {
          const SmartPointer<DataFloat> p0 = castToFloat(params[0]);
		  if (CHECK_LIGHT_METHOD_TYPE(p0, 1, _T("float"))) {
            Vec4f new_diffuse = light->diffuse();
		    new_diffuse.x = p0->value;
			light->set_diffuse(new_diffuse);
			return newData(new_diffuse.x);
		  }
        }
        if (CHECK_LIGHT_METHOD_PARAM(1, _T("setDiffuseY"))) {
          const SmartPointer<DataFloat> p0 = castToFloat(params[0]);
		  if (CHECK_LIGHT_METHOD_TYPE(p0, 1, _T("float"))) {
			Vec4f new_diffuse = light->diffuse();
		    new_diffuse.y = p0->value;
			light->set_diffuse(new_diffuse);
			return newData(new_diffuse.y);
		  }
        }
        if (CHECK_LIGHT_METHOD_PARAM(1, _T("setDiffuseZ"))) {
          const SmartPointer<DataFloat> p0 = castToFloat(params[0]);
		  if (CHECK_LIGHT_METHOD_TYPE(p0, 1, _T("float"))) {
			Vec4f new_diffuse = light->diffuse();
		    new_diffuse.z = p0->value;
			light->set_diffuse(new_diffuse);
			return newData(new_diffuse.z);
		  }
        }
        if (CHECK_LIGHT_METHOD_PARAM(1, _T("setAmbientX"))) {
          const SmartPointer<DataFloat> p0 = castToFloat(params[0]);
		  if (CHECK_LIGHT_METHOD_TYPE(p0, 1, _T("float"))) {
			Vec4f new_ambient = light->ambient();
		    new_ambient.x = p0->value;
			light->set_ambient(new_ambient);
			return newData(new_ambient.x);
		  }
        }
        if (CHECK_LIGHT_METHOD_PARAM(1, _T("setAmbientY"))) {
          const SmartPointer<DataFloat> p0 = castToFloat(params[0]);
		  if (CHECK_LIGHT_METHOD_TYPE(p0, 1, _T("float"))) {
			Vec4f new_ambient = light->ambient();
		    new_ambient.y = p0->value;
			light->set_ambient(new_ambient);
			return newData(new_ambient.y);
		  }
        }
        if (CHECK_LIGHT_METHOD_PARAM(1, _T("setAmbientZ"))) {
          const SmartPointer<DataFloat> p0 = castToFloat(params[0]);
		  if (CHECK_LIGHT_METHOD_TYPE(p0, 1, _T("float"))) {
			Vec4f new_ambient = light->ambient();
		    new_ambient.z = p0->value;
			light->set_ambient(new_ambient);
			return newData(new_ambient.z);
		  }
        }
        if (CHECK_LIGHT_METHOD_PARAM(1, _T("setSpecularX"))) {
          const SmartPointer<DataFloat> p0 = castToFloat(params[0]);
		  if (CHECK_LIGHT_METHOD_TYPE(p0, 1, _T("float"))) {
			Vec4f new_specular = light->specular();
		    new_specular.x = p0->value;
			light->set_specular(new_specular);
			return newData(new_specular.x);
		  }
        }
        if (CHECK_LIGHT_METHOD_PARAM(1, _T("setSpecularY"))) {
          const SmartPointer<DataFloat> p0 = castToFloat(params[0]);
		  if (CHECK_LIGHT_METHOD_TYPE(p0, 1, _T("float"))) {
			Vec4f new_specular = light->specular();
		    new_specular.y = p0->value;
			light->set_specular(new_specular);
			return newData(new_specular.y);
		  }
        }
        if (CHECK_LIGHT_METHOD_PARAM(1, _T("setSpecularZ"))) {
          const SmartPointer<DataFloat> p0 = castToFloat(params[0]);
		  if (CHECK_LIGHT_METHOD_TYPE(p0, 1, _T("float"))) {
			Vec4f new_specular = light->specular();
		    new_specular.z = p0->value;
			light->set_specular(new_specular);
			return newData(new_specular.z);
		  }
        }
      } break;

  }
  return NULL;
}

#undef CHECK_LIGHT_METHOD_PARAM
#undef CHECK_GROUP_METHOD_PARAM
#undef CHECK_TRANSLATE_METHOD_PARAM
#undef CHECK_SCALE_METHOD_PARAM
#undef CHECK_ROTATE_METHOD_PARAM
#undef CHECK_GEOMETRY_METHOD_PARAM
#undef CHECK_LIGHT_METHOD_TYPE
#undef CHECK_GROUP_METHOD_TYPE
#undef CHECK_TRANSLATE_METHOD_TYPE
#undef CHECK_SCALE_METHOD_TYPE
#undef CHECK_ROTATE_METHOD_TYPE
#undef CHECK_GEOMETRY_METHOD_TYPE

END_TEG_NAMESPACE();
