#include "xentax.h"
#include "ps3_macross_frontier.h"

// failed
// 03057802.hld on 3rd surface

namespace PS3 { namespace MacrossFrontier {

 class extractor {
  private :
   std::string pathname;
   std::ifstream ifile;
   uint32 curr_model;
   std::deque<VERTEX_BUFFER> curr_vd;
   std::deque<INDEX_BUFFER> curr_fd;
  private :
   bool processHLD(const char* filename);
   bool processHRTM(uint32 param01, uint32 param02, uint32 param03, uint32 param04);
   bool processHARF(uint32 param01, uint32 param02, uint32 param03, uint32 param04);
   bool processLKSE(uint32 param01, uint32 param02, uint32 param03, uint32 param04);
   bool processMARF(uint32 param01, uint32 param02, uint32 param03, uint32 param04);
   bool process_XET(uint32 param01, uint32 param02, uint32 param03, uint32 param04);
   bool process2MDB(uint32 param01, uint32 param02, uint32 param03, uint32 param04);
   bool process2HMG(uint32 param01, uint32 param02, uint32 param03, uint32 param04);
   bool process_2MG(uint32 param01, uint32 param02, uint32 param03, uint32 param04);
   bool processTPMG(uint32 param01, uint32 param02, uint32 param03, uint32 param04);
  public :
   bool extract(void);
  public :
   extractor(const char* pn);
  ~extractor();
 };

};}; // PS3::MacrossFrontier

namespace PS3 { namespace MacrossFrontier {

 extractor::extractor(const char* pn)
 {
  pathname = pn;
  curr_model = 0;
 }
 
 extractor::~extractor()
 {
 }

 bool extractor::extract(void)
 {
  std::deque<std::string> namelist;
  BuildFilenameList(namelist, "hld", pathname.c_str());
  for(size_t i = 0; i < namelist.size(); i++) {
      cout << "Processing file " << i << " of " << namelist.size() << ": " << namelist[i] << "." << endl;
      if(!processHLD(namelist[i].c_str())) return false;
     }
  return true;
 }

 bool extractor::processHLD(const char* filename)
 {
  // open file
  ifile.open(filename, ios::binary);
  if(!ifile) return error("Failed to open file.");

  // read HLDM
  uint32 hldm = BE_read_uint32(ifile);
  if(hldm != 0x484C444D) return error("Expecting HLDM.");

  // read HLDM parameters
  uint32 HLDM_param01 = BE_read_uint32(ifile); // 0x08
  uint32 HLDM_param02 = BE_read_uint32(ifile); // 0x01
  uint32 HLDM_param03 = BE_read_uint32(ifile); // 0x08
  uint32 HLDM_param04 = BE_read_uint32(ifile); // _LDM
  uint32 HLDM_param05 = BE_read_uint32(ifile); // 0x01

  if(HLDM_param04 != 0x5F4C444D) return error("Expecting _LDM.");
  if(HLDM_param05 != 0x00000001) return error("Expecting 0x01.");

  for(;;)
     {
      // read chunkname
      uint32 param01 = BE_read_uint32(ifile);
      uint32 param02 = BE_read_uint32(ifile);
      uint32 param03 = BE_read_uint32(ifile);
      uint32 param04 = BE_read_uint32(ifile);

      // save position
      uint32 position = (uint32)ifile.tellg();
      if(ifile.fail()) return error("Failed to tell position.");

      // DONE chunk
      if(param01 == 0x20444E45)
        {
         std::string shortname = GetShortFilenameWithoutExtension(filename);
         bool success = GeometryToOBJ(GetPathnameFromFilename(filename).c_str(), shortname.c_str(), curr_vd, curr_fd);
         curr_model = 0; // actually current surface
         curr_vd.clear();
         curr_fd.clear();
         ifile.close();
         if(!success) return false;
         break;
        }
      // HRTM chunk
      else if(param01 == 0x4852544D)
        {
         if(!processHRTM(param01, param02, param03, param04)) return false;
        }
      // HARF chunk
      else if(param01 == 0x48415246)
        {
         if(!processHARF(param01, param02, param03, param04)) return false;
        }
      // LKSE chunk
      else if(param01 == 0x4C4B5345)
        {
         if(!processLKSE(param01, param02, param03, param04)) return false;
        }
      // _XET chunk
      else if(param01 == 0x20584554)
        {
         if(!process_XET(param01, param02, param03, param04)) return false;
        }
      // 2MDB chunk
      else if(param01 == 0x324D4442)
        {
         if(!process2MDB(param01, param02, param03, param04)) return false;
        }
      // 2HMG chunk
      else if(param01 == 0x32484D47)
        {
         if(!process2HMG(param01, param02, param03, param04)) return false;
        }
      // HMTA chunk
      else if(param01 == 0x484D5441) {}
      // ADOL chunk
      else if(param01 == 0x41444F4C) {}
      // DNBS chunk
      else if(param01 == 0x444E4253) {}
      else
         return error("Unknown chunk.");

      // move to next section
      ifile.seekg(position);
      ifile.seekg(param02, ios::cur);
      if(ifile.fail()) return error("Failed to seek position.");
     }

  return true;
 }

 bool extractor::processHRTM(uint32 param01, uint32 param02, uint32 param03, uint32 param04)
 {
  return true;
 }

 bool extractor::processHARF(uint32 param01, uint32 param02, uint32 param03, uint32 param04)
 {
  // number of MARF sections
  uint32 n_marf = BE_read_uint32(ifile);
  if(n_marf < 1 || n_marf > 999) return error("Unexpected number of MARF chunks.");

  // for each MARF section
  for(size_t i = 0; i < n_marf; i++)
     {
      // read MARF section
      uint32 param01 = BE_read_uint32(ifile);
      uint32 param02 = BE_read_uint32(ifile); // size of data after unk03
      uint32 param03 = BE_read_uint32(ifile); // 0x01
      uint32 param04 = BE_read_uint32(ifile); // size of data after unk03 again
      if(param01 != 0x4D415246) return error("Expecting MARF.");

      // save position
      uint32 position = (uint32)ifile.tellg();
      if(ifile.fail()) return error("Failed to tell position.");

      // process MARF data
      processMARF(param01, param02, param03, param04);
 
      // move to next section
      ifile.seekg(position);
      ifile.seekg(param02, ios::cur);
      if(ifile.fail()) return error("Failed to seek position.");
     }

  return true;
 }

 bool extractor::processLKSE(uint32 param01, uint32 param02, uint32 param03, uint32 param04)
 {
  // skip skeleton data for now
  return true;
 }

 bool extractor::processMARF(uint32 param01, uint32 param02, uint32 param03, uint32 param04)
 {
  return true;
 }

 bool extractor::process_XET(uint32 param01, uint32 param02, uint32 param03, uint32 param04)
 {
  uint32 n_textures = param03; // number of textures
  uint32 buffersize = param04; // filename sizes
  for(size_t i = 0; i < n_textures; i++) {
      char buffer[1024];
      ifile.read(buffer, buffersize);
     }
  return true;
 }

 bool extractor::process2MDB(uint32 param01, uint32 param02, uint32 param03, uint32 param04)
 {
  return true;
 }

 bool extractor::process2HMG(uint32 param01, uint32 param02, uint32 param03, uint32 param04)
 {
  // process _2MG sections
  uint32 n_sections = param03;
  for(size_t i = 0; i < n_sections; i++)
     {
      // read _2MG section
      uint32 param01 = BE_read_uint32(ifile);
      uint32 param02 = BE_read_uint32(ifile);
      uint32 param03 = BE_read_uint32(ifile);
      uint32 param04 = BE_read_uint32(ifile);
      if(param01 != 0x20324D47) return error("Expecting 2MG.");

      // save position
      uint32 position = (uint32)ifile.tellg();
      if(ifile.fail()) return error("Failed to tell position.");

      // process _2MG data
      if(!process_2MG(param01, param02, param03, param04))
         return false;
 
      // move to next section
      ifile.seekg(position);
      ifile.seekg(param02, ios::cur);
      if(ifile.fail()) return error("Failed to seek position.");
     }

  return true;
 }

 bool extractor::process_2MG(uint32 param01, uint32 param02, uint32 param03, uint32 param04)
 {
  // skip unknown data
  ifile.seekg(0x50, ios::cur);
  if(ifile.fail()) return error("Seek failure.");

  // process TPMG sections
  uint32 n_sections = param03;
  for(size_t i = 0; i < n_sections; i++)
     {
      // read TPMG section
      uint32 param01 = BE_read_uint32(ifile);
      uint32 param02 = BE_read_uint32(ifile);
      uint32 param03 = BE_read_uint32(ifile);
      uint32 param04 = BE_read_uint32(ifile);
      if(param01 != 0x54504D47) return error("Expecting TPMG.");

      // save position
      uint32 position = (uint32)ifile.tellg();
      if(ifile.fail()) return error("Failed to tell position.");

      // process TPMG data
      if(!processTPMG(param01, param02, param03, param04))
          return false;
 
      // move to next section
      ifile.seekg(position);
      ifile.seekg(param02, ios::cur);
      if(ifile.fail()) return error("Failed to seek position.");
     }

  return true;
 }

 bool extractor::processTPMG(uint32 param01, uint32 param02, uint32 param03, uint32 param04)
 {
  // save position
  uint32 position = (uint32)ifile.tellg();
  if(ifile.fail()) return error("Failed to tell position.");

  // unknown
  uint16 u01 = BE_read_uint16(ifile); // 0x050F (1295)
  uint16 u02 = BE_read_uint16(ifile); // 0x0606 (1542)
  uint16 u03 = BE_read_uint16(ifile); // 0x10
  uint16 u04 = BE_read_uint16(ifile); // 0x00

  // number of points and indices
  uint16 n_point = BE_read_uint16(ifile); // number of points
  uint16 n_index = BE_read_uint16(ifile); // number of shorts (exact, no padding)
  BE_read_uint32(ifile); // 0xFFFFFFFF

  // offsets to points and indices
  uint32 ib_offset = BE_read_uint32(ifile);
  uint16 ib_size = BE_read_uint16(ifile);
  BE_read_uint16(ifile);
  uint32 vb_offset = BE_read_uint32(ifile); // offset from position to point buffer
  BE_read_uint16(ifile);
  uint32 vb_size = BE_read_uint32(ifile); // size of vertex data in bytes aligned to 16-byte boundary

  // move to index buffer
  ifile.seekg(position);
  ifile.seekg(ib_offset, ios::cur);

  // read index buffer
  INDEX_BUFFER ib;
  ib.format = FACE_FORMAT_UINT_16;
  ib.type = FACE_TYPE_TRIANGLES;
  ib.name = "";
  ib.reference = 0;
  ib.elem = n_index;
  ib.data.reset(reinterpret_cast<char*>(new uint16[ib.elem]));
  BE_read_array(ifile, reinterpret_cast<uint16*>(ib.data.get()), ib.elem);

  // move to point buffer
  ifile.seekg(position);
  ifile.seekg(vb_offset, ios::cur);
  if(ifile.fail()) return error("Failed to seek position.");

  // u1 = 0x050D -> has some extra vertexes
  // u1 = 0x050F
  // u2 = 0x0606 -> 1C
  if(u01 == 0x050D && u02 == 0x0606) ;
  if(u01 == 0x050E && u02 == 0x0606) ;
  if(u01 == 0x050F && u02 == 0x0606) ;

  // compute number of vertices manually because header data is somewhat unreliable
  uint16 n_vertices = 0;
  maximum(reinterpret_cast<uint16*>(ib.data.get()), n_index, n_vertices);
  n_vertices = n_vertices + 1;

  // initialize vertex buffer
  VERTEX_BUFFER vb;
  vb.flags = VERTEX_POSITION | VERTEX_UV;
  vb.elem = n_vertices;
  vb.data.reset(new VERTEX[vb.elem]);

  // read point buffer
  for(size_t i = 0; i < n_vertices; i++)
     {
      // 16 bytes
      if((u01 >= 0x0209 && u01 <= 0x020F) && u02 == 0x0C08)
        {
         vb.data[i].vx = BE_read_float32(ifile); // 4
         vb.data[i].vy = BE_read_float32(ifile); // 8
         vb.data[i].vz = BE_read_float32(ifile); // 12
         vb.data[i].tu = BE_read_uint16(ifile)/65535.0f; // 14
         vb.data[i].tv = 1.0f - BE_read_uint16(ifile)/65535.0f; // 16
        }
      // 16 bytes
      else if((u01 >= 0x0209 && u01 <= 0x020F) && u02 == 0x0707)
        {
         vb.data[i].vx = BE_read_float32(ifile); // 4
         vb.data[i].vy = BE_read_float32(ifile); // 8
         vb.data[i].vz = BE_read_float32(ifile); // 12
         vb.data[i].tu = BE_read_float16(ifile); // 14
         vb.data[i].tv = 1.0f - BE_read_float16(ifile); // 16
        }
      // 20 bytes
      else if((u01 >= 0x030A && u01 <= 0x0311) && u02 == 0x0D09)
        {
         vb.data[i].vx = BE_read_float32(ifile); // 4
         vb.data[i].vy = BE_read_float32(ifile); // 8
         vb.data[i].vz = BE_read_float32(ifile); // 12
         BE_read_float16(ifile); // 14
         BE_read_float16(ifile); // 16
         vb.data[i].tu = BE_read_float16(ifile); // 18
         vb.data[i].tv = 1.0f - BE_read_float16(ifile); // 20
        }
      // 20 bytes
      else if((u01 >= 0x030A && u01 <= 0x030F) && u02 == 0x0F0B)
        {
         vb.data[i].vx = BE_read_float32(ifile); // 4
         vb.data[i].vy = BE_read_float32(ifile); // 8
         vb.data[i].vz = BE_read_float32(ifile); // 12
         BE_read_float16(ifile); // 14
         BE_read_float16(ifile); // 16
         vb.data[i].tu = BE_read_float16(ifile); // 18
         vb.data[i].tv = 1.0f - BE_read_float16(ifile); // 20
        }
      else if(u01 == 0x030A && u02 == 0x100C) // different format!
        {
         vb.data[i].vx = BE_read_float32(ifile); // 4
         vb.data[i].vy = BE_read_float32(ifile); // 8
         vb.data[i].vz = BE_read_float32(ifile); // 12
         BE_read_uint16(ifile); // 14
         BE_read_uint16(ifile); // 16
         vb.data[i].tu = BE_read_uint16(ifile)/127.0f; // 18
         vb.data[i].tv = 1.0f - BE_read_uint16(ifile)/127.0f; // 20
        }
      else if((u01 >= 0x040B && u01 <= 0x0411) && u02 == 0x0303)
        {
         vb.data[i].vx = BE_read_float32(ifile); // 4
         vb.data[i].vy = BE_read_float32(ifile); // 8
         vb.data[i].vz = BE_read_float32(ifile); // 12
         BE_read_float16(ifile); // 14
         BE_read_float16(ifile); // 16
         BE_read_float16(ifile); // 18
         BE_read_float16(ifile); // 20
         vb.data[i].tu = BE_read_float16(ifile); // 22
         vb.data[i].tv = 1.0f - BE_read_float16(ifile); // 24
        }
      else if((u01 >= 0x040B && u01 <= 0x040F) && u02 == 0x110D)
        {
         vb.data[i].vx = BE_read_float32(ifile); // 4
         vb.data[i].vy = BE_read_float32(ifile); // 8
         vb.data[i].vz = BE_read_float32(ifile); // 12
         BE_read_float16(ifile); // 14
         BE_read_float16(ifile); // 16
         BE_read_float16(ifile); // 18
         BE_read_float16(ifile); // 20
         vb.data[i].tu = BE_read_uint16(ifile)/65535.0f; // 22
         vb.data[i].tv = 1.0f - BE_read_uint16(ifile)/65535.0f; // 24
        }

      // 28 bytes
      else if((u01 >= 0x050C && u01 <= 0x0512) && u02 == 0x0606)
        {
         vb.data[i].vx = BE_read_float32(ifile); // 4
         vb.data[i].vy = BE_read_float32(ifile); // 8
         vb.data[i].vz = BE_read_float32(ifile); // 12
         BE_read_float16(ifile); // 16
         BE_read_float16(ifile); // 16
         BE_read_float16(ifile); // 20
         BE_read_float16(ifile); // 20
         BE_read_float16(ifile); // 24
         BE_read_float16(ifile); // 24
         vb.data[i].tu = BE_read_float16(ifile); // 28
         vb.data[i].tv = 1.0f - BE_read_float16(ifile); // 28
        }
      // 28 bytes
       else if((u01 >= 0x050C && u01 <= 0x0512) && u02 == 0x120E) // architecture format... 03b00002.hld
        {
         vb.data[i].vx = BE_read_float32(ifile); // 4
         vb.data[i].vy = BE_read_float32(ifile); // 8
         vb.data[i].vz = BE_read_float32(ifile); // 12
         BE_read_float16(ifile); // 16
         BE_read_float16(ifile); // 16
         BE_read_float16(ifile); // 20
         BE_read_float16(ifile); // 20
         BE_read_float16(ifile); // 24
         BE_read_float16(ifile); // 24
         vb.data[i].tu = BE_read_uint16(ifile)/65535.0f; // 28
         vb.data[i].tv = 1.0f - BE_read_uint16(ifile)/65535.0f; // 28
        }

       // 32 bytes
       else if((u01 >= 0x060D && u01 <= 0x0612) && u02 == 0x130F)
        {
         vb.data[i].vx = BE_read_float32(ifile); // 4
         vb.data[i].vy = BE_read_float32(ifile); // 8
         vb.data[i].vz = BE_read_float32(ifile); // 12
         BE_read_float16(ifile); // 16
         BE_read_float16(ifile);
         BE_read_float16(ifile); // 20
         BE_read_float16(ifile);
         BE_read_float16(ifile); // 24
         BE_read_float16(ifile);
         BE_read_float16(ifile); // 28
         BE_read_float16(ifile);
         vb.data[i].tu = BE_read_float16(ifile); // 32
         vb.data[i].tv = 1.0f - BE_read_float16(ifile);
        }
       // 32 bytes
       else if((u01 >= 0x060D && u01 <= 0x0612) && u02 == 0x1713)
        {
         vb.data[i].vx = BE_read_float32(ifile); // 4
         vb.data[i].vy = BE_read_float32(ifile); // 8
         vb.data[i].vz = BE_read_float32(ifile); // 12
         BE_read_float16(ifile); // 16
         BE_read_float16(ifile);
         BE_read_float16(ifile); // 20
         BE_read_float16(ifile);
         BE_read_float16(ifile); // 24
         BE_read_float16(ifile);
         BE_read_float16(ifile); // 28
         BE_read_float16(ifile);
         vb.data[i].tu = BE_read_float16(ifile); // 32
         vb.data[i].tv = 1.0f - BE_read_float16(ifile);
        }
       // 32 bytes
       else if((u01 >= 0x060D && u01 <= 0x0612) && u02 == 0x1814)
        {
         vb.data[i].vx = BE_read_float32(ifile); // 4
         vb.data[i].vy = BE_read_float32(ifile); // 8
         vb.data[i].vz = BE_read_float32(ifile); // 12
         BE_read_float16(ifile); // 16
         BE_read_float16(ifile);
         BE_read_float16(ifile); // 20
         BE_read_float16(ifile);
         BE_read_float16(ifile); // 24
         BE_read_float16(ifile);
         BE_read_float16(ifile); // 28
         BE_read_float16(ifile);
         vb.data[i].tu = BE_read_float16(ifile); // 32
         vb.data[i].tv = 1.0f - BE_read_float16(ifile);
        }
       // 36 bytes
       else if((u01 >= 0x070E && u01 <= 0x0713) && u02 == 0x1612)
        {
         vb.data[i].vx = BE_read_float32(ifile); // 4
         vb.data[i].vy = BE_read_float32(ifile); // 8
         vb.data[i].vz = BE_read_float32(ifile); // 12
         BE_read_float16(ifile); // 16
         BE_read_float16(ifile);
         BE_read_float16(ifile); // 20
         BE_read_float16(ifile);
         BE_read_float16(ifile); // 24
         BE_read_float16(ifile);
         BE_read_float16(ifile); // 28
         BE_read_float16(ifile);
         BE_read_float16(ifile); // 32
         BE_read_float16(ifile);
         vb.data[i].tu = BE_read_float16(ifile); // 36
         vb.data[i].tv = 1.0f - BE_read_float16(ifile);
        }
       else {
         cout << u01 << "," << u02 << endl;
         return error("Unknown vertex format.");
        }
     }

  // finish model properties
  stringstream ss;
  ss << "surface_" << setfill('0') << setw(3) << curr_model;
  ib.name = ss.str();
  ib.reference = curr_model;

  // insert model data
  curr_vd.push_back(vb);
  curr_fd.push_back(ib);

  // increment model number
  curr_model++;
  return true;
 }

};}; // PS3::MacrossFrontier

namespace PS3 { namespace MacrossFrontier {

bool extract(void)
{
 char pathname[MAX_PATH];
 GetModulePathname(pathname, MAX_PATH);
 return extract(pathname);
}

bool extract(const char* pathname)
{
 return extractor(pathname).extract();
}

};}; // PS3::MacrossFrontier