#pragma warning ( disable: 4786 )
#include <windows.h>
#include <stack>
#include <string>
#include <vector>
#include <map>
#include <set>
#include <cmath>
#include <cassert>

#include "DTSShape.h"
#include "DTSBrushMesh.h"
#include "DTSMilkshapeMesh.h"
#include "DTSMilkshapeShape.h"

#include "msLib.h"

namespace DTS
{
   
   // -----------------------------------------------------------------------
   // Interpolation methods used to compute tween frames - phdana

   void lerpTranslation(Point *out, Point &a, Point&b, float alpha)
   {
      out->x(a.x() + (b.x()-a.x()) * alpha);
      out->y(a.y() + (b.y()-a.y()) * alpha);
      out->z(a.z() + (b.z()-a.z()) * alpha);
   }

   void lerpTranslation(Point *out, Point &a, Point &b, int outFrame, int aFrame, int bFrame)
   {
      float alpha = (float)(outFrame - aFrame) / (float)(bFrame - aFrame);

      lerpTranslation(out,a,b,alpha);
   }

   void lerpRotation(Quaternion *out, Quaternion &q1, Quaternion &q2, float alpha)
   {
      //-----------------------------------
      // Calculate the cosine of the angle:

      double cosOmega = q1.x() * q2.x() + q1.y() * q2.y() + q1.z() * q2.z() + q1.w() * q2.w();

      //-----------------------------------
      // adjust signs if necessary:

      float sign2;
      if ( cosOmega < 0.0 )
      {
         cosOmega = -cosOmega;
         sign2 = -1.0f;
      }
      else
         sign2 = 1.0f;

      //-----------------------------------
      // calculate interpolating coeffs:

      double scale1, scale2;
      if ( (1.0 - cosOmega) > 0.00001 )
      {
         // standard case
         double omega = acos(cosOmega);
         double sinOmega = sin(omega);
         scale1 = sin((1.0 - alpha) * omega) / sinOmega;
         scale2 = sign2 * sin(alpha * omega) / sinOmega;
      }
      else
      {
         // if quats are very close, just do linear interpolation
         scale1 = 1.0 - alpha;
         scale2 = sign2 * alpha;
      }


      //-----------------------------------
      // actually do the interpolation:

      out->x( float(scale1 * q1.x() + scale2 * q2.x()));
      out->y( float(scale1 * q1.y() + scale2 * q2.y()));
      out->z( float(scale1 * q1.z() + scale2 * q2.z()));
      out->w( float(scale1 * q1.w() + scale2 * q2.w()));
   }

   void lerpRotation(Quaternion *out, Quaternion &a, Quaternion &b,
      int outFrame, int aFrame, int bFrame)
   {
      float alpha = (float)(outFrame - aFrame) / (float)(bFrame - aFrame);

      lerpRotation(out,a,b,alpha);
   }

   // -----------------------------------------------------------------------

   int extractMeshFlags(char * name)
   {
      // Extract comma seperated flags embeded in the name. Anything after
      // the ":" in the name is assumed to be flag arguments and stripped
      // off the name.
      int flags = 0;
      char *ptr = strstr(name,":");
      if (ptr)
      {
         *ptr = 0;
         for (char *tok = strtok(ptr+1,","); tok != 0; tok = strtok(0,","))
         {
            // Strip lead/trailing white space
            for (; isspace(*tok); tok++) ;
            for (char* itr = tok + strlen(tok)-1; itr > tok && isspace(*itr); itr--)
               *itr = 0;

            //
            if (!stricmp(tok,"billboard"))
               flags |= Mesh::Billboard;
               else
            if (!stricmp(tok,"billboardz"))
               flags |= Mesh::Billboard | Mesh::BillboardZ;
               else
            if (!stricmp(tok,"enormals"))
               flags |= Mesh::EncodedNormals;
         }
      }
      return flags;
   }

   void extractMaterialFlags(char * name, int& flags)
   {
      // Extract comma seperated flags embeded in the name. Anything after
      // the ":" in the name is assumed to be flag arguments and stripped
      // off the name.
      char *ptr = strstr(name,":");
      if (ptr)
      {
         *ptr = 0;
         for (char *tok = strtok(ptr+1,","); tok != 0; tok = strtok(0,","))
         {
            // Strip lead/trailing white space
            for (; isspace(*tok); tok++) ;
            for (char* itr = tok + strlen(tok)-1; itr > tok && isspace(*itr); itr--)
               *itr = 0;

			if (!stricmp(tok,"add"))
				flags |= Material::Additive;
				else
			if (!stricmp(tok,"sub"))
                flags |= Material::Subtractive;
				else
			if (!stricmp(tok,"illum"))
                flags |= Material::SelfIlluminating;
				else
			if (!stricmp(tok,"nomip"))
				flags |= Material::NoMipMap;
				else
			if (!stricmp(tok,"mipzero"))
				flags |= Material::MipMapZeroBorder;
         }
      }
   }

   // -----------------------------------------------------------------------

   bool isBoneAnimated(msBone *bone, int start, int end)
   {
      // Returns true if the bone contains any key frames
      // within the given time range.
      int numPKeys  = msBone_GetPositionKeyCount(bone);
      for (int j = 0 ; j < numPKeys ; j++) {
         msPositionKey * msKey = msBone_GetPositionKeyAt (bone, j);
         // MS is one based, start/end 0 based..
         if (msKey->fTime > start && msKey->fTime <= end)
            return true;
      }
      int numRKeys  = msBone_GetRotationKeyCount(bone);
      for (int i = 0 ; i < numRKeys ; i++)
      {
         msRotationKey * msKey = msBone_GetRotationKeyAt (bone, i);
         // MS is one based, start/end 0 based..
         if (msKey->fTime > start && msKey->fTime <= end)
            return true;
      }
      return false;
   }


   // -----------------------------------------------------------------------
   //  Imports a Milkshape Model
   // -----------------------------------------------------------------------

   MilkshapeShape::ImportConfig::ImportConfig ()
   {
      withMaterials       = true ;
      collisionType       = C_None ;
      collisionComplexity = 0.2f ; 
      collisionVisible    = false ;
      animation           = true ;
      reset();
   }

   void MilkshapeShape::ImportConfig::reset()
   {
      // Reset values that must be specified using opt: or seq:
      scaleFactor         = 0.1f ;
      minimumSize         = 0 ;
      animationFPS        = 15 ;
      animationCyclic     = false;
      sequence.resize(0);
   }


   MilkshapeShape::MilkshapeShape (msModel * model, MilkshapeShape::ImportConfig config) 
   {
      int  i, j ;
      char buffer[512] ;

      int numBones     = msModel_GetBoneCount(model) ;
      int numMeshes    = msModel_GetMeshCount(model) ;
      int numMaterials = msModel_GetMaterialCount(model) ;

      // --------------------------------------------------------
      //  Materials (optional)
      // --------------------------------------------------------

      if (config.withMaterials)
      {
         for (i = 0 ; i < numMaterials ; i++)
         {
            msMaterial * msMat = msModel_GetMaterialAt(model, i) ;
            char         textureName[MS_MAX_PATH+1] ;

            // Check for "reserved" names first.  Reserved names
            // are special materials used to encode exporter options...
            // a total hack, but what can you do?
            msMaterial_GetName (msMat, textureName, MS_MAX_PATH);
            if (!strncmp(textureName,"seq:",4) || !strncmp(textureName,"opt:",4))
               continue;

            // If no texture, use the material name
            msMaterial_GetDiffuseTexture (msMat, textureName, MS_MAX_PATH) ;
            if (!textureName[0])
            {
               msMaterial_GetName (msMat, textureName, MS_MAX_PATH) ;

               // If no name, create one
               if (!textureName[0])
                  strcpy (textureName, "Unnamed") ;
            }
            
            // Strip texture file extension
            char * ptr = textureName + strlen(textureName) ;
            while (ptr > textureName && *ptr != '.' && *ptr != '\\') ptr-- ;
            if (*ptr == '.') *ptr = '\0' ;
            
            // Strip texture file path 
            ptr = textureName + strlen(textureName) ;
            while (ptr > textureName && *ptr != '\\') ptr-- ;
            if (*ptr == '\\') memmove (textureName, ptr+1, strlen(ptr)+1) ;

            // Create the material
            Material mat ;
            mat.name        = textureName ;
            mat.flags       = Material::SWrap | Material::TWrap ;
            mat.reflectance = materials.size() ;
            mat.bump        = -1 ;
            mat.detail      = -1 ;
            mat.detailScale = 1.0f ;

            // take the strength of the enviro mapping from the shininess
            float shininess = msMaterial_GetShininess(msMat) / 128.0f;
            if (shininess > 0.0f)
            {
                mat.reflection = shininess;
            }
            else
            {
                mat.flags |= Material::NeverEnvMap;
                mat.reflection = 0.0f;
            }

            // look for tags in material name.. the tags are short because the max
            // length of a material name is only 32 chars
            char materialName[MS_MAX_PATH+1];			
            msMaterial_GetName (msMat, materialName, MS_MAX_PATH);
            extractMaterialFlags(materialName, mat.flags);


            // now check for transparency < 1.0
             if (msMaterial_GetTransparency (msMat) < 1.0f)
                mat.flags |= Material::Translucent;
                
                

            materials.push_back(mat) ;
         }
      }

      // --------------------------------------------------------
      //  Nodes & Bones
      // --------------------------------------------------------

      Node n ;

      // Loop through first and import all the bones.  I assume here
      // that the parent joints always exist before their children.
      // The Torque engine certainly expects nodes to be organized
      // that way. Other parts of the code assume that Milkshape
      // Joint index = Torque node index
      assert(!nodes.size());
      for (int bone_num = 0 ; bone_num < numBones ; bone_num++)
      {
         char bone_name[256];
         char parent_name[256];
         msBone * bone = msModel_GetBoneAt (model, bone_num) ;
         msBone_GetName (bone, bone_name, 256) ;
         msBone_GetParentName (bone, parent_name, 256);

         // Create a node for this bone
         n.parent = msModel_FindBoneByName (model, parent_name); 
         n.name = addName (bone_name) ;
         nodes.push_back (n) ;

         // Create the default position and rotation for the node
         msVec3 pos ;
         msVec3 rot ;
         msBone_GetPosition (bone, pos) ;
         msBone_GetRotation (bone, rot) ;
         nodeDefTranslations.push_back(MilkshapePoint(pos) * config.scaleFactor) ;
         nodeDefRotations.push_back(MilkshapeQuaternion(rot)) ;
      }

      // Add a default "root" node for shapes.  All meshes need to
      // be assigned to a node and the Root will be used to catch
      // any mesh not assigned to a bone.
      n.name        = addName ("Root") ;
      n.parent      = -1;
      nodes.push_back(n) ;

      // Node indexes map one to one with the default rotation and
      // tranlation arrays: push identity transform for the Root.
      nodeDefRotations.push_back(Quaternion(0,0,0,1)) ;
      nodeDefTranslations.push_back(Point(0,0,0)) ;


      // --------------------------------------------------------
      //  Mesh objects.
      // --------------------------------------------------------

      Object o ;

      for (i = 0 ; i < numMeshes ; i++)
      {
         msMesh * mesh = msModel_GetMeshAt(model, i) ;
         msMesh_GetName (mesh, buffer, 510) ;
         int flags = extractMeshFlags(buffer);
	     
		    // Changed 01/16/2004 by Sven Knie
         // Ignore meshes reserved for collision
         if (!strnicmp(buffer,"Collision",9))
            continue;

		   // added 01/16/2004 by Sven Knie	
		   // Ignore meshes reserved for LoS collision
         if (!strnicmp(buffer,"LoSCollision", 11))
            continue;
			 	
         // Get object struct ready.  Objects are entities that
         // represent renderable items. Objects can have more
         // than one mesh to represent different detail levels.
         o.name      = addName(buffer) ;
         o.numMeshes = 1 ;
         o.firstMesh = meshes.size() ;
         o.node = nodes.size() - 1;

         // Process the raw data.
         meshes.push_back(MilkshapeMesh(mesh, o.node, config.scaleFactor, config.withMaterials)) ;
         Mesh& m = meshes.back();
         m.setFlag(flags);

         // Rigid meshes can be attached to a single node, in which
         // case we need to transform the vertices into the node's
         // local space.
         if (m.getType() == Mesh::T_Standard)
         {
            o.node = m.getNodeIndex(0);
            if (o.node != -1) {
               // Transform the mesh into node space.  The mesh vertices
               // must all be relative to the bone their attached to.
               Quaternion world_rot;
               Point world_trans;
               getNodeWorldPosRot(o.node,world_trans, world_rot);
               m.translate(-world_trans);
               m.rotate(world_rot.inverse());
            }
         }

         // Skin meshes need transform information to be able to
         // transform vertices into bone space (should fix the Torque
         // engine to calculate these at load time).
         if (m.getType() == Mesh::T_Skin)
         {
            for (int n = 0; n < m.getNodeIndexCount(); n++)
            {
               Quaternion world_rot;
               Point world_trans;
               getNodeWorldPosRot(m.getNodeIndex(n),world_trans, world_rot);
               m.setNodeTransform(n,world_trans,world_rot);
            }
         }

         objects.push_back(o) ;
      }

      // --------------------------------------------------------
      //  Detail levels 
      //  Not fully supported, we'll just export all our current
      //  stuff as single detail level.
      // --------------------------------------------------------

      DetailLevel dl ;
      dl.name         = addName("Detail-1") ;
      dl.size         = 0 ; // Projected size of detail level
      dl.objectDetail = 0 ;
      dl.polyCount    = 0 ;
      dl.subshape     = 0 ;
      dl.maxError     = -1 ;
      dl.avgError     = -1 ;

      std::vector<Mesh>::iterator mesh_ptr ;
      for (mesh_ptr = meshes.begin() ; mesh_ptr != meshes.end() ; mesh_ptr++)
         dl.polyCount += mesh_ptr->getPolyCount() ;

      detailLevels.push_back(dl) ;

      // --------------------------------------------------------
      //  Collision detail levels and meshes (optional)
      // --------------------------------------------------------

      // Update our bounds values as we might use them to build
      // a collision mesh.
      calculateBounds() ;
      calculateCenter() ;
      calculateRadius() ;
      calculateTubeRadius() ;

      // Export every mesh called "Collision" as a collision mesh.
      if (config.collisionType != Shape::C_None)
      {
         // Material for visible meshes
         int meshMaterial = materials.size();
         if (config.collisionVisible) 
         {
               Material mat ;
               mat.name        = "Collision_Mesh" ;
               mat.flags       = Material::NeverEnvMap | Material::SelfIlluminating;
               mat.reflectance = materials.size() ;
               mat.bump        = -1 ;
               mat.detail      = -1 ;
               mat.reflection  = 1.0f ;
               mat.detailScale = 1.0f ;
               materials.push_back(mat) ;
         }
         
         // Loop through all the meshes and extract those that will be used for collision
         if (config.collisionType == Shape::C_Mesh) 
         {
            int numCollisions = 1;
            for (i = 0 ; i < numMeshes ; i++)
            {
               msMesh * mesh = msModel_GetMeshAt(model, i);
               msMesh_GetName (mesh, buffer, 510) ;
               // Changed 01/16/2004 by Sven Knie
               bool colMesh = !strnicmp(buffer,"Collision", 9);
               bool losMesh = !strnicmp(buffer,"LoSCollision", 11);
               if (!colMesh && !losMesh)
                   continue;
               
               Mesh* collmesh = new MilkshapeMesh(mesh, nodes.size() - 1, config.scaleFactor, true) ;
            
               // Create the collision detail level
               char detailName[256];
               sprintf(detailName,colMesh? "Collision-%d": "LOS-%d", numCollisions++);
               dl.name         = addName(detailName) ;
               // Changed 01/16/2004 by Sven Knie
               dl.size         = (float)numCollisions * (-1) ; // negativ sized detail levels are never rendered
               dl.objectDetail = detailLevels.size() ;
               dl.subshape     = 0 ;
               dl.polyCount    = collmesh->getPolyCount() ;
               detailLevels.push_back(dl) ;
            
               // Create an object for the collision mesh and attach it to
               // the "Root" node.
               o.name         = colMesh? addName("Col"): addName("LoSCol") ;
               o.node         = nodes.size() - 1 ;
               o.firstMesh    = meshes.size() ;
               o.numMeshes    = dl.objectDetail + 1 ;
               objects.push_back(o) ;
      
               // Create a renderable copy of the collision meshes
               // for visible detail levels, or a null placeholder
               for (int d = 0 ; d < dl.objectDetail ; d++)
                  if (config.collisionVisible)
                  {
                     meshes.push_back(*collmesh) ;
                     meshes[meshes.size()-1].setMaterial(meshMaterial) ;
                  }
                  else
                     meshes.push_back(Mesh(Mesh::T_Null)) ;
      
               // Add the mesh to the list 
               meshes.push_back(*collmesh);
            }
         }
         else 
         {
            // Let's create the specified collision mesh type
            Mesh* collmesh;
            switch (config.collisionType)
            {
               case C_BBox:
                  collmesh = new BoxMesh(getBounds()) ;
                  break ;
               case C_Cylinder:
                  collmesh = new CylinderMesh(getBounds(), getTubeRadius(), config.collisionComplexity) ;
                  break ;
               default:
                  assert (0 && "Invalid collision mesh type") ;
            }
            
            // Create the collision detail level
            dl.name         = addName("Collision-1") ;
            dl.size         = -1 ; // -1 sized detail levels are never rendered
            dl.objectDetail = detailLevels.size() ;
            dl.subshape     = 0 ;
            dl.polyCount    = collmesh->getPolyCount() ;
            detailLevels.push_back(dl) ;
         
            // Create an object for the collision mesh and attach it to
            // the "Root" node.
            o.name         = addName("Col") ;
            o.node         = nodes.size() - 1 ;
            o.firstMesh    = meshes.size() ;
            o.numMeshes    = dl.objectDetail + 1 ;
            objects.push_back(o) ;
   
            // Create a renderable copy of the collision meshes
            // for visible detail levels, or a null placeholder
            for (int d = 0 ; d < dl.objectDetail ; d++)
               if (config.collisionVisible)
               {
                  meshes.push_back(*collmesh) ;
                  meshes[meshes.size()-1].setMaterial(meshMaterial) ;
               }
               else
                  meshes.push_back(Mesh(Mesh::T_Null)) ;
   
            // Add the mesh to the list 
            meshes.push_back(*collmesh);
         }
      }

      // --------------------------------------------------------
      //  Subshape and final stuff
      // --------------------------------------------------------

      // Create a subshape with everything

      Subshape s ;
      s.firstObject = 0 ;
      s.firstNode   = 0 ;
      s.firstDecal  = 0 ;
      s.numNodes    = nodes.size() ;
      s.numObjects  = objects.size() ;
      s.numDecals   = decals.size() ;
      subshapes.push_back(s) ;

      // Create an object state for each object (not sure about this)

      ObjectState os ;
      os.vis      = 1.0f ;
      os.frame    = 0 ;
      os.matFrame = 0 ;
      for (i = 0 ; i < objects.size() ; i++)
         objectStates.push_back(os) ;

      // Recalculate bounds (they may have changed)

      calculateBounds() ;
      calculateCenter() ;
      calculateRadius() ;
      calculateTubeRadius() ;

      setSmallestSize(config.minimumSize) ;

      // --------------------------------------------------------
      //  Animation (optional)

      // For each sequence, Torque wants an array of frame * nodes
      // information for all nodes affected by that sequence. Node
      // animation information can be translation, rotation, scale,
      // visibility, material, etc.
      //
      // The sequence contains a "matters" array for each type of
      // animation info.  Each type has it's own array of frame * nodes
      // which contains only the nodes affected by that type of
      // information for the sequence.  Since each array is NxN,
      // if a node is animated on a single frame of the sequence, it
      // will get an entry for every frame.

      // --------------------------------------------------------

      int frameCount = msModel_GetTotalFrames (model);
      if (frameCount && numBones && config.animation)
      {
         // Process all the sequences.
         for (int sc = 0; sc < config.sequence.size(); sc++)
         {
            MilkshapeShape::ImportConfig::Sequence& si = config.sequence[sc];

            // Build the exported sequence structure
            Sequence s;
            s.flags           = Sequence::UniformScale; 
            if (si.cyclic)
               s.flags |= Sequence::Cyclic;
            s.nameIndex       = addName(si.name) ;
            s.numKeyFrames    = si.end - si.start;
            s.duration        = float(s.numKeyFrames) / si.fps;
            s.baseTranslation = nodeTranslations.size() ;
            s.baseRotation    = nodeRotations.size();

            // Count how many nodes are affected by the sequence and
            // set the sequence.matter arrays to indicate which ones.
            s.matters.translation.assign (nodes.size(), false);
            s.matters.rotation.assign    (nodes.size(), false);
            int nodeCount = 0;
            for (i = 0 ; i < numBones ; i++)
            {
               msBone * bone = msModel_GetBoneAt(model, i);
               if (isBoneAnimated(bone,si.start,si.end))
               {
                  // Milkshape seems to always produce rotation & position
                  // keys in pairs, so we'll just deal with them together.
                  s.matters.translation[i] = true;
                  s.matters.rotation[i] = true;
                  nodeCount++;
               }
            }

            // Size arrays to hold keyframe * nodeCount
            nodeTranslations.resize (nodeTranslations.size() + nodeCount * s.numKeyFrames);
            nodeRotations.resize (nodeTranslations.size());

            // Set the keyframe data for each affected bone.  Unaffected
            // bones are skipped so the final NxN array of transforms
            // and rotations is "compressed", sort of.  We could compress
            // both the rotation and the position arrays individually,
            // but MS seems to always generate the values in pairs, so
            // we'll do them together. Though the msKey loops are seperate,
            // just in case.
            int index = 0;
            for (i = 0 ; i < numBones ; i++)
            {
               msBone * bone = msModel_GetBoneAt(model, i) ;
               if (isBoneAnimated(bone,si.start,si.end))
               {
                  int numPKeys  = msBone_GetPositionKeyCount(bone);
                  int numRKeys  = msBone_GetRotationKeyCount(bone);

                  // Insert translation keys into the table.
                  Point *translations = &nodeTranslations[s.baseTranslation + index * s.numKeyFrames];
                  int lastFrame = 0;

                  for (j = 0 ; j < numPKeys ; j++)
                  {
                     msPositionKey * msKey = msBone_GetPositionKeyAt (bone, j);
                     int frame = int(msKey->fTime) - 1;

                     // Only want keys in the sequence range. If it's before
                     // our range, we'll put it into the 0 frame in case we don't
                     // get a key for that frame later.
                     if (frame >= si.end)
                        break;
                     if (frame < si.start)
                        frame = 0;
                     else
                        frame -= si.start;

                     // Store the total translation for the node and fill in
                     // the initial frame if this is the first key frame.
                     translations[frame] = nodeDefTranslations[i] +
                        nodeDefRotations[i].apply(MilkshapePoint(msKey->Position) * config.scaleFactor);
                     if (!j && frame > 0)
                        translations[0] = translations[frame];

                     // Interpolate the missing frames.
                     for (int f = lastFrame + 1; f < frame; f++)
                        lerpTranslation(&translations[f],translations[lastFrame],
                           translations[frame], f, lastFrame, frame);

                     lastFrame = frame;
                  }

                  // Duplicate the last frame to the end.
                  for (int t = lastFrame + 1; t < s.numKeyFrames; t++)
                     translations[t] = translations[lastFrame];
   
                  // Insert rotation keys into the table.
                  Quaternion *rotations = &nodeRotations[s.baseTranslation + index * s.numKeyFrames];
                  lastFrame = 0;

                  for (j = 0 ; j < numRKeys ; j++)
                  {
                     msRotationKey * msKey = msBone_GetRotationKeyAt (bone, j) ;
                     int frame = int(msKey->fTime) - 1;
   
                     // Only want keys in the sequence range. If it's before
                     // our range, we'll put it into the 0 frame in case we don't
                     // get a key for that frame later.
                     if (frame >= si.end)
                        break;
                     if (frame < si.start)
                        frame = 0;
                     else
                        frame -= si.start;

                     // Store the total rotation for the node and fill in
                     // the initial frame if this is the first key frame.
                     rotations[frame] = MilkshapeQuaternion(msKey->Rotation) * nodeDefRotations[i];
                     if (!j && frame > 0)
                        rotations[0] = rotations[frame];

                     // Interpolate the missing frames.
                     for (int f = lastFrame + 1; f < frame; f++)
                        lerpRotation(&rotations[f],rotations[lastFrame],
                           rotations[frame], f, lastFrame, frame);

                     lastFrame = frame;
                  }

                  // Duplicate the last frame to the end.
                  for (int r = lastFrame + 1; r < s.numKeyFrames; r++)
                     rotations[r] = rotations[lastFrame];

                  // Increment the position & rotation array index
                  index++;
               }
            }

            sequences.push_back(s);
         }
      }
   }

} // DTS namespace
