#ifdef __linux__
#  include <sys/stat.h>
#  include <sys/types.h>
#else
#  include <direct.h>
#endif

#include <sstream>
#include <vector>
#include <fstream>
#include <iostream>

int create_dir(const std::string& path)
{
#ifdef __linux__
  return mkdir(path.c_str(), 0755);
#else
  return _mkdir(path.c_str());
#endif
}

struct FileEntry
{
  int offset;
  int filesize;

  // Those could be analog to:
  // FileCount:    923
  // NumberCount:  371
  // ByteCount:   4598
  
  // if filesize is != 0 unknown1 is 0
  //                  range  : count
  int unknown1;  // ~ 0-897  : 289 (like number1, reference to other file?)
  int unknown2;  // ~ 0-85   : 78  (always ~100 range)
  int unknown3;  // ~ 0-4585 : 427 (like number3)
};

struct TLJPak
{
  char magic[12];
  int  file_count;   // file entries
  int  number_count; // number of entries in the second and last section
  int  byte_count;   // a count that is similar to the last number in numbers
  
  std::vector<FileEntry> files;   // array of FileEntries with length file_count
  std::vector<char>      bytes;   // array of bytes with length byte_count, values are never larger then 43
  std::vector<int>       numbers; // array of int32 with length number_count

  TLJPak(std::istream& in) 
  {
    in.read(reinterpret_cast<char*>(&magic), 12);
    in.read(reinterpret_cast<char*>(&file_count), sizeof(int));
    in.read(reinterpret_cast<char*>(&number_count), sizeof(int));
    in.read(reinterpret_cast<char*>(&byte_count), sizeof(int));

    for(int i = 0; i < file_count; ++i)
      {
        FileEntry entry;
        int vals[5];

        in.read(reinterpret_cast<char*>(vals), sizeof(int)*5); 

        entry.offset    = vals[0];
        entry.filesize  = vals[1];
        entry.unknown1  = vals[2];
        entry.unknown2  = vals[3];
        entry.unknown3  = vals[4];

        files.push_back(entry);
      }

    for(int i = 0; i < byte_count; ++i)
      {
        char character;
        in.read(reinterpret_cast<char*>(&character), sizeof(char));      
        bytes.push_back(character);
      }

    for(int i = 0; i < number_count; ++i)
      {
        int number;
        in.read(reinterpret_cast<char*>(&number), sizeof(int));
        numbers.push_back(number);
      }   
  }

  void print_bytes()
  {
    for(int i = 0; i < int(bytes.size()); ++i)
      std::cout << int(bytes[i]) << std::endl;
  }

  void print_numbers()
  {
    for(int i = 0; i < int(numbers.size()); ++i)
      std::cout << numbers[i] << std::endl;
  }

  void print_file_table()
  {
    std::cout << "  Nr.     offset   filesize " << std::endl;
    std::cout << "================================================================" << std::endl;
    for(int i = 0; i < int(files.size()); ++i)
      printf("%4d) %10d %10d %10d %10d %10d\n", 
             i,
             files[i].offset, 
             files[i].filesize,
             files[i].unknown1,
             files[i].unknown2,
             files[i].unknown3);
  }

  void print_info()
  {
    //std::cout << "Filename: " << filename << std::endl;
    //std::cout << "Filesize: " << st.st_size/1024/1024 << "mb (" << st.st_size << " bytes)" << std::endl;
    std::cout << "Magic:         ";
    std::cout.write(magic, 12);
    std::cout << std::endl;

    std::cout << "File_Count:    " << file_count << std::endl;
    std::cout << "Number_Count:  " << number_count << std::endl;
    std::cout << "Bytes Count:   " << byte_count << std::endl;


    int sum = 0;
    for(int i = 0; i < int(files.size()); ++i)
      if (files[i].filesize != 0)
        sum += 1;
    std::cout << "Real Files:    " << sum << " (files with non-null filesize)" << std::endl;

    std::cout << std::endl;

    std::cout << "relative: " << numbers.back() << " < " << byte_count << std::flush;
    if (numbers.back() >= byte_count) std::cout << "!!!WRONG!!!" << std::endl; 
    else std::cout << std::endl;
    
    //print_bytes();
    print_file_table();
  }
};

void extract(TLJPak& pak, std::istream& in, const std::string& outpath)
{
  std::string path = outpath;

  create_dir(path);
  create_dir(path + "/dds");
  create_dir(path + "/wav");
  create_dir(path + "/mp3");
  create_dir(path + "/dat");
  create_dir(path + "/shader");
  create_dir(path + "/misc");

  for(int i = 0; i < int(pak.files.size()); ++i)
    {
      if (pak.files[i].filesize > 0)
        {
          char filename[1024];

          char magic[8];
          in.seekg(pak.files[i].offset, std::ios::beg);
          in.read(magic, 8);

          if (strncmp(magic, "DDS", 3) == 0)
            snprintf(filename, 1024, "dds/%04d.dds", i);
          else if (strncmp(magic, "tljbone", 7) == 0)
            snprintf(filename, 1024, "misc/%04d.tljbone", i);
          else if (strncmp(magic, "ID3", 3) == 0)
            snprintf(filename, 1024, "mp3/%04d.mp3", i);
          else if (strncmp(magic, "vs", 2) == 0 || strncmp(magic, "xvs", 3) == 0)
            snprintf(filename, 1024, "shader/%04d.vs", i);
          else if (strncmp(magic, "ps", 2) == 0 || strncmp(magic, "xps", 3) == 0)
            snprintf(filename, 1024, "shader/%04d.ps", i);
          else if (strncmp(magic, "; ", 2) == 0 || strncmp(magic, "//", 2) == 0)
            snprintf(filename, 1024, "shader/%04d.us", i);
          else if (strncmp(magic, "STFU4", 4) == 0)
            snprintf(filename, 1024, "misc/%04d.stfu4", i);
          else if (strncmp(magic, "RIFF", 4) == 0)
            snprintf(filename, 1024, "wav/%04d.wav", i);
          else if (strncmp(magic, "shark3d", 7) == 0)
            snprintf(filename, 1024, "misc/%04d.s3dsb", i);
          else if (((unsigned char)magic[0]) == 255 && ((unsigned char)magic[1]) == 0xFB)
            snprintf(filename, 1024, "mp3/%04d.mp3", i);
          else if (int(magic[0]) == 0x07 && int(magic[1]) == 0x54)
            snprintf(filename, 1024, "%04d.directory", i);
          else
            snprintf(filename, 1024, "dat/%04d.dat", i);

          std::cout << filename << " " << pak.files[i].offset << " " << pak.files[i].filesize << std::endl;

          std::ofstream out((path + "/" + filename).c_str(), std::ios::binary);
          out.write(magic, 8);         
          for(int j = 0; j < pak.files[i].filesize-8; ++j)
            out.put(in.get());
          out.close();
        }
    }
}

int main(int argc, char** argv)
{
  for(int i = 1; i < argc; ++i)
    {
      std::ifstream in(argv[i], std::ios::binary);
      TLJPak pak(in);
      
      std::cout << "Filename:      " << argv[i] << std::endl;
      pak.print_info();
      std::cout << "-----------------------------------\n" << std::endl;      

      extract(pak, in, "dreamfall-extract");

      in.close();
    }
}

/* EOF */

