#include "xentax.h"
#include "x_lwo.h"
#include "x_smc.h"

struct SMCJointInfo {
 uint32 listindex;
 std::string name;
};

class SMCJointVisitor : public JOINTVISITOR {
 private :
  std::map<uint32, SMCJointInfo> indexmap;
  std::deque<lwVertex3D> pnts;
  std::deque<lwSkelegon> pols;
 private :
  SMCJointVisitor(const SMCJointVisitor&);
  void operator =(const SMCJointVisitor&);
 public :
  SMCJointVisitor() {}
 ~SMCJointVisitor() {}
 public :
  uint32 joints(void)const
  {
   return pnts.size();
  }
  uint32 bones(void)const
  {
   return pols.size();
  }
  boost::shared_array<lwVertex3D> createJoints(void)const
  {
   boost::shared_array<lwVertex3D> data(new lwVertex3D[pnts.size()]);
   for(size_t i = 0; i < pnts.size(); i++) {
       data[i].x = pnts[i].x;
       data[i].y = pnts[i].y;
       data[i].z = pnts[i].z;
      }
   return data;
  }
  boost::shared_array<lwSkelegon> createBones(void)const
  {
   boost::shared_array<lwSkelegon> data(new lwSkelegon[pols.size()]);
   for(size_t i = 0; i < pols.size(); i++) {
       data[i].a = pols[i].a;
       data[i].b = pols[i].b;
       data[i].name = pols[i].name;
      }
   return data;
  }
  return_type operator ()(argument1 joint, argument2 index)
  {
   // joint must be valid
   if(index == INVALID_JOINT) return false;

   // initialize position
   lwVertex3D pos;
   pos.x = joint.rel_x;
   pos.y = joint.rel_y;
   pos.z = joint.rel_z;

   // save bone
   if(joint.parent != INVALID_JOINT)
     {
      // get parent information
      std::map<uint32, SMCJointInfo>::iterator iter = indexmap.find(joint.parent);
      if(iter == indexmap.end()) return false;
      SMCJointInfo parent_info = iter->second;

      // convert to absolute position
      pos.x += pnts[parent_info.listindex].x;
      pos.y += pnts[parent_info.listindex].y;
      pos.z += pnts[parent_info.listindex].z;

      // save bone
      lwSkelegon bone;
      bone.a = parent_info.listindex;
      bone.b = pnts.size();
      bone.name += parent_info.name;
      bone.name += "_";
      bone.name += joint.name;
      pols.push_back(bone);
     }

   // save point information
   SMCJointInfo info;
   info.listindex = pnts.size();
   info.name = joint.name;
   indexmap.insert(std::map<uint32, SMCJointInfo>::value_type(index, info));

   // save point
   pnts.push_back(pos);
   return true;
  }
};

template<class T>
bool SaveTriangleList(const SMC_INDEX_BUFFER& ib, boost::shared_array<lwTriangle>& facedata)
{
 // validate
 if(ib.elem < 3) return error("Index buffer is not a triangle list.");
 if(ib.elem % 3) return error("Index buffer is not a triangle list.");
 if(ib.type != FACE_TYPE_TRIANGLES) return error("Index buffer is not a triangle list.");

 // pointer to data
 const T* data = reinterpret_cast<const T*>(ib.data.get());
 if(!data) return error("Invalid data pointer.");

 // transfer data
 uint32 n_triangles = ib.elem/3;
 uint32 index = 0;
 for(uint32 i = 0; i < n_triangles; i++) {
     facedata[i].a = data[index++];
     facedata[i].b = data[index++];
     facedata[i].c = data[index++];
    }

 return true;
}

template<class T>
bool SaveTriangleStrip(const SMC_INDEX_BUFFER& ib, boost::shared_array<lwTriangle>& facedata)
{
 // validate
 if(ib.elem < 3) return error("Index buffer is not a triangle strip.");
 if(ib.type != FACE_TYPE_TRISTRIP) return error("Index buffer is not a triangle strip.");

 // pointer to data
 const T* data = reinterpret_cast<const T*>(ib.data.get());
 if(!data) return error("Invalid data pointer.");

 // first triangle
 uint32 a = facedata[0].a = data[0];
 uint32 b = facedata[0].b = data[1];
 uint32 c = facedata[0].c = data[2];

 // other triangles
 uint32 n_triangles = ib.elem - 2;
 for(size_t i = 1; i < n_triangles; i++)
    {
     // next triangle
     a = b;
     b = c;
     c = data[i];

     // winding
     if(!(a == b || a == c || b == c)) {
        if(i % 2) {
           facedata[i].a = a;
           facedata[i].b = c;
           facedata[i].c = b;
          }
        else {
           facedata[i].a = a;
           facedata[i].b = b;
           facedata[i].c = c;
          }
       }
    }

 return true;
}

bool SaveLWO(const char* path, const char* name, const SIMPLEMODELCONTAINER& data)
{
 // validate
 using namespace std;
 if(!path || !strlen(path)) return error("SaveLWO: A path must be specified.");
 if(!name || !strlen(name)) return error("SaveLWO: A name must be specified.");

 // data
 boost::shared_array<lwVertex3D> vd; // PNTS data
 boost::shared_array<lwVertex2D> ud; // TXUV data
 boost::shared_array<lwTriangle> fd; // POLS data

 // save LAYR
 lwFileSaver saver;
 saver.insertLayr(0, nullptr);

 // save CLIP
 for(size_t i = 0; i < data.textures.size(); i++)
     saver.insertClip((i + 1), data.textures[i].filename.c_str());

 // save PNTS
 if(data.vbuffer.elem) {
    // save xyz
    if(data.vbuffer.flags & VERTEX_POSITION) {
       vd.reset(new lwVertex3D[data.vbuffer.elem]);
       for(uint32 i = 0; i < data.vbuffer.elem; i++) {
           vd[i].x = data.vbuffer.data[i].vx;
           vd[i].y = data.vbuffer.data[i].vy;
           vd[i].z = data.vbuffer.data[i].vz;
          }
       saver.insertPnts(0, 0, vd, data.vbuffer.elem);
      }
    // save uv
    if(data.vbuffer.flags & VERTEX_UV) {
       ud.reset(new lwVertex2D[data.vbuffer.elem]);
       for(uint32 i = 0; i < data.vbuffer.elem; i++) {
           ud[i].x = data.vbuffer.data[i].tu;
           ud[i].y = data.vbuffer.data[i].tv;
          }
      }
    // save weights
    if(data.vbuffer.flags & VERTEX_WEIGHTS)
      {
       if(data.vbuffer.flags & VERTEX_BONEIDX)
         {
          // TODO:
         }
       else if(data.vbuffer.flags & VERTEX_WMAPIDX)
         {
          // construct WGHT maps
          typedef map<uint32, deque<lwRefVertex1D>> wmap_type;
          wmap_type wmap;
          for(uint32 i = 0; i < data.vbuffer.elem; i++) {
              for(uint32 j = 0; j < 8; j++) {
                  if(data.vbuffer.data[i].mi[j] != INVALID_VERTEX_WMAP_INDEX) {
                     wmap_type::iterator iter = wmap.find(data.vbuffer.data[i].mi[j]);
                     if(iter == wmap.end()) iter = wmap.insert(wmap_type::value_type(data.vbuffer.data[i].mi[j], deque<lwRefVertex1D>())).first;
                     lwRefVertex1D item;
                     item.reference = i;
                     item.x = data.vbuffer.data[i].wv[j]/65535.0f;
                     iter->second.push_back(item);
                    }
                 }
             }

          // save WGHT maps
          for(wmap_type::iterator iter = wmap.begin(); iter != wmap.end(); iter++) {
              if(iter->second.size()) {
                 boost::shared_array<lwRefVertex1D> wset(new lwRefVertex1D[iter->second.size()]);
                 uint32 index = 0;
                 for(size_t i = 0; i < iter->second.size(); i++) {
                     wset[index].reference = iter->second[i].reference;
                     wset[index].x = iter->second[i].x;
                     index++;
                    }
                 stringstream ss;
                 ss << "wmap_" << setfill('0') << setw(3) << iter->first;
                 saver.insertWght(0, 0, ss.str().c_str(), wset, iter->second.size());
                }
             }
         }
      }
   }

 // save POLS
 for(uint32 i = 0; i < data.ibuffer.size(); i++)
    {
     const SMC_INDEX_BUFFER& ib = data.ibuffer[i];
     if(ib.type == FACE_TYPE_TRIANGLES) {
        uint32 n_triangles = ib.elem/3;
        fd.reset(new lwTriangle[n_triangles]);
        if(ib.format == FACE_FORMAT_UINT_08) SaveTriangleList<uint08>(ib, fd);
        else if(ib.format == FACE_FORMAT_UINT_16) SaveTriangleList<uint16>(ib, fd);
        else if(ib.format == FACE_FORMAT_UINT_32) SaveTriangleList<uint32>(ib, fd);
        saver.insertPols(0, 0, ib.name.c_str(), fd, n_triangles);
       }
     else if(ib.type == FACE_TYPE_TRISTRIP) {
        uint32 n_triangles = ib.elem - 2;
        fd.reset(new lwTriangle[n_triangles]);
        if(ib.format == FACE_FORMAT_UINT_08) SaveTriangleStrip<uint08>(ib, fd);
        else if(ib.format == FACE_FORMAT_UINT_16) SaveTriangleStrip<uint16>(ib, fd);
        else if(ib.format == FACE_FORMAT_UINT_32) SaveTriangleStrip<uint32>(ib, fd);
        saver.insertPols(0, 0, ib.name.c_str(), fd, n_triangles);
       }

     // save TXUV
    if(data.vbuffer.flags & VERTEX_UV)
      {
       // construct TXUV map
       map<uint32, lwVertex2D> uvmap;
       uint32 n_triangles = ib.elem/3;
       for(uint32 j = 0; j < n_triangles; j++) {
           uint32 ref_a = fd[j].a;
           uint32 ref_b = fd[j].b;
           uint32 ref_c = fd[j].c;
           lwVertex2D val_a = ud[ref_a];
           lwVertex2D val_b = ud[ref_b];
           lwVertex2D val_c = ud[ref_c];
           uvmap.insert(map<uint32, lwVertex2D>::value_type(ref_a, val_a));
           uvmap.insert(map<uint32, lwVertex2D>::value_type(ref_b, val_b));
           uvmap.insert(map<uint32, lwVertex2D>::value_type(ref_c, val_c));
          }

       // save TXUV map
       boost::shared_array<lwRefVertex2D> uvset(new lwRefVertex2D[uvmap.size()]);
       uint32 index = 0;
       for(map<uint32, lwVertex2D>::iterator iter = uvmap.begin(); iter != uvmap.end(); iter++) {
           uvset[index].reference = iter->first;
           uvset[index].x = iter->second.x;
           uvset[index].y = iter->second.y;
           index++;
          }
       saver.insertTxuv(0, 0, ib.name.c_str(), uvset, uvmap.size());

       // enable texture mapping
       if(ib.material != INVALID_MATERIAL)
         {
          // enable material
          const SMC_MATERIAL& material = data.materials[ib.material];
          if(material.basemap != INVALID_TEXTURE_INDEX) {
             saver.enableSurfColrImag(ib.name.c_str());
             saver.setSurfColrVmap(ib.name.c_str(), ib.name.c_str());
             saver.setSurfColrImag(ib.name.c_str(), material.basemap + 1);
            }
          if(material.specmap != INVALID_TEXTURE_INDEX) {
             saver.enableSurfSpecImag(ib.name.c_str());
             saver.setSurfSpecVmap(ib.name.c_str(), ib.name.c_str());
             saver.setSurfSpecImag(ib.name.c_str(), material.specmap + 1);
            }
          if(material.tranmap != INVALID_TEXTURE_INDEX) {
             saver.enableSurfTranImag(ib.name.c_str());
             saver.setSurfTranVmap(ib.name.c_str(), ib.name.c_str());
             saver.setSurfTranImag(ib.name.c_str(), material.tranmap + 1);
             saver.setSurfTranNega(ib.name.c_str(), 1); // TODO: remove
            }
         }
      }
    }

 // save SKEL
 for(size_t i = 0; i < data.skeletons.size(); i++)
    {
     // skeleton identifier
     const SKELETON& item = data.skeletons[i];

     // traverse joints
     SMCJointVisitor jv;
     item.traverse(jv);
     if(jv.joints() != item.joints()) return error("Invalid skeleton.");

     // insert skeleton
     saver.insertJnts(0, i, jv.createJoints(), jv.joints());
     saver.insertSkel(0, 0, item.get_identifier(), jv.createBones(), jv.bones());
    }

 // save file
 std::stringstream ss;
 ss << path << name << ".lwo";
 return saver.save(ss.str().c_str());
}