  // AdvIO.cpp : I/O functions
//

#include "stdafx.h"
#include "stdio.h"
#include "string.h"

#include "AdvDatabase.h"
#include "AdvDef.h"
#include "AdvIO.h"

/////////////////////
// Forward references
void displayTextLines
  (PTR_TEXTLINES  pTextLines);

void displayTextLinesReplaceText
  (PTR_TEXTLINES  pTextLines,
   char*          pszReplacementText);

long translateVocabWord
  (AdvGlobalContext&  gc,
  char*               pszVocabWord);

//////
// API

void describeObject
  (AdvGlobalContext&  gc,         // global context
   long               nObject,    // object id
   long               nState)     // object state
//
//  Displays a the description of an object in a specific state.
//
{
  if (nObject < MAXOBJECTS)
      if (nState <= gc.m_objects [nObject].nStates)
         {
           TEXTLINES*  pTextLines = gc.m_objects [nObject].stateDescriptions [nState];
           if (pTextLines != NULL)
              {
                displayTextLines (pTextLines);
                return;
              }
         }

  char  szNumber [STRINGLEN];
  sprintf (szNumber, "%d", nObject);
  displayError (ERR_DESCRIBE_OBJECT, szNumber, NULL, NULL);
}

void describeObjectInven
  (AdvGlobalContext&  gc,         // global context
   long               nObject)    // object id
//
//  Displays the inventory description of an object.
//
{
  if (nObject < MAXOBJECTS)
     if (gc.m_objects [nObject].pszInventory != NULL)
        {
          // Don't display empty string!
          if (strlen (gc.m_objects [nObject].pszInventory) > 0)
             printf ("   %s\n", gc.m_objects [nObject].pszInventory);
          return;
        }

  char  szNumber [STRINGLEN];
  sprintf (szNumber, "%d", nObject);
  displayError (ERR_DESCRIBE_OBJECT_INVEN, szNumber, NULL, NULL);
}

void describePlace
  (AdvGlobalContext&  gc,       // global context
   long               nPlace,   // location id
   bool               bLong)    // flag: display long description
//
//  Displays a room's description
//
{
  PTR_TEXTLINES*  descriptionArray = & (bLong ? gc.m_longp[0] : gc.m_shortp[0]);
  if (nPlace < MAXPLACES)
     {
       displayTextLines (descriptionArray [nPlace]);
       return;
     }

  // Display error if place could be found
  char  szNumber [STRINGLEN];
  sprintf (szNumber, "%d", nPlace);
  displayError (ERR_DESCRIBE_PLACE, szNumber, NULL, NULL);
}

void displayError
  (long   nErrorCode,   // error code
   char*  szArg1,       // optional argument 1
   char*  szArg2,       // optional argument 2
   char*  szArg3)       // optional argument 3
//
//  Displays a standard error message.
//
{
  fprintf (stderr, "<GLITCH!>\n");

  switch (nErrorCode)
  {
    case ERR_CANT_OPEN_FILE:
      fprintf (stderr, "Can't open file %s", szArg1);
      break;

    case ERR_INVALID_DATAFILE_ENTRY:
      fprintf (stderr, "Invalid entry in datafile %s\n%s", szArg1, szArg2);
      break;

    case ERR_TOO_MANY_ENTRIES:
      fprintf (stderr, "Too many entries in datafile %s\n", szArg1);
      break;

    case ERR_OUT_OF_MEMORY_READ_FILE:
      fprintf (stderr, "Out of memory at line %s in datafile %s\n%s", szArg1, szArg2);
      break;

    case ERR_DESCRIBE_OBJECT:
      fprintf (stderr, "Attempt to describe unknown object %s", szArg1);
      break;

    case ERR_DESCRIBE_OBJECT_INVEN:
      fprintf (stderr, "Attempt to describe unknown object inventory %s", szArg1);
      break;

    case ERR_DESCRIBE_PLACE:
      fprintf (stderr, "Attempt to describe unknown location %s", szArg1);
      break;

    case ERR_SAY_MESSAGE:
      fprintf (stderr, "Attempt to say unknown message %s", szArg1);
      break;

    case ERR_SAY_MESSAGE_TEXT:
      fprintf (stderr, "Attempt to say unknown message %s (%s)", szArg1, szArg2);
      break;

    case ERR_SAY_MESSAGE_VALUE:
      fprintf (stderr, "Attempt to say unknown message %s (%s)", szArg1, szArg2);
      break;

    case ERR_SAY_MESSAGE_UNKNOWN_WORD:
      fprintf (stderr, "Attempt to say message %s with unknown word %s", szArg1, szArg2);
      break;

    case ERR_SAY_UNKNOWN_MESSAGE_WORD:
      fprintf (stderr, "Attempt to say unknown message %s with word %s", szArg1, szArg2);
      break;

    default:
      fprintf (stderr, "%s %d", "Unknown error - ", nErrorCode);
      break;
  }

  fprintf (stderr, "\nPlease report this bug to ravib@ravib.com.  Thanks!\n");
  return;
}

void getCommand
  (AdvGlobalContext&  gc)   // global context
//
//  Gets the player's 2 word command into gc.m_szArg1 and gc.m_szArg2.  The
//  parsed commands are stored in gc.m_nArg1 and gc.m_nArg2.
//
{
long    nIndex;       // generic index  
STRING  szResponse;   // player input

  do
  {
    // Display prompt and get response - Ctrl/Z means QUIT
    gc.m_nCmdWords = 0;
    strcpy (gc.m_szArg1, "");
    strcpy (gc.m_szArg2, "");
    strcpy (szResponse, "");
    printf ("> ");
    if (gets (szResponse) == NULL)
       {
         gc.m_nContext = null;
         gc.m_nArg1 = qquit;
         gc.m_nArg2 = null;
         printf ("\n");
         return;
       }

    // Trim whitespace - return if null input
    for (nIndex = strlen (szResponse) - 1; (nIndex >= 0); nIndex--)
        if (isspace (szResponse [nIndex]))
           szResponse [nIndex] = '\0';
        else
           break;

    // Check for null input
    bool  bNullInput = false;
    if (strlen (szResponse) == 0)
       bNullInput = true;
    else
       {
         char* pToken = strtok (szResponse, " \t");
         if (pToken == NULL)
            bNullInput = true;
         else
            {
              strcpy (gc.m_szArg1, pToken);
              gc.m_nCmdWords = 1;
            }
       }

    // Return if null input
    if (bNullInput)
       {
         gc.m_nArg1 = null;
         gc.m_nArg2 = null;
         if (gc.m_nContext != null)
            {
              gc.m_nArg1 = gc.m_nContext;
              gc.m_nContext = null;
            }
         printf ("\n");
         return;
       }

    // Extract 2nd word if any
    char *pToken = strtok (NULL, " \t");
    if (pToken != NULL)
       {
         strcpy (gc.m_szArg2, pToken);
         gc.m_nCmdWords = 2;
       }

    // Complain if more than 2 words entered
    if (gc.m_nCmdWords == 2)
       if (strtok (NULL, " \t") != NULL)
          {
            sayMessage (gc, too_many_words);
            continue;
          }

    // Identify first command word
    gc.m_nArg1 = translateVocabWord (gc, gc.m_szArg1);
    if (gc.m_nArg1 == null)
       gc.m_nArg1 = errword;
    else
       gc.m_nArg2 = null;

    // Get second command word and apply context to words 1 and 2
    if (gc.m_nCmdWords == 2)
       {
         gc.m_nContext = null;
         gc.m_nArg2 = translateVocabWord (gc, gc.m_szArg2);
         if ((gc.m_nArg2 == null) && (gc.m_nArg1 != say))
            gc.m_nArg2 = errword;
         else
            if (isObject (gc.m_nArg1))
               {
                 gc.m_nContext = gc.m_nArg2;
                 gc.m_nArg2 = gc.m_nArg1;
                 gc.m_nArg1 = gc.m_nContext;
                 gc.m_nContext = null;
               }
       }
    else
       if (gc.m_nContext != null)
          {
            if (isObject (gc.m_nContext))
               {
                 if (isVerb (gc.m_nArg1) || isUtil (gc.m_nArg1))
                    {
                      gc.m_nArg2 = gc.m_nContext;
                      gc.m_nCmdWords = 2;
                    }
               }
            else
               {
                 if (isVerb (gc.m_nContext) && (isObject (gc.m_nArg1) || isDirection (gc.m_nArg1)) ||
                     isUtil (gc.m_nContext) && isObject (gc.m_nArg1))
                    {
                      gc.m_nArg2 = gc.m_nArg1;
                      gc.m_nArg1 = gc.m_nContext;
                      gc.m_nCmdWords = 2;
                    }
               }
            gc.m_nContext = null;
          }

    // Check for profanities
    if ((gc.m_nArg1 == obscenity) || (gc.m_nArg2 == obscenity))
       {
         gc.m_nContext = null;
         gc.m_nArg1 = obscenity;
         gc.m_nArg2 = null;
       }

    printf ("\n");
    return;
  }
  forever;
}

void sayMessage
  (AdvGlobalContext&  gc,         // global context
   long               nMessage)   // message id
//
//  Displays a message.
//
{
  if (nMessage < MAXMESSAGES)
     {
       displayTextLines (gc.m_messages [nMessage]);
       return;
     }

  // Display error if message could be found
  char  szNumber [STRINGLEN];
  sprintf (szNumber, "%d", nMessage);
  displayError (ERR_SAY_MESSAGE, szNumber, NULL, NULL);
}

void sayMessageText
  (AdvGlobalContext&  gc,         // global context
   long               nMessage,   // message id
   char*              pText)      // replacement text
//
//  Displays a message, replacing every occurence of %s with the specified
//  text.
//
//  NOTE: (1) The presence of a % sign implies %s
//        (2) There can be only one %s in each line of the message
//
{
  if (nMessage < MAXMESSAGES)
     {
       displayTextLinesReplaceText (gc.m_messages [nMessage], pText);
       return;
     }

  char  szNumber [STRINGLEN];
  sprintf (szNumber, "%d", nMessage);
  displayError (ERR_SAY_MESSAGE_TEXT, szNumber, pText, NULL);
}

void sayMessageValue
  (AdvGlobalContext&  gc,         // global context
   long               nMessage,   // message id
   long               nValue)     // replacement value
//
//  Displays a message, replacing every occurence of %s with the specified
//  numeric value.
//
//  NOTE: (1) The presence of a % sign implies %s
//        (2) There can be only one %s in each line of the message
//
{
char  szValue [STRINGLEN];  // numeric string

  sprintf (szValue, "%d", nValue);
  if (nMessage < MAXMESSAGES)
     {
       displayTextLinesReplaceText (gc.m_messages [nMessage], szValue);
       return;
     }

  char  szMessage [STRINGLEN];
  sprintf (szMessage, "%d", nMessage);
  displayError (ERR_SAY_MESSAGE_VALUE, szMessage, szValue, NULL);
}

void sayMessageWord
  (AdvGlobalContext&  gc,         // global context
   long               nMessage,   // message id
   long               nWord)      // replacement vocabulary word
//
//  Displays a message, replacing every occurence of %s with the (first) textual
//  representation of the specified vocabulary word.
//
//  NOTE: (1) The presence of a % sign implies %s
//        (2) There can be only one %s in each line of the message
//
{
char  szMessage [STRINGLEN];    // numeric string
char  szWord [STRINGLEN];       // numeric string

  if (nMessage < MAXMESSAGES)
     if (nWord < MAXVOCAB)
        {
          PTR_TEXTLINES  pTextLinesVocab = gc.m_vocab [nWord];
          if (pTextLinesVocab != NULL)
             if (pTextLinesVocab->pszText != NULL)
                {
                  PTR_TEXTLINES  pTextLinesMessage = gc.m_messages [nMessage];
                  if (pTextLinesMessage != NULL)
                     {
                       displayTextLinesReplaceText (pTextLinesMessage, pTextLinesVocab->pszText);
                       return;
                     }
                }
        }

  sprintf (szMessage, "%d", nMessage);
  sprintf (szWord, "%d", nWord);
  displayError (ERR_SAY_UNKNOWN_MESSAGE_WORD, szMessage, szWord, NULL);
}

bool yes
  (AdvGlobalContext&  gc,
   long               nMessage)
//
//  Displays a message and waits for the user to enter a Yes/No response.
//  Returns true if user answers "Yes", false otherwise.
//
{
STRING  szResponse;

  do
  {
    sayMessage (gc, nMessage);
    do
      printf ("> ");
    while (gets (szResponse) == NULL);
    _strlwr (szResponse);
    if (!strcmp (szResponse, "yes") || !strcmp (szResponse, "y"))
       {
         printf ("\n");
         return (true);
       }
    else
       if (!strcmp (szResponse, "no") || !strcmp (szResponse, "n"))
          {
            printf ("\n");
            return (false);
          }
    printf ("\nPlease answer the question.\n");
  }
  while (true);
}

////////////////////
// Private functions

void displayTextLines
  (PTR_TEXTLINES pTextLines)  // text line list
//
//  Displays a collection of text lines.
//
{
  while (pTextLines != NULL)
      {
        printf ("%s\n", pTextLines->pszText);
        pTextLines = pTextLines->pNext;
      }
}

void displayTextLinesReplaceText
  (PTR_TEXTLINES  pTextLines,
   char*          pszReplacementText)
//
//  Displays a collection of text lines, replacing every occurence of %s with
//  the specified text.
//
//  NOTE: (1) The presence of a % sign implies %s
//        (2) There can be only one %s in each line of the message
//
{
  while (pTextLines != NULL)
      {
        if (strchr (pTextLines->pszText, '%') == NULL)
           printf ("%s\n", pTextLines->pszText);
        else
           {
             printf (pTextLines->pszText, pszReplacementText);
             printf ("\n");
           }
        pTextLines = pTextLines->pNext;
      }
}

long translateVocabWord
  (AdvGlobalContext&  gc,               // global context
  char*               pszVocabWord)     // vocabulary word
//
//  Attempts to translate a vocabulary word.  Returns the vocabulary
//  id or "null" if not found.
//
{
  for (long nVocabIndex=0; (nVocabIndex < MAXVOCAB); nVocabIndex++)
    {
      PTR_TEXTLINES pTextLines = gc.m_vocab [nVocabIndex];
      while (pTextLines != NULL)
            if (strcmpi (pTextLines->pszText, pszVocabWord) == 0)
               return (nVocabIndex);
            else
               pTextLines = pTextLines->pNext;
    }
  return (null);
}

// End AdvIO.cpp