/***************************************************************************
 *   Copyright (C) 2008 by Neil Hellfeldt                                  *
 *   devilsclaw@devilsclaws.net                                            *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

//RTXT stands for Reference Text

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <iostream>
#include <cstdlib>
#include <stdio.h>
#include <iomanip>
#include <string>
#include <limits>
#include <cmath>
#include <bitset>
#include <sstream>

using namespace std;

#ifdef WIN32
   #define EOL "\n"
#elif LINUX
   #define EOL "\r\n"
#endif

#define GROUP_PREFIX "\'["
#define GROUP_POSTFIX "]\'"
#define REFCONT_PREFIX " = \""
#define REFCONT_POSTFIX "\";"

string OUT_EXTENTION  = ".txt";
#define BYONE(a) (a * sizeof(char))

/*
   '[REFERENCE_GROUP]'
   REFERENCE = "REFERENCE CONTENT";
*/

//Reference Group Table: Contains the number of references for this group and there offset.
struct ref_group_t
{
   union
   {
      char * RRO; //Relative References Offset: Starting off we only have a relative offset
      char * RO;  //References Offset: Then we update to be the References Offset for this group
      size_t s_RRO; //This is so I can do pointer manipulation easier
      size_t s_RO;  //This is so I can do pointer manipulation easier
   };
   size_t NOR;    //Number of References
};

//RTXT Table: Constains the Number of References that are in the file.
//The size of the Reference Data. The total number of Reference Groups
//and the offset to the Reference Group Table.
struct rtxt_t
{
   char RTXTID[4]; //File Identifier should contain RTXT
   union
   {
                           //Starting off its a Relative Pointer to the Number Of Groups we update that later
      size_t* TNOG;        //Total Number of Reference Groups:   Starting off it points to the number of groups
      ref_group_t * RGTO;  //Group Table Offset: Then we update the possition to point to the Group Table Offset
      size_t s_TNOG; //This is so I can do pointer manipulation easier
      size_t s_RGTO; //This is so I can do pointer manipulation easier
   };
   size_t SORD;    //Size of Reference Data
   size_t TNOR;    //Total Number of References
};

//Reference Content Table: Contains a pointer to the Text that will be displayed in the game
//and the way it will be aligned and style eg. bold
struct ref_cont_t
{
   union
   {
      char* RCO; //Reference Content Offset: When read in from the file it is a Relative Item Offset address
      char* RC;  //Reference Content: When we update the address to be bassed off memory location it becomes Reference Content
      size_t s_RCO; //This is so I can do pointer manipulation easier
      size_t s_RC;  //This is so I can do pointer manipulation easier
   };
   size_t Unknown0; //The unknown is somewhat known I just need to research more. It has to do with alinement and bolding
   size_t Unknown1;
   size_t Unknown2;
};

#ifdef WIN32
   //This is a wraparound for the secure open file in windows
   FILE* _fopen(const char* file,const char* mode)
   {
      FILE* filep = 0;
      fopen_s(&filep,file,mode);
      return filep;
   }
#else
#define _fopen fopen
#endif

size_t get_filesize(FILE* input_file)
{
   size_t org_offset = ftell(input_file);

   //use seek to find the file size and check to see if there were any errors while trying to send it to end of file.
   if((fseek(input_file,0,SEEK_END)))
   {
      return 0;
   }

   //store the file size into filesize
   size_t filesize = ftell(input_file);

   //return to the original possition
   if((fseek(input_file,org_offset,SEEK_SET)))
   {
      return 0;
   }

   return filesize;
}

//Convert the Binary RTXT format into Text
int rtxt2txt(FILE* ifile, size_t fsize,char* filename)
{
   //All string data about the file is stored here and will be written to file.
   string output = "";

   rtxt_t rTable; //allocate some memory for the reference table.
   memset(&rTable,0,sizeof(rtxt_t)); //initialize rTable to 0

   //Read the reference table from file and test to see if we actually read the entire table
   //NOTE: fread progresses the file possition on its own so after this any reading is past the reference table.
   if((fread(&rTable,sizeof(char),sizeof(rtxt_t),ifile)) != sizeof(rtxt_t))
   {
      return 1;
   }

   //Check to see if its a valid RTXT file.
   if(!(rTable.RTXTID[0] == 'R' && rTable.RTXTID[1] == 'T' && rTable.RTXTID[2] == 'X' && rTable.RTXTID[3] == 'T'))
   {
      return 1;
   }

   //I don't feel like recasting all the time so im going to make a union for this
   union
   {
      char* fbuff;
      size_t s_fbuff;
   };

   //Allocate memory for the rest of the file which will be read into memory soon.
   fbuff = (char*)malloc(fsize - sizeof(rtxt_t));

   if(!fbuff) return 1; //check to see if we manage to allocate the memory we needed.

   //Read the rest of the file into memory. Check to see if we read all the intended bytes we wanted.
   //NOTE: After this we only work with the file in memory
   if((fread(fbuff,sizeof(char),fsize - sizeof(rtxt_t),ifile)) != (fsize - sizeof(rtxt_t)))
   {
      //Clean the allocated memory
      free(fbuff);
      return 1;
   }

   ref_cont_t * ref_conts = (ref_cont_t *)s_fbuff;
   for(size_t counter = 0; counter < rTable.TNOR; ++counter)
   {
      //Convert Relative Content Offset to Content Offset
      ref_conts[counter].RCO += (s_fbuff + (rTable.TNOR * sizeof(ref_cont_t)));
   }

   //Update the Relative Offset to real offset
   rTable.s_TNOG += (s_fbuff - sizeof(rtxt_t));
   size_t TNOG = *rTable.TNOG; //Creat a Local Total Number of Groups

   rTable.s_RGTO += BYONE(4); //Update the Reference Group Table Offset to actually point to the correct offset

   //Point group string to the begining of where the group string data is located
   char * GroupString = (char *)(rTable.s_RGTO + (sizeof(ref_group_t) * TNOG));

   //Reference Counter
   size_t RefCount = 0;

   //Start decoding the file from rtxt format to text
   for(size_t counter = 0; counter < TNOG ; ++counter )
   {
      //Update the Relative Reference offset to the actual location of the Reference
      rTable.RGTO[counter].s_RRO += rTable.s_RGTO;

      //Format the group string and put it into the output string that will be written later to file.
      output += GROUP_PREFIX + (string)GroupString + GROUP_POSTFIX + EOL;

      //Since all string data is null terminated we get the size of the string and add 1
      //and then update the group string possition to the next group string
      GroupString += strlen(GroupString) + BYONE(1);

      //start working on the References and Reference Content.
      for(size_t j = 0; j < rTable.RGTO[counter].NOR; ++j, ++RefCount)
      {
         //Format the Reference and Reference Content and put it into the output string.
         output += (string)rTable.RGTO[counter].RO + REFCONT_PREFIX + (string)ref_conts[RefCount].RC + REFCONT_POSTFIX + EOL;

         //Update the Reference offset to the next refence.
         rTable.RGTO[counter].RO += strlen(rTable.RGTO[counter].RO) + BYONE(1);
      }
      output += EOL;
   }

   //Clean the allocated memory
   free(fbuff);

   //open the new ouput file
   FILE* ftemp = _fopen(((string)filename + (string)OUT_EXTENTION).c_str(),"w+");
   if(ftemp)
   {
      //write the data to file
      fwrite(output.c_str(),1,output.size(),ftemp);
   }
   else
   {
      cout << "Could not creat " << ((string)filename + (string)OUT_EXTENTION) << " returning with error." << endl;
      return 1;
   }

   //close the temp file.
   fclose(ftemp);

   return 0;
}

bool txt2rtxt(FILE* ifile, size_t fsize,char* filename)
{

   //Allocate memory for the entire file
   char* fbuff = (char*)malloc(fsize);
   if(!fbuff)
   {
      return 1;
   }

   if((fread(fbuff,1,fsize,ifile)) != fsize)
   {
      //free the allocated memory
      free(fbuff);
      return 1;
   }

   //Build, Clear and Set the rTable
   rtxt_t rTable;
   memset(&rTable,0,sizeof(rtxt_t));
   rTable.RTXTID[0] = 'R';
   rTable.RTXTID[1] = 'T';
   rTable.RTXTID[2] = 'X';
   rTable.RTXTID[3] = 'T';

   size_t TNOG = 0;  //Total Number Of Groups
   string tmp  = "";
   ref_cont_t  * ref_conts = 0;
   ref_group_t * ref_groups = 0;

   size_t TRCSS = 0; //Total Reference Content String Size
   size_t TRGSS = 0; //Total Reference Group String Size
   size_t TRSS = 0;  //Total Reference String Size
   char* RCS = 0;    //Reference Content Strings
   char* RGS = 0;    //Reference Group Strings
   char* RS = 0;     //Reference Strings

   //Loop until we reach the end of memory buffer
   for(size_t pos = 0; pos < fsize;++pos)
   {
      //Check for start of a group
      if(fbuff[pos] == '\'' && fbuff[pos+1] == '[')
      {
         //update possition past '[
         pos += BYONE(2);

         //loop until we find the end of this group or until end of file.
         for(;pos < fsize;++pos)
         {
            //Check for end of group
            if(fbuff[pos] == ']' && fbuff[pos+1] == '\'' && fbuff[pos+2] == '\r')
            {
               //rallocate memory so we can add the current group string to the Reference Group String Buffer
               RGS = (char*)realloc(RGS, TRGSS + (tmp.size() + BYONE(1)));

               //Clear the newly allocated memory just incase.
               memset(&RGS[TRGSS],0,tmp.size() + BYONE(1));

               //Copy the group string in.
               strcpy((char*)&RGS[TRGSS],tmp.c_str());

               //reallocate memory for the ref group tables
               ref_groups = (ref_group_t *)realloc(ref_groups,(TNOG + 1) * sizeof(ref_group_t));

               //clear the new memory allocated so we dont get corrupted data
               memset(&ref_groups[TNOG],0,sizeof(ref_group_t));

               //if this group has no Relative Reference Offset then we need to set one.
               //TODO: Check to see if this cases problems with groups that have not references.
               if(!ref_groups[TNOG].RRO)
               {
                  ref_groups[TNOG].s_RRO = TRSS;
                  ref_groups[TNOG].NOR = 0;
               }

               //update the Total Reference Group String Size
               TRGSS += tmp.size() + BYONE(1);
               tmp = "";  //clear the temp string
               pos += BYONE(4); //update possition past ]'EOL

               //Loop until we find the Reference Content for this Reference or until we hit the
               //end of the memory data.
               for(;pos < fsize; ++pos)
               {
                  //Check to see if we found Reference Content for this References
                  if(fbuff[pos] == ' ' && fbuff[pos+1] == '=' && fbuff[pos+2] == ' '  && fbuff[pos+3] == '\"')
                  {
                     RS = (char*)realloc(RS, TRSS + tmp.size() + BYONE(1));
                     strcpy((char*)&RS[TRSS],tmp.c_str());
                     TRSS += tmp.size() + BYONE(1);
                     tmp = "";
                     pos += BYONE(4);

                     //Loop until we find the end of the Reference Content or until the end of
                     //the memory data
                     for(;pos < fsize; ++pos)
                     {
                        //Check to see if we found the end of the Reference Content
                        if(fbuff[pos] == '\"' && fbuff[pos+1] == ';' && fbuff[pos+2] == '\r')
                        {

                           //Allocate memory for the Reference Content Table
                           ref_conts = (ref_cont_t *)realloc(ref_conts,(rTable.TNOR +1) * sizeof(ref_cont_t));

                           //Allocate memoyr for the Reference Content String Data
                           RCS = (char*)realloc(RCS, TRCSS + tmp.size() + BYONE(1));

                           //Clear the newly allocated memory
                           memset(&RCS[TRCSS],0,tmp.size());

                           //Initalize Reference Content Table
                           ref_conts[rTable.TNOR].s_RCO = TRCSS;
                           ref_conts[rTable.TNOR].Unknown0 = 0;
                           ref_conts[rTable.TNOR].Unknown1 = 0;
                           ref_conts[rTable.TNOR].Unknown2 = 0;

                           //Copy the Reference Content into the allocated memory
                           strcpy((char*)&RCS[TRCSS],tmp.c_str());

                           TRCSS += tmp.size() + BYONE(1);

                           //Update possition past the "; we don't move past the \r since the
                           //loop will handle it in this case
                           pos += BYONE(3);

                           //Clear the temp string
                           tmp = "";

                           //update the Total Number of References
                           ++rTable.TNOR;
                           break;
                        }
                        //If its not the end of the Reference Content add current char to the temp string
                        else tmp += fbuff[pos];
                     }
                     //Update the Number of References in this group
                     ++ref_groups[TNOG].NOR;
                  }
                  //If we dont find Reference Content but we find a end line then break out of this loop
                  //TODO: Find out if this is the right location for this.
                  else if(fbuff[pos] == '\r')
                  {
                     break;
                  }
                  //If its not the end of the this Reference string date then add current char to the temp string
                  else tmp += fbuff[pos];
               }
               //Update Total Number of Groups
               ++TNOG;
               break; //Break out of this loop
            }
            //If its not the end of the Group's string date then add current char to the temp string
            else tmp += fbuff[pos];
         }
      }
   }

   //Update the Size of Relative Data
   rTable.SORD = TRGSS + (TNOG * sizeof(ref_group_t)) + TRSS;

   //Update the Reference Group Table Offset
   rTable.s_RGTO = (rTable.TNOR * sizeof(ref_cont_t)) + TRCSS + sizeof(rtxt_t);

   //Update the Reference Groups Relative Reference offset to point correctly for the file
   for(size_t counter = 0; counter < TNOG; ++counter)
   {
      ref_groups[counter].s_RRO += (TRGSS + (TNOG * sizeof(ref_group_t)));
   }


   //Create the output file
   FILE* ftemp = _fopen(((string)filename + OUT_EXTENTION).c_str(),"w+b");
   if(ftemp)
   {
      //write all the data to file
      fwrite(&rTable,1,sizeof(rtxt_t),ftemp);
      fwrite(ref_conts,1,sizeof(ref_cont_t) * rTable.TNOR,ftemp);
      fwrite(RCS,1,TRCSS,ftemp);
      fwrite(&TNOG,1,sizeof(size_t),ftemp);
      fwrite(ref_groups,1,sizeof(ref_group_t) * TNOG,ftemp);
      fwrite(RGS,1,TRGSS,ftemp);
      fwrite(RS,1,TRSS,ftemp);
   }
   else
   {
      free(fbuff); //Free memory for for the file buffer.
      free(RCS);   //Free memory for Reference Content Strings
      free(RGS);   //Free memory for Reference Group Strings
      free(RS);    //Free memory for Reference Strings
      cout << "Could not creat " << (string)filename + OUT_EXTENTION << " returning with error." << endl;
      return 1;
   }
   fclose(ftemp);

   free(fbuff); //Free memory for for the file buffer.
   free(RCS);   //Free memory for Reference Content Strings
   free(RGS);   //Free memory for Reference Group Strings
   free(RS);    //Free memory for Reference Strings

   return 0;
}

//TODO:Intergrate the Commandling Handling Into this project
int main(int argc, char** argv)
{
   //Encode as binary or text
   bool bin = false;
   if(argc >= 2) //check to see if at least 2 parameters were passed
   {
      if(((string)"-b") == ((string)argv[1])) //if parameter 2 is -b then binary
      {
         OUT_EXTENTION = ".bin";
         bin = true;
      }
      else if(((string)"-t") == ((string)argv[1]))//if parameter 2 is -t then text
      {
         OUT_EXTENTION = ".txt";
         bin = false;
      }
      else
      {
         cout << "Error: Unknown parameter." << endl;
         return 1;
      }
   }
   else
   {
      cout << "Usage: \n\t -b convert to binary\n\t -t convert to text\neg.\t" << argv[0] << " -b " << "files\neg.\t" << argv[0] << " -t " << "files" << endl;
      return 1;
   }

   //set counter passed the first two parameters and point it to the files
   for(int counter = 2; counter < argc ; ++counter)
   {
      cout << "Opening " << argv[counter] << endl;

      //open file that has to be there
      FILE* hfile = _fopen(argv[counter],"r+b");
      if(hfile)
      {
         size_t fsize = get_filesize(hfile);
         if(fsize)
         {
            //check to see if any errors have happend sofar and if there is a file size.
            if(!((ferror(hfile)) || !fsize))
            {
               //run text encoder or binary encoder
               if(!((bin)?txt2rtxt(hfile,fsize,argv[counter]):rtxt2txt(hfile,fsize,argv[counter])))
               {
                  cout << "Finished processing " << argv[counter] << endl;
                  cout << "Outputed File:  " << argv[counter] << OUT_EXTENTION << endl;
               }
               else
               {
                  cout << "Error while processing " << argv[counter] << endl;
               }
            }
            else
            {
               cout << "There was an error with " << argv[counter] << endl;
            }
         }
         else
         {
            cout << argv[counter] << " was zero in size." << endl;
         }
      }
      else
      {
         cout << "Could not open " << argv[counter] << endl;
      }
      //close the file
      fclose(hfile);
      hfile = 0;
   }
}
