/******************************************************************************
 Copyright (c) 2000-2001 Richard Woolcock

 Permission is hereby granted, free of charge, to any person obtaining a copy 
 of this software and associated documentation files (the "Software"), to deal 
 in the Software without restriction, including without limitation the rights 
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
 copies of the Software, and to permit persons to whom the Software is 
 furnished to do so, subject to the following conditions:

 The above copyright notice and this permission notice shall be included in all 
 copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 
 SOFTWARE.
 ******************************************************************************/

/******************************************************************************
 File Name        : file_io.c
 ******************************************************************************
 Description      : Contains the file loading and saving functions.
 ******************************************************************************/

/******************************************************************************
 Required library files.
 ******************************************************************************/

#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include <netinet/in.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include "sockets.h"
#include "string.h"
#include "glad.h"
#include "text_io.h"

/******************************************************************************
 Required types.
 ******************************************************************************/

typedef enum
{
   TEnum,   /* Enumerated type */
   TAlpha,  /* Alphabetical string (letters only) */
   TString, /* Normal string (can contain any characters) */
   TInt,    /* Integer */
   TEnd     /* Indicates the end of the player file */
} TType_t;

typedef struct
{
   char * szVariable;
   char * szTextValue;
   int    iNumberValue;
} enum_t;

typedef struct
{
   char *  szName;
   void *  pvStorage;
   TType_t eType;
   int     iMin;
   int     iMax;
   int     iDefault;
} playerfile_t;

/******************************************************************************
 Required literals.
 ******************************************************************************/

#define MAX_PFILE_LINE 256  /* Max size of a single line read from pfile */
#define MAX_TEXT_SIZE  4096 /* Max size of a single textual description */

/******************************************************************************
 Required macros.
 ******************************************************************************/

#define PFILE(p) (void*) &(s_stBody.p)

/******************************************************************************
 Local operation prototypes.
 ******************************************************************************/

static bool ReadEntry( char *szName, char *szValue );

/******************************************************************************
 Local variables.
 ******************************************************************************/

static body_t s_stBody; /* Storage variable used for saving/loading */

const playerfile_t a_kstPlayerFileTable[] = 
{ /*  Keyword    Storage variable     Type     Minimum  Maximum     Default  */
   { "Name",     PFILE(a_chName),     TAlpha,  1,       15,         -1        },
   { "Password", PFILE(a_chPassword), TString, 1,       15,         -1        },
   { "Status",   PFILE(eStatus),      TEnum,   CITIZEN, CHALLENGER, GLADIATOR },
   { "Strength", PFILE(iStats[STR]),  TInt,    1,       9,          1         },
   { "Dexterity",PFILE(iStats[DEX]),  TInt,    1,       9,          1         },
   { "Stamina",  PFILE(iStats[STA]),  TInt,    1,       9,          1         },
   { "Size",     PFILE(iStats[SIZ]),  TInt,    1,       9,          1         },
   { "Wits",     PFILE(iStats[WIT]),  TInt,    1,       9,          1         },
   { "Sex",      PFILE(eSex),         TEnum,   MALE,    FEMALE,     MALE      },
   { "Room",     PFILE(iRoom),        TInt,    0,       100000,     0         },
   { "Wins",     PFILE(iWin),         TInt,    0,       100000,     0         },
   { "Losses",   PFILE(iLoss),        TInt,    0,       100000,     0         },
   { "Kills",    PFILE(iKill),        TInt,    0,       100000,     0         },
   { "Wounds",   PFILE(iDam),         TInt,    0,       99,         0         },

   /* Anything after "End" will be saved but not loaded */
   { "End",      NULL,                TEnd,    0,       0,          0         },
   { NULL, NULL, 0, 0, 0, 0 } /* This must ALWAYS be at the end */
};

const enum_t a_kstEnumTable[] =
{ /*  Name     String value  Numeric value */

   { "Status", "CROWD",      CROWD         },
   { "Status", "CITIZEN",    CITIZEN       },
   { "Status", "TRAINING",   TRAINING      },
   { "Status", "GLADIATOR",  GLADIATOR     },
   { "Status", "CHALLENGER", CHALLENGER    },
   { "Status", "FIGHTING",   FIGHTING      },

   { "Sex",    "MALE",       MALE          },
   { "Sex",    "FEMALE",     FEMALE        },

   { NULL, NULL, 0 } /* This must ALWAYS be at the end */
};

/******************************************************************************
 Required global operations.
 ******************************************************************************/

/* Function: SaveBody
 *
 * This function saves the body which is passed in as an argument.
 *
 * The function takes one parameter, as follows:
 *
 * pstBody: The body to be saved.
 *
 * This function returns a bool, which is TRUE if the body was successfully 
 * saved and FALSE if it was not.
 */
bool SaveBody( body_t *pstBody )
{
   FILE* pFile;            /* File pointer */
   int   i = 0;            /* Loop counter */
   char  a_chFileName[64]; /* Name of the player file */
   bool  bEnd = FALSE;

   /* Determine the file name */
   sprintf( a_chFileName, "../player/%s", pstBody->a_chName );

   /* Try and open the file */
   if ( ( pFile = fopen(a_chFileName,"w") ) == NULL )
   {
      Log( "SaveBody: Unable to open player file '%s'\n", a_chFileName );
      return ( FALSE );
   }

   /* Copy the body */
   s_stBody = *pstBody;

   /* Write out the player file */
   while ( a_kstPlayerFileTable[i].szName != NULL )
   {
      int j = 0; /* Loop counter */
      bool bFound = FALSE;

      switch ( a_kstPlayerFileTable[i].eType )
      {
         case TEnum:
            while ( a_kstEnumTable[j].szVariable != NULL && !bFound )
            {
               if ( !strcmp( a_kstEnumTable[j].szVariable, 
                  a_kstPlayerFileTable[i].szName ) )
               {
                  if ( a_kstEnumTable[j].iNumberValue == 
                     *((int*)a_kstPlayerFileTable[i].pvStorage) )
                  {
                     fprintf( pFile, "%-10s %s\n", 
                        a_kstPlayerFileTable[i].szName,
                        a_kstEnumTable[j].szTextValue );
                     bFound = TRUE;
                  }
               }
               j++;
            }
            break;
         case TAlpha:
            fprintf( pFile, "%-10s %s\n", 
               a_kstPlayerFileTable[i].szName, 
               (char*) a_kstPlayerFileTable[i].pvStorage );
            break;
         case TString:
            fprintf( pFile, "%-10s %s\n", 
               a_kstPlayerFileTable[i].szName, 
               (char*) a_kstPlayerFileTable[i].pvStorage );
            break;
         case TInt:
            fprintf( pFile, "%-10s %d\n", 
               a_kstPlayerFileTable[i].szName, 
               *((int*)a_kstPlayerFileTable[i].pvStorage) );
            break;
         case TEnd:
            bEnd = TRUE;
            fprintf( pFile, "End\n" );
            break;
      }
      i++;
   }

   /* Close the player file */
   fclose(pFile);

   /* Make sure it put the "End" on the end, as it is needed for loading */
   if ( !bEnd )
   {
      Log( "SaveBody: The end of the player file is missing!\n" );
      return ( FALSE );
   }

   return ( TRUE );
}


/* Function: LoadBody
 *
 * This function loads the body which is passed in as an argument.
 *
 * The function takes two parameters, as follows:
 *
 * szFileName: The file name of the player file.
 * pstBody:    The body to be loaded.
 *
 * This function returns a bool, which is TRUE if the body was successfully 
 * loaded and FALSE if it was not.
 */
bool LoadBody( char *szFileName, body_t *pstBody )
{
   FILE* pFile;                        /* File pointer */
   int   iLetter;                      /* Used for reading from the player file */
   char  a_chFileName[64];             /* Name of the player file */
   bool  bEnd = FALSE;                 /* End of file */
   char  a_chReadLine[MAX_PFILE_LINE]; /* Line read from the player file */
   int   iSize = 0;                    /* Size of the line */

   /* Determine the file name */
   sprintf( a_chFileName, "../player/%s", szFileName );

   /* Try and open the file */
   if ( ( pFile = fopen(a_chFileName,"r") ) == NULL )
   {
      /* If you can't find the player file, return */
      return ( FALSE );
   }

   /* This will be wiped over if their player file is actually okay */
   pstBody->eStatus = CORRUPTED_PFILE;

   /* Copy the body so that pointers/etc aren't lost */
   s_stBody = *pstBody;

   /* Read in the player file */
   while ( !bEnd )
   {
      iLetter = getc(pFile);
      switch ( iLetter )
      {
         case EOF:
            /* Log the fault, close the player file and return */
            Log( "LoadBody: Player file '%s' is corrupted (reached EOF).\n", szFileName );
            fclose(pFile);
            return ( FALSE );
         default:
            if ( iSize+2 >= MAX_PFILE_LINE )
            {
               /* Log the fault, close the player file and return */
               Log( "LoadBody: Player file '%s' is corrupted (line too long).\n", szFileName );
               fclose(pFile);
               return ( FALSE );
            }
            else /* There is enough space for the next character plus a NUL */
            {
               a_chReadLine[iSize++] = iLetter;
            }
            break;
         case '\n':
            if ( iSize+1 >= MAX_PFILE_LINE )
            {
               /* Log the fault, close the player file and return */
               Log( "LoadBody: Player file '%s' is corrupted (line too long).\n", szFileName );
               fclose(pFile);
               return ( FALSE );
            }
            else /* There is enough space to stick a NUL on the end */
            {
               char *szVariable;
               char *szValue;
               char *szInput = a_chReadLine;

               /* Terminate the input string */
               a_chReadLine[iSize] = '\0';
               iSize = 0;

               szVariable = ChopToken( &szInput );
               szValue    = ChopToken( &szInput );

               if ( szVariable[0] == '\0' || szValue[0] == '\0' || 
                  ReadEntry( szVariable, szValue ) == FALSE )
               {
                  if ( !strcmp( szVariable, "End" ) )
                  {
                     bEnd = TRUE;
                  }
                  else
                  {
                     /* Log the fault, close the player file and return */
                     Log( "LoadBody: Player file '%s' is corrupted.\n", szFileName );
                     fclose(pFile);
                     return ( FALSE );
                  }
               }
            }
            break;
      }
   }

   /* Copy the body back over the player's */
   *pstBody = s_stBody;

   /* Close the player file */
   fclose(pFile);

   return ( TRUE );
}


/* Function: LoadHelp
 *
 * This function loads the help file which is passed in as an argument.
 *
 * The function takes one parameters, as follows:
 *
 * szFileName: The file name of the help file.
 *
 * This function returns a pointer to the help file, which is stored on the 
 * heap.
 */
char *LoadHelp( char *szFileName )
{
   FILE* pFile;                       /* File pointer */
   int   iLetter;                     /* Used for reading from the help file */
   char  a_chFileName[128];           /* Name of the help file */
   char  a_chReadLine[MAX_TEXT_SIZE]; /* Line read from the help file on disk */
   int   iSize = 0;                   /* Size of the line */

   if ( !StringAlpha(szFileName) )
   {
      /* If the helpfile has an invalid filename, return NULL */
      Log( "LoadHelp: Help file '%s' is not a valid filename.\n\r", szFileName );
      return ( NULL );
   }

   /* Determine the file name */
   sprintf( a_chFileName, "../help/%s.txt", szFileName );

   /* Try and open the file */
   if ( ( pFile = fopen(a_chFileName,"r") ) == NULL )
   {
      /* If you can't find the help file, return NULL */
      return ( NULL );
   }

   /* Read in the first character of the help file */
   iLetter = getc(pFile);

   /* Read in the rest of the help file */
   while ( iLetter != EOF )
   {
      if ( iSize+2 >= MAX_TEXT_SIZE )
      {
         /* Log the fault, close the help file and return */
         Log( "LoadHelp: Help file '%s' exceeds maximum size of %d characters.\n", szFileName, MAX_TEXT_SIZE );
         fclose(pFile);
         return ( NULL );
      }
      else /* There is enough space for the next character plus a NUL */
      {
         /* Only store printable characters */
         a_chReadLine[iSize++] = iLetter;
      }
      iLetter = getc(pFile); /* Read the next character */
   }

   a_chReadLine[iSize] = '\0'; /* Terminate the string */

   while ( iSize > 0 && (a_chReadLine[iSize-1] == '\n' || a_chReadLine[iSize-1] == '\r') )
   {
      /* Strip off any blank lines from the end of the help file */
      a_chReadLine[--iSize] = '\0';
   }

   /* Close the help file */
   fclose(pFile);

   return ( strdup( a_chReadLine ) );
}

/******************************************************************************
 Required global operations.
 ******************************************************************************/

/* Function: ReadEntry
 *
 * This function reads through the player file table looking for a match for 
 * the variable passed in.  Once found, the variable is stored in the body.
 *
 * The function takes two parameters, as follows:
 *
 * szName:  The name of the variable.
 * szValue: The value of the variable.
 *
 * This function returns a bool, which is TRUE if a match was found and FALSE 
 * if it was not.
 */
static bool ReadEntry( char *szName, char *szValue )
{
   int i = 0; /* Loop counter */

   /* Write out the player file */
   while ( a_kstPlayerFileTable[i].szName != NULL )
   {
      int j = 0; /* Loop counter */

      if ( !strcmp( a_kstPlayerFileTable[i].szName, szName ) )
      {
         switch ( a_kstPlayerFileTable[i].eType )
         {
            case TEnum:
               while ( a_kstEnumTable[j].szVariable != NULL )
               {
                  if ( !strcmp( a_kstEnumTable[j].szVariable, szName ) )
                  {
                     if ( !strcmp( a_kstEnumTable[j].szTextValue, szValue ) )
                     {
                        *((int*)a_kstPlayerFileTable[i].pvStorage) = 
                           a_kstEnumTable[j].iNumberValue;
                        return ( TRUE );
                     }
                  }
                  j++;
               }
               Log( "ReadEntry: Invalid enum value '%s' for %s.\n", szValue, szName );
               return ( FALSE );
            case TAlpha:
               if ( !StringAlpha(szValue) )
               {
                  Log( "ReadEntry: Variable '%s' contains non-alphabetical character '%c'.\n", szName, szValue[j] );
                  return ( FALSE );
               }
               /* Intentional fall-through */
            case TString:
               strcpy( (char*)a_kstPlayerFileTable[i].pvStorage, szValue );
               if ( strlen(szValue) < a_kstPlayerFileTable[i].iMin )
               {
                  Log( "ReadEntry: Variable '%s' below minimum length.\n", szName );
                  return ( FALSE );
               }
               else if ( strlen(szValue) > a_kstPlayerFileTable[i].iMax )
               {
                  Log( "ReadEntry: Variable '%s' above maximum length.\n", szName );
                  return ( FALSE );
               }
               return ( TRUE );
            case TInt:
               *((int*)a_kstPlayerFileTable[i].pvStorage) = TextCalculate( NULL, szValue );
               if ( *((int*)a_kstPlayerFileTable[i].pvStorage) < a_kstPlayerFileTable[i].iMin )
               {
                  Log( "ReadEntry: Variable '%s' below minimum valid value.\n", szName );
                  return ( FALSE );
               }
               else if ( *((int*)a_kstPlayerFileTable[i].pvStorage) > a_kstPlayerFileTable[i].iMax ) 
               {
                  Log( "ReadEntry: Variable '%s' above maximum valid value.\n", szName );
                  return ( FALSE );
               }
               return ( TRUE );
            case TEnd:
               return ( FALSE );
         }
      }
      i++;
   }

   /* No match was found */
   return ( FALSE );
}