/******************************************************************************
 Copyright 2000-2001 Richard Woolcock.  All rights reserved.  This software may
 only be used, copied, modified, distributed or sub-licensed under the terms of
 the Glad license, which must always be included with any distributions of this
 software.  This copyright notice must remain unmodified at the top of any file
 containing any of the code found within this file.
 ******************************************************************************/

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

 Date/Author : DD-MMM-YYYY   <author's name>
 Description : <description of change>

 Date/Author : 15-Jan-2001   Richard Woolcock (aka KaVir).
 Description : Initial version for Glad 2.0a.
 ******************************************************************************/

/******************************************************************************
 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 );
}