/******************************************************************************
 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        : glad.c
 ******************************************************************************
 Description      : Contains the core functionality of the mud.
 ******************************************************************************/

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

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

/******************************************************************************
 Required global variables.
 ******************************************************************************/

static time_t s_tStartup;

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

/* Function: main
 *
 * This is the main function required by C.  It starts up the mud, calls
 * InitSocket to create the control socket, loads up any data files
 * required, then calls the GameLoop function, which is only returned
 * from when the mud is in shutdown state.  At that point the control
 * socket is closed and the program exits.
 *
 * The function takes no parameters.
 *
 * This function has to return an int, which is 0 (unless the mud crashes).
 */
int main()
{
   int iControl; /* Contains the control socket descriptor */

   /* Store the startup time */
   s_tStartup = time(0);

   /* Seed the dice */
   srand(s_tStartup);

   /* Startup message */
   Log("Mud started on port %d.\n", PORT );

   /* Initialise the control socket */
   iControl = InitSocket();

   /* Call the main loop */
   GameLoop(iControl);

   /* Close the main socket */
   close(iControl);

   /* Exit the program */
   return ( 0 );
}



/* Function: GameLoop
 *
 * This function controls the main program flow.  It goes through the
 * connections looking for input, parses any it finds, then sends output
 * back to those connections.
 *
 * The function takes one parameter, as follows:
 *
 * iControl: The control socket.
 *
 * This function has no return value.
 */
void GameLoop( int iControl )
{
   struct  timeval stTv;
   fd_set  sFd;
   int     iFdMax;
   time_t  tTime = 0;
   conn_t* pstConn;
   conn_t* pstConnNext;

   /* Work out how many descriptors can be open at once */
   iFdMax = getdtablesize(); /* May want to error trap for -1 */

   /* Clear the descriptor set */
   FD_ZERO(&a_sFd);

   /* Add iControl to the descriptor set */
   FD_SET(iControl,&a_sFd);

   do /* until a shutdown occurs */
   {
      /* Clear the socket flags */
      bcopy((char*) &a_sFd, (char*)&sFd, sizeof(sFd));

      /* Set to 1 second */
      stTv.tv_sec = 1;
      stTv.tv_usec = 0;

      /* Sleep until a descriptor tries to do something */
      if ( select(iFdMax, &sFd, (fd_set*) 0, (fd_set*) 0, &stTv) < 0 )
         continue; /* Try again if it raised an exception */

      /* Update pulse called every second */
      if ( (time(0) - tTime) >= 1 /* second */ )
      {
         Update();
         tTime = time(0);
      }

      /* Check if iControl is in the sFd descriptor set */
      if ( FD_ISSET(iControl,&sFd) )
      {
         struct sockaddr_in stSa;
         unsigned int iNewConnection,iSaSize = sizeof(stSa);
         /* Looking for pending connections on the control socket */
         if ( (iNewConnection = accept(iControl, (struct sockaddr*) &stSa, &iSaSize) ) >= 0 )
         {
            NewConnection(iNewConnection); /* Create a new connection */
         }
      }

      /* Loop through all of the connections */
      for ( pstConn = pstFirstConn; pstConn != NULL; pstConn = pstConnNext )
      {
         pstConnNext = pstConn->pstNext;

         /* Activity on the socket but no input means dropped connection */
         if ( FD_ISSET(pstConn->iSocket,&sFd) && !GetInput(pstConn) )
         {
            CloseConnection(pstConn);
         }
         else /* Otherwise flush the output buffer */
         {
            FlushOutput(pstConn);
         }
      }
   }
   while ( !bShutdown );
}



/* Function: Update
 *
 * This function is the pulse routine, called once per second.
 *
 * The function takes no parameters.
 *
 * This function has no return value.
 */
void Update()
{
   body_t* pstBody;
   body_t* pstBodyNext;
   body_t* pstBody2;
   int     i;

   /* Loop through all of the bodies */
   for ( pstBody = pstFirstBody; pstBody != NULL; pstBody = pstBodyNext )
   {
      pstBodyNext = pstBody->pstNext;

      /* Store their opponent */
      pstBody2 = pstBody->pstOpponent;

      /* Check if the player is fighting */
      if ( pstBody->eStatus == FIGHTING && pstBody2 != NULL )
      {
         /* Determine the action of each location */
         for ( i = AP_LEFT_HAND; i < AP_MAX; i++ )
         {
            /* If they're in mid-attack they don't get action points */
            if ( pstBody->bBusy[i] == FALSE )
            {
               /* If they are fighting, increase their action points */
               if ( ( pstBody->iActions[i] += GetSpeed(pstBody)+3 ) > 999 )
               {
                  /* You can't store up more than 999 action points though */
                  pstBody->iActions[i] = 999;
               }
            }

            /* No speed counter means no move is being performed */
            if ( pstBody->iSpeed[i] > 0 )
            {
               if ( --pstBody->iSpeed[i] <= 0 )
               {
                  /* Set up the variables */
                  TextInit(i);

                  /* Once they've finished their move, it's time to hit */
                  (*(pstBody->pfnAttack[i]))(pstBody,i);

                  /* Once they've hit, free up their hand */
                  pstBody->bBusy[i] = FALSE;
               }
            }
         }

         /* See if they've beaten their opponent */
         if ( pstBody2->iDam >= GetHealth(pstBody2) )
         {
            /* Inform the fighters */
            SendToBody(pstBody,TO_USER,"%s falls to the floor, unconscious!\n\r",
               pstBody->pstOpponent->a_chName);
            SendToBody(pstBody->pstOpponent,TO_USER,
               "You fall to the floor, unconscious!\n\r");

            /* Send the victory message */
            SendToBody(pstBody,TO_ALL,"[%s has beaten %s in the arena!]\n\r",
               pstBody->a_chName,pstBody2->a_chName);

            /* And log the kill */
            Log("[%s has beaten %s in the arena!]\n",
               pstBody->a_chName,pstBody2->a_chName);

            /* Update wins and losses... */
            pstBody->iWin++;
            pstBody2->iLoss++;

            /* ...stop them fighting... */
            pstBody->eStatus = GLADIATOR;
            pstBody2->eStatus = GLADIATOR;
            pstBody->pstOpponent = NULL;
            pstBody2->pstOpponent = NULL;

            /* ...return them to the stadium... */
            pstBody->iRoom = 0;
            pstBody2->iRoom = 0;

            /* ...and save them! */
            SaveBody(pstBody);
            SaveBody(pstBody2);
         }
      }
   }
}



/* Function: Log
 *
 * This function will send a line of text to the log file, along with
 * the current date and time.
 *
 * The function takes two parameters, as follows:
 *
 * szTxt: A pointer to the string to be logged.
 * ...:   One or more other arguments.
 *
 * This function has no return value.
 */
void Log( char* szTxt, ... )
{
   FILE*   pFile;            /* File pointer */
   time_t  tTime = time(0);  /* The current time */
   va_list pArg;             /* Pointer to the next argument */
   char    a_chFileName[64]; /* Name of the log file */

   /* Variable arguments lists "start" macro */
   va_start(pArg,szTxt);

   sprintf( a_chFileName, "../log/log_%ld.txt", (long)s_tStartup );

   /* Open the log file in append mode */
   if ( ( pFile = fopen(a_chFileName,"a") ) != NULL )
   {
      /* Write the date and time to the log file */
      fprintf(pFile,"%.24s: ",ctime(&tTime));

      /* Write the text (including optional arguments) to the log file */
      vfprintf(pFile,szTxt,pArg);

      /* Flush and close the log file */
      fflush(pFile);
      fclose(pFile);
   }

   /* Variable arguments lists "end" macro */
   va_end(pArg);
}



/* Function: TrainStat
 *
 * This function trains a stat.
 *
 * The function takes three parameters, as follows:
 *
 * pstConn: The connection of the player to train.
 * szTxt:   A pointer to the string containing the text form of the stat.
 * iStat:   The number of the stat to be trained.
 * iNum:    The number of increments.
 *
 * This function has no return value.
 */
void TrainStat( conn_t* pstConn, char* szTxt, int iStat, int iNum )
{
   int iCount = 0;
   int i;

   if ( pstConn->pstBody == NULL )
   {
      /* Logs an error and returns, if pstConn has no body */
      Log("BUG[%s]: No body!\n",__FUNCTION__);
      return;
   }

   /* Calculate their total number of points worth of stats */
   for ( i = STR; i <= WIT; i++ )
   {
      iCount += pstConn->pstBody->iStats[i];
   }

   /* Check if they have enough points remaining to train their stat */
   if ( (iCount + iNum) <= MAX_TRAIN )
   {
      /* Increment the stat as long as it doesn't take it over 9 */
      if ( (pstConn->pstBody->iStats[iStat] + iNum) > 9 )
      {
         PutOutput(pstConn,TO_USER,"Your %s cannot exceed 9.\n\r",szTxt);
      }
      else if ( (pstConn->pstBody->iStats[iStat] + iNum) < 1 )
      {
         PutOutput(pstConn,TO_USER,"Your %s cannot go below 1.\n\r",szTxt);
      }
      else
      {
         pstConn->pstBody->iStats[iStat] += iNum;
         PutOutput(pstConn,TO_USER,"You train %d time%s.  Current %s: %d.  Points remaining: %d.\n\r", 
            iNum, (iNum == 1) ? "" : "s", szTxt, 
            pstConn->pstBody->iStats[iStat],MAX_TRAIN-iNum-iCount);
      }
   }
   else /* Not enough points to train any further */
   {
      PutOutput(pstConn,TO_USER,"Not enough remaining points.\n\r");
   }
}



/* Function: StatusName
 *
 * This function returns the status (as a string) of the specified body.
 *
 * The function takes one parameter, as follows:
 *
 * pstBody: The body to find the status of.
 *
 * This function returns a pointer to a string, which contains the
 * textual value of the body's status.
 */
char* StatusName(body_t*pstBody)
{
   int iStatus = pstBody->iWin+pstBody->iKill-pstBody->iLoss;

   /* Make sure iStatus is in the range 0 to 50 */
   iStatus = (iStatus < 0) ? 0 : (iStatus > 50) ? 50 : iStatus;

   /* Return the appropriate name according to how much status they have */
   switch ( iStatus/10 )
   {
      default: return ( "Novice"  );
      case  1: return ( "Blooded" );
      case  2: return ( "Skilled" );
      case  3: return ( "Veteran" );
      case  4: return ( "Expert"  );
      case  5: return ( "Master"  );
   }
}



/* Function: FindPerson
 *
 * This function returns a pointer to the specified person.
 *
 * The function takes one parameter, as follows:
 *
 * szTxt: The name of the person to find.
 *
 * This function returns a pointer to a structure, which contains the
 * data of the person located - or NULL if they could not be found.
 */
conn_t* FindPerson( char* szTxt )
{
   conn_t* pstUser;

   /* Loop through all of the connections, checking their names (if any) */
   for ( pstUser = pstFirstConn; pstUser != NULL; pstUser = pstUser->pstNext )
   {
      if ( pstUser->pstBody && !strcasecmp(pstUser->pstBody->a_chName,szTxt) )
      {
         return ( pstUser ); /* The person was found */
      }
   }

   return ( NULL ); /* The person was not found */
}



/* Function: FindBody
 *
 * This function returns a pointer to the specified body.
 *
 * The function takes one parameter, as follows:
 *
 * szTxt: The name of the body to find.
 *
 * This function returns a pointer to a structure, which contains the
 * data of the body located - or NULL if they could not be found.
 */
body_t* FindBody( char* szTxt )
{
   body_t* pstBody;

   /* Loop through all of the connections, checking their names (if any) */
   for ( pstBody = pstFirstBody; pstBody != NULL; pstBody = pstBody->pstNext )
   {
      if ( !strcasecmp(pstBody->a_chName,szTxt) )
      {
         return ( pstBody ); /* The person was found */
      }
   }

   return ( NULL ); /* The person was not found */
}



/* Function: FreeBody
 *
 * This function frees the memory allocated to the specified body.
 *
 * The function takes one parameter, as follows:
 *
 * ppstBody: The body to free.
 *
 * This function has no return value.
 */
void FreeBody( body_t **ppstBody )
{
   if ( (*ppstBody)->szPrompt != NULL )
   {
      /* Free their prompt, if they have one */
      free( (*ppstBody)->szPrompt );
   }

   /* Free their body */
   free( *ppstBody );

   /* Set the body to point to NULL */
   *ppstBody = NULL;
}



/* Function: RollDice
 *
 * This function rolls a dice and returns the result.
 *
 * The function takes one parameter, as follows:
 *
 * iSides: The number of sides on the dice.
 *
 * This function returns an int, which contains the dice roll result.
 */
int RollDice( int iSides )
{
   int iRoll = (1+(int)(((double)(iSides))*rand()/(RAND_MAX+1.0)));

   return ( iRoll );
}



/* Function: GetHealth
 *
 * This function returns the HEALTH of the specified body.
 *
 * The function takes one parameter, as follows:
 *
 * pstBody: The body from which to determine the HEALTH.
 *
 * This function returns an int, which contains the HEALTH of the body.
 */
int GetHealth( body_t *pstBody )
{
   int iHealth = 0;

   iHealth += pstBody->iStats[STR];
   iHealth += pstBody->iStats[STA] * 7;
   iHealth += pstBody->iStats[SIZ] * 3;

   return ( iHealth );
}



/* Function: GetAttack
 *
 * This function returns the ATTACK of the specified body.
 *
 * The function takes one parameter, as follows:
 *
 * pstBody: The body from which to determine the ATTACK.
 *
 * This function returns an int, which contains the ATTACK of the body.
 */
int GetAttack( body_t *pstBody )
{
   int iAttack = 0;

   iAttack += pstBody->iStats[DEX] * 4;
   iAttack += pstBody->iStats[SIZ] * 2;

   return ( iAttack );
}



/* Function: GetDefence
 *
 * This function returns the DEFENCE of the specified body.
 *
 * The function takes one parameter, as follows:
 *
 * pstBody: The body from which to determine the DEFENCE.
 *
 * This function returns an int, which contains the DEFENCE of the body.
 */
int GetDefence( body_t *pstBody )
{
   int iDefence = 0;

   iDefence += pstBody->iStats[WIT] * 4;
   iDefence += pstBody->iStats[DEX] * 2;

   return ( iDefence );
}



/* Function: GetDamage
 *
 * This function returns the DAMAGE of the specified body.
 *
 * The function takes one parameter, as follows:
 *
 * pstBody: The body from which to determine the DAMAGE.
 *
 * This function returns an int, which contains the maximum DAMAGE capacity of 
 * the body.
 */
int GetDamage( body_t *pstBody )
{
   int iDamage = 0;

   iDamage += pstBody->iStats[STR] * 3;
   iDamage += pstBody->iStats[SIZ];

   return ( iDamage );
}



/* Function: GetSpeed
 *
 * This function returns the SPEED of the specified body.
 *
 * The function takes one parameter, as follows:
 *
 * pstBody: The body from which to determine the SPEED.
 *
 * This function returns an int, which contains the SPEED of the body.
 */
int GetSpeed( body_t *pstBody )
{
   int iSpeed = 0;

   /* Each point of WITS gives +2/3 SPEED, each DEXTERITY gives +1/3 */
   iSpeed += pstBody->iStats[WIT] * 2;
   iSpeed += pstBody->iStats[DEX];
   iSpeed /= 3;

   return ( iSpeed );
}



/* Function: GetRoom
 *
 * This function returns the ROOM of the specified body.
 *
 * The function takes one parameter, as follows:
 *
 * pstConn: The connection from which to determine the ROOM.
 *
 * This function returns an int, which contains the ROOM of the body.
 */
int GetRoom( conn_t *  pstConn )
{
   int iRoom = 0; /* If they don't have a body, they'll be in the crowd (0) */

   if ( pstConn != NULL && pstConn->pstBody != NULL )
   {
      iRoom = pstConn->pstBody->iRoom;
   }

   return ( iRoom );
}



/* Function: GetName
 *
 * This function returns the NAME of the specified body.
 *
 * The function takes one parameter, as follows:
 *
 * pstConn: The connection from which to determine the NAME.
 *
 * This function returns an int, which contains the NAME of the body.
 */
char *GetName( conn_t *  pstConn )
{
   char *szName = "Someone in the crowd";

   if ( pstConn != NULL && pstConn->pstBody != NULL )
   {
      szName = pstConn->pstBody->a_chName;
   }

   return ( szName );
}



/* Function: GetStatus
 *
 * This function returns the STATUS of the specified body.
 *
 * The function takes one parameter, as follows:
 *
 * pstConn: The connection from which to determine the STATUS.
 *
 * This function returns a status_t enumeration, which contains the STATUS of 
 * the body.
 */
status_t GetStatus( conn_t *  pstConn )
{
   int eStatus = CROWD;

   if ( pstConn != NULL && pstConn->pstBody != NULL )
   {
      eStatus = pstConn->pstBody->eStatus;
   }

   return ( eStatus );
}