/******************************************************************************
 Copyright (c) 1999-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        : text_io.c
 ******************************************************************************
 Description      : Dynamic description code, previously one of my snippets.
 ******************************************************************************/

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

#include <stdio.h>
#include <string.h>
#include <setjmp.h>
#include <sys/socket.h>
#include "sockets.h"
#include "glad.h"
#include "text_io.h"

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

typedef enum
{
   VARIABLE_FUNCTION_STRING,
   VARIABLE_CONSTANT_STRING,
   VARIABLE_FUNCTION_NUMBER,
   VARIABLE_CONSTANT_NUMBER
} variable_t;

typedef enum
{
   STATE_NONE,
   STATE_LEFT_SIGN,
   STATE_LEFT,
   STATE_DONE_LEFT,
   STATE_OPERATOR,
   STATE_RIGHT_SIGN,
   STATE_RIGHT
} state_t;

typedef union
{
   char* szText;
   int   iNumber;
} replace_t;

typedef struct
{
   const char *     kszVariable;
   const variable_t keType;
   replace_t        stReplace;
} variable_table_t;

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

#define STRING_FUNCTION(f)    VARIABLE_FUNCTION_STRING, {(char *) (f)}
#define STRING_CONSTANT(f)    VARIABLE_CONSTANT_STRING, {(f)}
#define NUMBER_FUNCTION(f)    VARIABLE_FUNCTION_NUMBER, {(void *) (f)}
#define NUMBER_CONSTANT(f)    VARIABLE_CONSTANT_NUMBER, {(void *) (f)}
#define END_OF_LIST           VARIABLE_CONSTANT_NUMBER, {NULL}

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

static int  MainCalc  ( conn_t *pstConn, char szCalcString[] );
static bool StringVar ( char *szVariableString, char **szOutString );
static bool NumberVar ( char *szVariableString, char *szOutString );

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

static inline char *NumberToText( int iNumber, int iPadding );

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

static body_t *s_pstYou = NULL; /* The person to whom the text is specific */
static body_t *s_pstOpp = NULL; /* The combat opponent of s_pstYou */
static ap_t    s_eLocation;     /* The location s_pstYou is current using */
static int     s_iResult;       /* The result of the last calculation */

static jmp_buf s_jBuf;
static int s_iIndex;
static int s_iLevel;

/******************************************************************************
 Number access functions.
 ******************************************************************************/

int GetNumGotBody( void ) { return ( (s_pstYou && s_pstYou->a_chName[0] != '\0') ? 1 : 0 ); }

int GetNumWin    ( void ) { return ( s_pstYou ? s_pstYou->iWin : 0 ); }
int GetNumLoss   ( void ) { return ( s_pstYou ? s_pstYou->iLoss : 0 ); }
int GetNumKill   ( void ) { return ( s_pstYou ? s_pstYou->iKill : 0 ); }
int GetNumStr    ( void ) { return ( s_pstYou ? s_pstYou->iStats[STR] : 0 ); }
int GetNumDex    ( void ) { return ( s_pstYou ? s_pstYou->iStats[DEX] : 0 ); }
int GetNumSta    ( void ) { return ( s_pstYou ? s_pstYou->iStats[STA] : 0 ); }
int GetNumSiz    ( void ) { return ( s_pstYou ? s_pstYou->iStats[SIZ] : 0 ); }
int GetNumWit    ( void ) { return ( s_pstYou ? s_pstYou->iStats[WIT] : 0 ); }
int GetNumAtt    ( void ) { return ( s_pstYou ? GetAttack(s_pstYou) : 0 ); }
int GetNumDef    ( void ) { return ( s_pstYou ? GetDefence(s_pstYou) : 0 ); }
int GetNumDam    ( void ) { return ( s_pstYou ? GetDamage(s_pstYou) : 0 ); }
int GetNumWounds ( void ) { return ( s_pstYou ? s_pstYou->iDam : 0 ); }
int GetNumHealth ( void ) { return ( s_pstYou ? GetHealth(s_pstYou) : 0 ); }
int GetNumSpeed  ( void ) { return ( s_pstYou ? GetSpeed(s_pstYou) : 0 ); }
int GetNumAPLeft ( void ) { return ( s_pstYou ? s_pstYou->iActions[AP_LEFT_HAND] : 0 ); }
int GetNumAPRight( void ) { return ( s_pstYou ? s_pstYou->iActions[AP_RIGHT_HAND] : 0 ); }
int GetNumAPFeet ( void ) { return ( s_pstYou ? s_pstYou->iActions[AP_LEGS] : 0 ); }
int GetNumAPEyes ( void ) { return ( s_pstYou ? s_pstYou->iActions[AP_EYES] : 0 ); }

/******************************************************************************
 Text access functions.
 ******************************************************************************/

char *GetTxtResult ( void ) { return ( NumberToText(s_iResult,1) ); }

char *GetTxtWin    ( void ) { return ( NumberToText(GetNumWin(),2) ); }
char *GetTxtLoss   ( void ) { return ( NumberToText(GetNumLoss(),2) ); }
char *GetTxtKill   ( void ) { return ( NumberToText(GetNumKill(),2) ); }
char *GetTxtStr    ( void ) { return ( NumberToText(GetNumStr(),1) ); }
char *GetTxtDex    ( void ) { return ( NumberToText(GetNumDex(),1) ); }
char *GetTxtSta    ( void ) { return ( NumberToText(GetNumSta(),1) ); }
char *GetTxtSiz    ( void ) { return ( NumberToText(GetNumSiz(),1) ); }
char *GetTxtWit    ( void ) { return ( NumberToText(GetNumWit(),1) ); }
char *GetTxtAtt    ( void ) { return ( NumberToText(GetNumAtt(),2) ); }
char *GetTxtDef    ( void ) { return ( NumberToText(GetNumDef(),2) ); }
char *GetTxtDam    ( void ) { return ( NumberToText(GetNumDam(),2) ); }
char *GetTxtWounds ( void ) { return ( NumberToText(GetNumWounds(),2) ); }
char *GetTxtHealth ( void ) { return ( NumberToText(GetNumHealth(),2) ); }
char *GetTxtSpeed  ( void ) { return ( NumberToText(GetNumSpeed(),2) ); }
char *GetTxtAPLeft ( void ) { return ( NumberToText(GetNumAPLeft(),1) ); }
char *GetTxtAPRight( void ) { return ( NumberToText(GetNumAPRight(),1) ); }
char *GetTxtAPFeet ( void ) { return ( NumberToText(GetNumAPFeet(),1) ); }
char *GetTxtAPEyes ( void ) { return ( NumberToText(GetNumAPEyes(),1) ); }

char *GetTxtTec_e  ( void ) { return ( s_pstYou ? s_pstYou->a_chTechniques[AP_EYES] : "" ); }
char *GetTxtTec_l  ( void ) { return ( s_pstYou ? s_pstYou->a_chTechniques[AP_LEFT_HAND] : "" ); }
char *GetTxtTec_r  ( void ) { return ( s_pstYou ? s_pstYou->a_chTechniques[AP_RIGHT_HAND] : "" ); }
char *GetTxtTec_f  ( void ) { return ( s_pstYou ? s_pstYou->a_chTechniques[AP_LEGS] : "" ); }

char *GetTxtName( void )
{
   return( s_pstYou ? s_pstYou->a_chName : "someone" );
}

char *GetTxtNamePadded( void )
{
   static char s_szName[16];
   sprintf( s_szName, "%-15s", s_pstYou ? s_pstYou->a_chName : "someone" );
   return ( s_szName );
}

char *GetTxtStatus( void )
{
   return( s_pstYou ? StatusName(s_pstYou) : "none" );
}

char *GetTxtOppName( void )
{
   return( s_pstOpp ? s_pstOpp->a_chName : "someone" );
}

char *GetTxtHand( void )
{
   return ( (s_eLocation == AP_LEFT_HAND) ? "left" : "right" );
}

char *GetTxtLocation( void )
{
   switch ( s_eLocation )
   {
      default:            return( "<bugged location>" );
      case AP_LEFT_HAND:  return( "left hand" );
      case AP_RIGHT_HAND: return( "right hand" );
      case AP_EYES:       return( "eyes" );
      case AP_LEGS:       return( "legs" );
   }
}

char *GetTxtHisHer( void )
{
   return( s_pstYou ? s_pstYou->eSex == MALE ? "his" : "her" : "their" );
}

char *GetTxtHisHers( void )
{
   return( s_pstYou ? s_pstYou->eSex == MALE ? "his" : "hers" : "their" );
}

char *GetTxtHimHer( void )
{
   return( s_pstYou ? s_pstYou->eSex == MALE ? "him" : "her" : "their" );
}

char *GetTxtHeShe( void )
{
   return( s_pstYou ? s_pstYou->eSex == MALE ? "he" : "she" : "they" );
}

char *GetTxtOppHisHer( void )
{
   return( s_pstOpp ? s_pstOpp->eSex == MALE ? "his" : "her" : "their" );
}

char *GetTxtOppHisHers( void )
{
   return( s_pstOpp ? s_pstOpp->eSex == MALE ? "his" : "hers" : "their" );
}

char *GetTxtOppHimHer( void )
{
   return( s_pstOpp ? s_pstOpp->eSex == MALE ? "him" : "her" : "their" );
}

char *GetTxtOppHeShe( void )
{
   return( s_pstOpp ? s_pstOpp->eSex == MALE ? "he" : "she" : "they" );
}

/******************************************************************************
 Variable lookup table.
 ******************************************************************************/

const variable_table_t a_kstVarTable[] =
{
   /* Colours */
   { "normal",             STRING_CONSTANT("\033[0;0m") },
   { "red",                STRING_CONSTANT("\033[0;31m") },
   { "green",              STRING_CONSTANT("\033[0;32m") },
   { "yellow",             STRING_CONSTANT("\033[0;33m") },
   { "blue",               STRING_CONSTANT("\033[0;34m") },
   { "magenta",            STRING_CONSTANT("\033[0;35m") },
   { "cyan",               STRING_CONSTANT("\033[0;36m") },
   { "white",              STRING_CONSTANT("\033[0;37m") },

   /* Constants */
   { "true",               NUMBER_CONSTANT(1) },
   { "false",              NUMBER_CONSTANT(0) },

   /* Pure string functions */
   { "result",             STRING_FUNCTION(GetTxtResult) },
   { "name",               STRING_FUNCTION(GetTxtName) },
   { "name.padded",        STRING_FUNCTION(GetTxtNamePadded) },
   { "status",             STRING_FUNCTION(GetTxtStatus) },
   { "opponent",           STRING_FUNCTION(GetTxtOppName) },
   { "hand",               STRING_FUNCTION(GetTxtHand) },
   { "location",           STRING_FUNCTION(GetTxtLocation) },
   { "his/her",            STRING_FUNCTION(GetTxtHisHer) },
   { "his/hers",           STRING_FUNCTION(GetTxtHisHers) },
   { "him/her",            STRING_FUNCTION(GetTxtHimHer) },
   { "he/she",             STRING_FUNCTION(GetTxtHeShe) },
   { "opponent.his/her",   STRING_FUNCTION(GetTxtOppHisHer) },
   { "opponent.his/hers",  STRING_FUNCTION(GetTxtOppHisHers) },
   { "opponent.him/her",   STRING_FUNCTION(GetTxtOppHimHer) },
   { "opponent.he/she",    STRING_FUNCTION(GetTxtOppHeShe) },
   { "eyes",               STRING_FUNCTION(GetTxtTec_e) },
   { "left",               STRING_FUNCTION(GetTxtTec_l) },
   { "right",              STRING_FUNCTION(GetTxtTec_r) },
   { "feet",               STRING_FUNCTION(GetTxtTec_f) },

   /* Pure number functions */
   { "got.body",           NUMBER_FUNCTION(GetNumGotBody) },

   /* String AND number functions */
   { "win",                NUMBER_FUNCTION(GetNumWin) },
   { "win",                STRING_FUNCTION(GetTxtWin) },
   { "loss",               NUMBER_FUNCTION(GetNumLoss) },
   { "loss",               STRING_FUNCTION(GetTxtLoss) },
   { "kill",               NUMBER_FUNCTION(GetNumKill) },
   { "kill",               STRING_FUNCTION(GetTxtKill) },
   { "str",                NUMBER_FUNCTION(GetNumStr) },
   { "str",                STRING_FUNCTION(GetTxtStr) },
   { "dex",                NUMBER_FUNCTION(GetNumDex) },
   { "dex",                STRING_FUNCTION(GetTxtDex) },
   { "sta",                NUMBER_FUNCTION(GetNumSta) },
   { "sta",                STRING_FUNCTION(GetTxtSta) },
   { "siz",                NUMBER_FUNCTION(GetNumSiz) },
   { "siz",                STRING_FUNCTION(GetTxtSiz) },
   { "wit",                NUMBER_FUNCTION(GetNumWit) },
   { "wit",                STRING_FUNCTION(GetTxtWit) },
   { "attack",             NUMBER_FUNCTION(GetNumAtt) },
   { "attack",             STRING_FUNCTION(GetTxtAtt) },
   { "defence",            NUMBER_FUNCTION(GetNumDef) },
   { "defence",            STRING_FUNCTION(GetTxtDef) },
   { "damage",             NUMBER_FUNCTION(GetNumDam) },
   { "damage",             STRING_FUNCTION(GetTxtDam) },
   { "wounds",             NUMBER_FUNCTION(GetNumWounds) },
   { "wounds",             STRING_FUNCTION(GetTxtWounds) },
   { "health",             NUMBER_FUNCTION(GetNumHealth) },
   { "health",             STRING_FUNCTION(GetTxtHealth) },
   { "speed",              NUMBER_FUNCTION(GetNumSpeed) },
   { "speed",              STRING_FUNCTION(GetTxtSpeed) },
   { "ap.l",               NUMBER_FUNCTION(GetNumAPLeft) },
   { "ap.l",               STRING_FUNCTION(GetTxtAPLeft) },
   { "ap.r",               NUMBER_FUNCTION(GetNumAPRight) },
   { "ap.r",               STRING_FUNCTION(GetTxtAPRight) },
   { "ap.f",               NUMBER_FUNCTION(GetNumAPFeet) },
   { "ap.f",               STRING_FUNCTION(GetTxtAPFeet) },
   { "ap.e",               NUMBER_FUNCTION(GetNumAPEyes) },
   { "ap.e",               STRING_FUNCTION(GetTxtAPEyes) },

   { NULL,  END_OF_LIST } /* Always leave this at end */
};

/******************************************************************************
 Global operations.
 ******************************************************************************/

/* Function: TextParse
 *
 * This function copies an input string to an output string, converting all the 
 * tags (variables) in the input string into their appropriate replacements in 
 * the process.
 *
 * The function takes three parameters, as follows:
 *
 * pstConn: The connection of the person to whom the text relates.
 * kszInString: The input string (which may or may not contain variables).
 * szOutString: The output string (after being processed).
 *
 * This function has no return value.
 */
void TextParse( body_t *pstBody, const char *kszInString, char *szOutString )
{
   conn_t *pstConn = pstBody->pstConn;
   char    a_chVariableName[256];
   int     iVariableCount = 0;
   bool    bIsVariable = FALSE;
   char    a_chCalculation[256];
   int     iCalculationCount = 0;
   bool    bCalculation = FALSE;
   int     iNest = 0;
   int     iValidNest = 0;

   s_pstYou = pstBody; /* Initialise variable used in function table */
   s_pstOpp = s_pstYou->pstOpponent; /* Initialise their opponent */

   while ( *kszInString )
   {
      switch ( *kszInString )
      {
         default:
            if ( iValidNest != iNest )
            {
               kszInString++;
            }
            else if ( bIsVariable )
            {
               a_chVariableName[iVariableCount++] = *kszInString++;
            }
            else if ( bCalculation )
            {
               a_chCalculation[iCalculationCount++] = *kszInString++;
            }
            else
            {
               *szOutString++ = *kszInString++;
            }
            break;
         case '{':
            if ( iValidNest != iNest )
            {
               kszInString++;
               break;
            }
            kszInString++;
            a_chVariableName[iVariableCount=0] = '\0';
            if ( bIsVariable ) PutOutput( pstConn, TO_USER, "Nested variable names not allowed.\n\r" );
            bIsVariable = TRUE;
            break;
         case '}':
            if ( iValidNest != iNest )
            {
               kszInString++;
               break;
            }
            if ( !bIsVariable ) PutOutput( pstConn, TO_USER, "Variable terminator without variable.\n\r" );
            a_chVariableName[iVariableCount] = '\0';
            kszInString++;
            bIsVariable = FALSE;
            if ( bCalculation )
            {
               (void) NumberVar( &a_chVariableName[0], &a_chCalculation[iCalculationCount] ); /* Calculator variable */
               iCalculationCount = strlen( a_chCalculation ); /* Nasty hack to recalculate iCalculationCount */
            }
            else
            {
               StringVar( &a_chVariableName[0], &szOutString ); /* String variable */
            }
            break;
         case '[':
            if ( iValidNest != iNest )
            {
               kszInString++;
               break;
            }
            kszInString++;
            a_chCalculation[iCalculationCount=0] = '\0';
            if ( bCalculation ) PutOutput( pstConn, TO_USER, "Nested calculations not allowed.\n\r" );
            bCalculation = TRUE;
            break;
         case ']':
            if ( iValidNest != iNest )
            {
               iNest++;
               kszInString++;
               break;
            }
            if ( !bCalculation ) PutOutput( pstConn, TO_USER, "Calculation terminator without calculation.\n\r" );
            a_chCalculation[iCalculationCount] = '\0';
            kszInString++;
            iNest++;
            s_iResult = TextCalculate( pstBody->pstConn, &a_chCalculation[0] );
            iValidNest += !!s_iResult;
            bCalculation = FALSE;
            break;
         case '|':
            if ( --iNest < 0 )
            {
               PutOutput( pstConn, TO_USER, "Conditional terminator without condition.\n\r" );
               iNest = 0;
            }
            if ( iValidNest > iNest ) iValidNest = iNest;
            kszInString++;
            break;
      }
   }
   *szOutString = '\0';
}



/* Function: TextDisplay
 *
 * This function displays an unprocessed string in a specially formatted style, 
 * so that conditional statements are indented and thus easy to read.  It is 
 * designed primarily for use by people who are working on dynamic descriptions 
 * online.
 *
 * The function takes two parameters, as follows:
 *
 * pstConn: The connection of the person to whom the text relates.
 * szText:  The unprocessed string.
 *
 * This function has no return value.
 */
void TextDisplay( conn_t *pstConn, char szText[] )
{
   int iAlign = 0;
   int i;

   while ( *szText )
   {
      switch ( *szText )
      {
         default:
            PutOutput( pstConn, TO_USER, "%c", *szText );
            break;
         case '[':
            PutOutput( pstConn, TO_USER, "\n" );
            for ( i = 0; i < iAlign; i++ ) 
            {
               PutOutput( pstConn, TO_USER, "   " );
            }
            PutOutput( pstConn, TO_USER, "[" );
            break;
         case ']':
            PutOutput( pstConn, TO_USER, "]\n" );
            iAlign++;
            for ( i = 0; i < iAlign; i++ ) 
            {
               PutOutput( pstConn, TO_USER, "   " );
            }
            break;
         case '|':
            PutOutput( pstConn, TO_USER, "\n" );
            iAlign--;
            for ( i = 0; i < iAlign; i++ ) 
            {
               PutOutput( pstConn, TO_USER, "   " );
            }
            PutOutput( pstConn, TO_USER, "|" );
            break;
      }
      szText++;
   }
   PutOutput( pstConn, TO_USER, "\n" );
}



/* Function: TextCalculate
 *
 * This function works as a simple calculator.  It reads in a string which 
 * contains a formula and works out the result, which is then returned.  The 
 * primary use of this function is to determine the result of "if" statements 
 * within the dynamic descriptions (it gets called after any text replacements 
 * have been made, because it doesn't understand variables, only true numbers).
 * Logical expressions can also be evaluated by this function, returning 1 for 
 * TRUE and 0 for FALSE.  This function is global in case you wish to use it 
 * as a standalone calculator (which isn't much use, but can be rather fun).
 *
 * The function takes two parameters, as follows:
 *
 * pstConn:       The connection of the person to whom the text relates.
 * szCalculation: The string containing the calculation.
 *
 * This function returns an int, which contains the result of the expression.
 */
int TextCalculate( conn_t *pstConn, char *szCalculation )
{
   int iResult;

   s_iIndex = -1;
   s_iLevel = 0;

   if ( setjmp( s_jBuf ) )
   {
      return ( 0 ); /* Unable to perform calculation */
   }

   if ( szCalculation[0] == '\0' )
   {
      return ( 0 ); /* Unable to perform calculation */
   }

   iResult = MainCalc( pstConn, szCalculation );

   if ( s_iLevel != 1 )
   {
      PutOutput(pstConn, TO_USER, "\nNon-matching brackets.\n" );
      return ( 0 );
   }

   s_iIndex = -1;
   s_iLevel = 0;

   return ( iResult );
}



/* Function: TextInit
 *
 * This function initialises variables ready for the dynamic description 
 * code to use.
 *
 * The function takes two parameters, as follows:
 *
 * eLocation: The body location currently being used.
 *
 * This function has no return value.
 */
void TextInit( ap_t eLocation )
{
   /* Store the variable */
   s_eLocation = eLocation;
}

/******************************************************************************
 Local operations.
 ******************************************************************************/

/* Function: MainCalc
 *
 * This is the real calculator function, getting called by TextCalculate.  It 
 * uses recursion to determine precedence when evaluating the expression.
 *
 * The function takes two parameters, as follows:
 *
 * pstConn:      The connection of the person to whom the text relates.
 * szCalcString: The string containing the part of the expression currently 
 *               being evaluated.
 *
 * This function returns an int, which contains the result of the expression.
 */
static int MainCalc( conn_t *pstConn, char szCalcString[] )
{
   state_t    eState     = STATE_NONE;
   int        iLeft      = 0;
   int        iRight     = 0;
   bool       bLeftNeg   = FALSE;
   bool       bRightNeg  = FALSE;
   char       chOperator = '\0';
   bool       bGLEqual   = FALSE;

   s_iLevel++;

   while ( szCalcString[++s_iIndex] != '\0' )
   {
      switch ( szCalcString[s_iIndex] )
      {
         case '0': case '1': case '2': case '3': case '4': 
         case '5': case '6': case '7': case '8': case '9': 
            switch ( eState )
            {
               case STATE_LEFT_SIGN:
                  bLeftNeg = TRUE;
               case STATE_NONE:
               case STATE_LEFT:
               case STATE_DONE_LEFT: /* TBD - check this */
                  eState = STATE_LEFT;
                  if ( bLeftNeg == TRUE ) iLeft = 0 - iLeft; /* Make positive */
                  iLeft *= 10;
                  iLeft += (int) (szCalcString[s_iIndex] - '0');
                  if ( bLeftNeg == TRUE ) iLeft = 0 - iLeft; /* Make negative */
                  break;
               case STATE_RIGHT_SIGN:
                  bRightNeg = TRUE;
               case STATE_OPERATOR:
               case STATE_RIGHT:
                  eState = STATE_RIGHT;
                  if ( bRightNeg == TRUE ) iRight = 0 - iRight; /* Make positive */
                  iRight *= 10;
                  iRight += (int) (szCalcString[s_iIndex] - '0');
                  if ( bRightNeg == TRUE ) iRight = 0 - iRight; /* Make negative */
                  break;
            }
            break;

         case '+': case '-': case '*': case '/':
            if ( eState == STATE_RIGHT )
            {
               switch ( chOperator )
               {
                  case '+':
                     iLeft = iLeft + iRight;
                     break;
                  case '-':
                     iLeft = iLeft - iRight;
                     break;
                  case '*':
                     iLeft = iLeft * iRight;
                     break;
                  case '/':
                     if ( iRight == 0 )
                     {
                        PutOutput( pstConn, TO_USER, "\nDivision by zero.\n" );
                        longjmp( s_jBuf, 1 );
                     }
                     iLeft = iLeft / iRight;
                     break;
                  default:
                     PutOutput( pstConn, TO_USER, "\nInvalid position of closed bracket.\n" );
                     longjmp( s_jBuf, 1 );
               }
               bRightNeg = FALSE;
               iRight = 0;
               eState = STATE_OPERATOR;
            }
            else if ( eState == STATE_NONE && szCalcString[s_iIndex] == '-' )
            {
               bLeftNeg = TRUE;
               eState = STATE_LEFT_SIGN;
            }
            else if ( eState == STATE_OPERATOR && szCalcString[s_iIndex] == '-' )
            {
               bRightNeg = TRUE;
               eState = STATE_RIGHT_SIGN;
            }
            else if ( eState > STATE_OPERATOR )
            {
               PutOutput( pstConn, TO_USER, "\nInvalid position of operator.\n" );
               longjmp( s_jBuf, 1 );
            }
            else
            {
               eState = STATE_OPERATOR;
               chOperator = szCalcString[s_iIndex];
            }
            break;

         case '=': case '!':
            if ( szCalcString[s_iIndex+1] != '=' )
            {
               PutOutput( pstConn, TO_USER, "\nInvalid operator '%c'.\n", szCalcString[s_iIndex] );
               longjmp( s_jBuf, 1 );
            }
            ++s_iIndex;
            if ( eState == STATE_RIGHT )
            {
               switch ( chOperator )
               {
                  case '=':
                     iLeft = (iLeft == iRight);
                     break;
                  case '!':
                     iLeft = (iLeft != iRight);
                     break;
                  default:
                     PutOutput( pstConn, TO_USER, "\nInvalid position of closed bracket.\n" );
                     longjmp( s_jBuf, 1 );
               }
               bRightNeg = FALSE;
               iRight = 0;
            }
            else if ( eState > STATE_OPERATOR )
            {
               PutOutput( pstConn, TO_USER, "\nInvalid position of operator.\n" );
               longjmp( s_jBuf, 1 );
            }
            else
            {
               eState = STATE_OPERATOR;
               chOperator = szCalcString[s_iIndex-1];
            }
            break;

         case '<': case '>':
            if ( szCalcString[s_iIndex+1] == '=' )
            {
               ++s_iIndex;
               bGLEqual = TRUE;
            }
            if ( eState == STATE_RIGHT )
            {
               switch ( chOperator )
               {
                  case '[':
                     iLeft = (iLeft <= iRight);
                     break;
                  case '<':
                     iLeft = (iLeft < iRight);
                     break;
                  case ']':
                     iLeft = (iLeft >= iRight);
                     break;
                  case '>':
                     iLeft = (iLeft > iRight);
                     break;
                  default:
                     PutOutput( pstConn, TO_USER, "\nInvalid position of closed bracket.\n" );
                     longjmp( s_jBuf, 1 );
               }
               bRightNeg = FALSE;
               iRight = 0;
            }
            else if ( eState > STATE_OPERATOR )
            {
               PutOutput( pstConn, TO_USER, "\nInvalid position of operator.\n" );
               longjmp( s_jBuf, 1 );
            }
            else
            {
               eState = STATE_OPERATOR;
               if ( bGLEqual )
               {
                  if ( szCalcString[s_iIndex-1] == '<' )
                  {
                     chOperator = '[';
                  }
                  else /* szCalcString[s_iIndex-1] == '>' */
                  {
                     chOperator = ']';
                  }
               }
               else
               {
                  chOperator = szCalcString[s_iIndex];
               }
            }
            bGLEqual = FALSE;
            break;

         case '&':
            if ( szCalcString[s_iIndex+1] != '&' )
            {
               PutOutput( pstConn, TO_USER, "\nInvalid operator '%c'.\n", szCalcString[s_iIndex]);
               longjmp( s_jBuf, 1 );
            }
            ++s_iIndex;
            if ( eState == STATE_RIGHT )
            {
               switch ( chOperator )
               {
                  case '&':
                     iLeft = (iLeft && iRight);
                     break;
                  default:
                     PutOutput( pstConn, TO_USER, "\nInvalid position of closed bracket.\n" );
                     longjmp( s_jBuf, 1 );
               }
               bRightNeg = FALSE;
               iRight = 0;
            }
            else if ( eState > STATE_OPERATOR )
            {
               PutOutput( pstConn, TO_USER, "\nInvalid position of operator.\n" );
               longjmp( s_jBuf, 1 );
            }
            else
            {
               eState = STATE_OPERATOR;
               chOperator = szCalcString[s_iIndex-1];
            }
            break;

         case '|':
            if ( szCalcString[s_iIndex+1] != '|' )
            {
               PutOutput( pstConn, TO_USER, "\nInvalid operator '%c'.\n", szCalcString[s_iIndex] );
               longjmp( s_jBuf, 1 );
            }
            ++s_iIndex;
            if ( eState == STATE_RIGHT )
            {
               switch ( chOperator )
               {
                  case '|':
                     iLeft = (iLeft || iRight);
                     break;
                  default:
                     PutOutput( pstConn, TO_USER, "\nInvalid position of closed bracket.\n" );
                     longjmp( s_jBuf, 1 );
               }
               bRightNeg = FALSE;
               iRight = 0;
            }
            else if ( eState > STATE_OPERATOR )
            {
               PutOutput( pstConn, TO_USER, "\nInvalid position of operator.\n" );
               longjmp( s_jBuf, 1 );
            }
            else
            {
               eState = STATE_OPERATOR;
               chOperator = szCalcString[s_iIndex-1];
            }
            break;

         case ' ':
            break;

         case '(':
            if ( eState == STATE_NONE )
            {
               iLeft = MainCalc( pstConn, szCalcString );
               eState = STATE_DONE_LEFT;
            }
            else if ( eState == STATE_OPERATOR )
            {
               iRight = MainCalc( pstConn, szCalcString );
               eState = STATE_RIGHT;
            }
            else
            {
               PutOutput( pstConn, TO_USER, "\nInvalid position of open bracket.\n" );
               longjmp( s_jBuf, 1 );
            }
            break;

         case ')':
            s_iLevel--;
            if ( eState == STATE_DONE_LEFT || eState == STATE_LEFT )
            {
               return ( iLeft );
            }
            if ( eState != STATE_RIGHT )
            {
               PutOutput( pstConn, TO_USER, "\nInvalid position of closed bracket.\n" );
               longjmp( s_jBuf, 1 );
            }
            switch ( chOperator )
            {
               case '+': return ( iLeft + iRight );
               case '-': return ( iLeft - iRight );
               case '*': return ( iLeft * iRight );
               case '/': 
                  if ( iRight == 0 )
                  {
                     PutOutput( pstConn, TO_USER, "\nDivision by zero.\n" );
                     longjmp( s_jBuf, 1 );
                  }
                  return ( iLeft / iRight );
               case '=': return ( iLeft == iRight );
               case '!': return ( iLeft != iRight );
               case '<': return ( iLeft < iRight );
               case '>': return ( iLeft > iRight );
               case '[': return ( iLeft <= iRight );
               case ']': return ( iLeft >= iRight );
               case '&': return ( iLeft && iRight );
               case '|': return ( iLeft || iRight );
            }
            PutOutput( pstConn, TO_USER, "\nInvalid position of closed bracket.\n" );
            longjmp( s_jBuf, 1 );

         case '\0':
            if ( s_iLevel > 1 )
            {
               PutOutput( pstConn, TO_USER, "\nInvalid character '%d'.\n", szCalcString[s_iIndex] );
               longjmp( s_jBuf, 1 );
            }
            PutOutput( pstConn, TO_USER, "\nDone.\n" );
            longjmp( s_jBuf, 1 );


         default:
            PutOutput( pstConn, TO_USER, "\nInvalid character '%c'.\n", szCalcString[s_iIndex] );
            longjmp( s_jBuf, 1 );
      }
   };

   if ( s_iLevel != 1 )
   {
      PutOutput( pstConn, TO_USER, "\nNon-matching brackets.\n" );
      longjmp( s_jBuf, 1 );
   }

   if ( eState != STATE_RIGHT )
   {
      if ( eState == STATE_DONE_LEFT || eState == STATE_LEFT )
      {
         return ( iLeft );
      }
      PutOutput( pstConn, TO_USER, "\nInvalid string.\n" );
      longjmp( s_jBuf, 1 );
   }
   switch ( chOperator )
   {
      case '+': return ( iLeft + iRight );
      case '-': return ( iLeft - iRight );
      case '*': return ( iLeft * iRight );
      case '/': 
         if ( iRight == 0 )
         {
            PutOutput( pstConn, TO_USER, "\nDivision by zero.\n" );
            longjmp( s_jBuf, 1 );
         }
         return ( iLeft / iRight );
      case '=': return ( iLeft == iRight );
      case '!': return ( iLeft != iRight );
      case '<': return ( iLeft < iRight );
      case '>': return ( iLeft > iRight );
      case '[': return ( iLeft <= iRight );
      case ']': return ( iLeft >= iRight );
      case '&': return ( iLeft && iRight );
      case '|': return ( iLeft || iRight );
   }
   PutOutput( pstConn, TO_USER, "\nInvalid calculation.\n" );

   return ( 0 );
}



/* Function: StringVar
 *
 * This function iterates through the variable table looking for a variable 
 * name match.  It only checks the string variables, which are used purely 
 * for text replacement.
 *
 * The function takes two parameters, as follows:
 *
 * szVariableString: A string containing the variable name to search for.
 * szOutString:      The string to which the text replacement is written.
 *
 * This function returns a bool, which is TRUE if a match was found and FALSE 
 * if not.
 */
static bool StringVar( char *szVariableString, char **szOutString )
{
   int i = -1;

   while ( a_kstVarTable[++i].kszVariable != NULL )
   {
      if ( !strcmp( szVariableString, a_kstVarTable[i].kszVariable ) )
      {
         char *szReplace;

         switch ( a_kstVarTable[i].keType )
         {
            case VARIABLE_FUNCTION_STRING:
               szReplace = ((char*(*)())(a_kstVarTable[i].stReplace.szText))();
               break;
            case VARIABLE_CONSTANT_STRING:
               szReplace = a_kstVarTable[i].stReplace.szText;
               break;
            default: continue; /* Cannot resolve numbers */
         }

         while ( *szReplace )
         {
            *(*szOutString)++ = *szReplace++;
         }

         return ( TRUE );
      }
   }
   return ( FALSE );
}



/* Function: NumberVar
 *
 * This function iterates through the variable table looking for a variable 
 * name match.  It only checks the number variables, which are used purely 
 * for expression evaluation within the calculator.
 *
 * The function takes two parameters, as follows:
 *
 * szVariableString: A string containing the variable name to search for.
 * szOutString:      The string to which the text replacement is written.
 *
 * This function returns a bool, which is TRUE if a match was found and FALSE 
 * if not.
 */
static bool NumberVar( char *szVariableString, char *szOutString )
{
   int i = -1;

   while ( a_kstVarTable[++i].kszVariable != NULL )
   {
      if ( !strcmp( szVariableString, a_kstVarTable[i].kszVariable ) )
      {
         int   iReplace;
         char  a_chReplace[64];
         char *szReplace = &a_chReplace[0];

         switch ( a_kstVarTable[i].keType )
         {
            case VARIABLE_FUNCTION_NUMBER:
               iReplace = ((int(*)())(a_kstVarTable[i].stReplace.iNumber))();
               break;
            case VARIABLE_CONSTANT_NUMBER:
               iReplace = a_kstVarTable[i].stReplace.iNumber;
               break;
            default: continue; /* Cannot resolve strings */
         }

         sprintf( szReplace, "%d", iReplace );

         while ( (*szOutString++ = *szReplace++) );

         return ( TRUE );
      }
   }
   return ( FALSE );
}



/* Function: NumberToText
 *
 * This function takes in a number, stores it in an internal string, then 
 * returns a pointer to that string.  This function is used by many of the 
 * GetTxt access functions, so it is stored as an inline so that each such 
 * function gets it's own character array.
 *
 * The function takes one parameter, as follows:
 *
 * iNumber: The number to be stored in the string.
 *
 * This function returns a pointer to a static string which contains the 
 * number.
 */
static inline char *NumberToText( int iNumber, int iPadding )
{
   char        a_chPadding  [32]; /* Buffer to store the padding */
   static char s_a_chResult [64]; /* Buffer to store the number */

   /* Store the padding if appropriate*/
   if ( iPadding > 1 )
   {
      sprintf( a_chPadding, "%%-%dd", iPadding );
   }
   else /* iPadding <= 1 */
   {
      strcpy( a_chPadding, "%d" );
   }

   /* Store the number in the buffer */
   sprintf( s_a_chResult, a_chPadding, iNumber );

   /* Return the address of the buffer */
   return ( s_a_chResult );
}