//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------

#include "console/engineAPI.h"
#include "platform/platform.h"

#include "ts/collada/colladaUtils.h"
#include "ts/collada/colladaAppNode.h"
#include "ts/collada/colladaShapeLoader.h"

#include "T3D/pointLight.h"
#include "T3D/spotLight.h"


//-----------------------------------------------------------------------------
// Collada <light> elements are very similar, but are arranged as separate, unrelated
// classes. These template functions are used to provide a simple way to access the
// common elements.
template<class T> static void resolveLightColor(T* light, ColorF& color)
{
   if (light->getColor())
   {
      color.red = light->getColor()->getValue()[0];
      color.green = light->getColor()->getValue()[1];
      color.blue = light->getColor()->getValue()[2];
   }
}

template<class T> static void resolveLightAttenuation(T* light, Point3F& attenuationRatio)
{
   if (light->getConstant_attenuation())
      attenuationRatio.x = light->getConstant_attenuation()->getValue();
   if (light->getLinear_attenuation())
      attenuationRatio.y = light->getLinear_attenuation()->getValue();
   if (light->getQuadratic_attenuation())
      attenuationRatio.z = light->getQuadratic_attenuation()->getValue();
}

//-----------------------------------------------------------------------------
// Recurse through the collada scene to add <light>s to the Torque scene
static void processNodeLights(AppNode* appNode, const MatrixF& offset, SimGroup* group)
{
   const domNode* node = dynamic_cast<ColladaAppNode*>(appNode)->getDomNode();

   for (S32 iLight = 0; iLight < node->getInstance_light_array().getCount(); iLight++) {

      domInstance_light* instLight = node->getInstance_light_array()[iLight];
      domLight* p_domLight = daeSafeCast<domLight>(instLight->getUrl().getElement());
      if (!p_domLight) {
         Con::warnf("Failed to find light for URL \"%s\"", instLight->getUrl().getOriginalURI());
         continue;
      }

      String lightName = Sim::getUniqueName(_GetNameOrId(node));
      const char* lightType = "";

      domLight::domTechnique_common* technique = p_domLight->getTechnique_common();
      if (!technique) {
         Con::warnf("No <technique_common> for light \"%s\"", lightName.c_str());
         continue;
      }

      LightBase* pLight = 0;
      ColorF color(ColorF::WHITE);
      Point3F attenuation(0, 1, 1);

      if (technique->getAmbient()) {
         domLight::domTechnique_common::domAmbient* ambient = technique->getAmbient();
         // No explicit support for ambient lights, so use a PointLight instead
         lightType = "ambient";
         pLight = new PointLight;
         resolveLightColor(ambient, color);
      }
      else if (technique->getDirectional()) {
         domLight::domTechnique_common::domDirectional* directional = technique->getDirectional();
         // No explicit support for directional lights, so use a SpotLight instead
         lightType = "directional";
         pLight = new SpotLight;
         resolveLightColor(directional, color);
      }
      else if (technique->getPoint()) {
         domLight::domTechnique_common::domPoint* point = technique->getPoint();
         lightType = "point";
         pLight = new PointLight;
         resolveLightColor(point, color);
         resolveLightAttenuation(point, attenuation);
      }
      else if (technique->getSpot()) {
         domLight::domTechnique_common::domSpot* spot = technique->getSpot();
         lightType = "spot";
         pLight = new SpotLight;
         resolveLightColor(spot, color);
         resolveLightAttenuation(spot, attenuation);
      }
      else
         continue;

      Con::printf("Adding <%s> light \"%s\" as a %s", lightType, lightName.c_str(), pLight->getClassName());

      MatrixF mat(offset);
      mat.mul(appNode->getNodeTransform(TSShapeLoader::DefaultTime));

      pLight->setDataField(StringTable->insert("color"), 0,
         avar("%f %f %f %f", color.red, color.green, color.blue, color.alpha));
      pLight->setDataField(StringTable->insert("attenuationRatio"), 0,
         avar("%f %f %f", attenuation.x, attenuation.y, attenuation.z));
      pLight->setTransform(mat);

      if (!pLight->registerObject(lightName)) {
         Con::errorf(ConsoleLogEntry::General, "Failed to register light for \"%s\"", lightName.c_str());
         delete pLight;
      }
      else if (group)
         group->addObject(pLight);
   }

   // Recurse child nodes
   for (S32 iChild = 0; iChild < appNode->getNumChildNodes(); iChild++)
      processNodeLights(appNode->getChildNode(iChild), offset, group);
}

// Load lights from a collada file and add to the scene.
DefineConsoleFunction( loadColladaLights, bool, (const char * filename, const char * parentGroup, const char * baseObject), ("", ""), 
   "(string filename, SimGroup parentGroup=MissionGroup, SimObject baseObject=-1)"
   "Load all light instances from a COLLADA (.dae) file and add to the scene.\n"
   "@param filename COLLADA filename to load lights from\n"
   "@param parentGroup (optional) name of an existing simgroup to add the new "
   "lights to (defaults to MissionGroup)\n"
   "@param baseObject (optional) name of an object to use as the origin (useful "
   "if you are loading the lights for a collada scene and have moved or rotated "
   "the geometry)\n"
   "@return true if successful, false otherwise\n\n"
   "@tsexample\n"
   "// load the lights in room.dae\n"
   "loadColladaLights( \"art/shapes/collada/room.dae\" );\n\n"
   "// load the lights in room.dae and add them to the RoomLights group\n"
   "loadColladaLights( \"art/shapes/collada/room.dae\", \"RoomLights\" );\n\n"
   "// load the lights in room.dae and use the transform of the \"Room\"\n"
   "object as the origin\n"
   "loadColladaLights( \"art/shapes/collada/room.dae\", \"\", \"Room\" );\n"
   "@endtsexample\n\n"
   "@note Currently for editor use only\n"
   "@ingroup Editors\n"
   "@internal")
{
   Torque::Path path(filename);

   // Optional group to add the lights to. Create if it does not exist, and use
   // the MissionGroup if not specified.
   SimGroup* missionGroup = dynamic_cast<SimGroup*>(Sim::findObject("MissionGroup"));
   SimGroup* group = 0;
   if (!String::isEmpty(parentGroup)){
      if (!Sim::findObject(parentGroup, group)) {
         // Create the group if it could not be found
         group = new SimGroup;
         if (group->registerObject(parentGroup)) {
            if (missionGroup)
               missionGroup->addObject(group);
         }
         else {
            delete group;
            group = 0;
         }
      }
   }
   if (!group)
      group = missionGroup;

   // Optional object to provide the base transform
   MatrixF offset(true);
   if (!String::isEmpty(baseObject)){
      SceneObject *obj;
      if (Sim::findObject(baseObject, obj))
         offset = obj->getTransform();
   }

   // Load the Collada file into memory
   domCOLLADA* root = ColladaShapeLoader::getDomCOLLADA(path);
   if (!root) {
      TSShapeLoader::updateProgress(TSShapeLoader::Load_Complete, "Load complete");
      return false;
   }

   // Extract the global scale and up_axis from the top level <asset> element,
   F32 unit = 1.0f;
   domUpAxisType upAxis = UPAXISTYPE_Z_UP;
   if (root->getAsset()) {
      if (root->getAsset()->getUnit())
         unit = root->getAsset()->getUnit()->getMeter();
      if (root->getAsset()->getUp_axis())
         upAxis = root->getAsset()->getUp_axis()->getValue();
   }

   ColladaUtils::getOptions().unit = unit;
   ColladaUtils::getOptions().upAxis = upAxis;

   // First grab all of the top-level nodes
   Vector<ColladaAppNode*> sceneNodes;
   for (S32 iSceneLib = 0; iSceneLib < root->getLibrary_visual_scenes_array().getCount(); iSceneLib++) {
      const domLibrary_visual_scenes* libScenes = root->getLibrary_visual_scenes_array()[iSceneLib];
      for (S32 iScene = 0; iScene < libScenes->getVisual_scene_array().getCount(); iScene++) {
         const domVisual_scene* visualScene = libScenes->getVisual_scene_array()[iScene];
         for (S32 iNode = 0; iNode < visualScene->getNode_array().getCount(); iNode++)
            sceneNodes.push_back(new ColladaAppNode(visualScene->getNode_array()[iNode]));
      }
   }

   // Recurse the scene tree looking for <instance_light>s
   for (S32 iNode = 0; iNode < sceneNodes.size(); iNode++) {
      processNodeLights(sceneNodes[iNode], offset, group);
      delete sceneNodes[iNode];
   }

   TSShapeLoader::updateProgress(TSShapeLoader::Load_Complete, "Load complete");

   return true;
}
