/*
 *  CELLS       An Implementation of the WireWorld cellular automata
 *              as described in Scientific American, Jan 1990.
 *
 *              Copyright 1990 by Davide P. Cervone.
 *  You may use this code, provided this copyright notice is kept intact.
 *  See the CELLS.HELP file for complete information on distribution conditions.
 */

/*
 *  File:  cHelp.c                  Handles the HELP file system
 */


#include "cGadget.h"
#include "cRequest.h"
#include "cHelp.h"
#include "cReqHelp.h"
#include <stdio.h>

#define MAXERROR    8

#define ABTTEXT     ABTRequest.er_Request.ReqText
#define HLPGADGET   HLPRequest.er_Request.ReqGadget

extern int HelpActive;

static FILE *HelpFile;                          /* the HELP file handle */
static char HelpName[] = "HELP:Cells.Help";     /* the name of the file */
#define HELPNAME    &(HelpName[5])              /* HelpName without 'HELP:' */
static long LineCount;                          /* number of lines read */
static char Line[MAXBUFFER];                    /* the current line of text */
static int noError;                             /* TRUE if no errors so far */
static int EndOfFile;                           /* TRUE if at end of file */
static int CurrentLevel;                        /* help topic level */
static HELPITEM *CurrentNode;                   /* current help topic node */
static int TextNotReported;                     /* TRUE if no error for text */
static int EndHelpSeen;                         /* TRUE if ENDHELP exists */
static int ErrorCount;                          /* number of errors found */


/*
 *  Main help topic tree
 */

HELPITEM HelpRoot =
   {NULL,NULL, NULL,0, HLP_STATIC, NULL, 0, NULL,NULL,NULL, 0};


/*
 *  HelpError()
 *
 *  Set up the message string, and do the error requester
 *  Count the error, and if enough have already been reported,
 *    ask if the user wants to keep looking at the file
 *  Inform him that we are still loading the file
 */

static void HelpError(s,x1,x2,x3,x4)
char *s,*x1,*x2,*x3,*x4;
{
   char Message[80];

   sprintf(Message,s,x1,x2,x3,x4);
   sprintf(&Message[strlen(Message)]," at line %d",LineCount);
   DoError(Message);
   ErrorCount++;
   if (ErrorCount == MAXERROR)
   {
      noError = DoQuestion("%d Errors:  Continue Loading?",ErrorCount);
      ErrorCount = 0;
   }
   DoInfoMessage("Continuing to Load Help...");
}


/*
 *  FreeHelpItem()
 *
 *  If an topic was given,
 *    Free its name, if any, and its title text, if any
 *    If the topic is not a static one, free it
 */

static void FreeHelpItem(theItem)
HELPITEM *theItem;
{
   if (theItem)
   {
      if (theItem->Name && theItem->NameLen)
         FREECHAR(theItem->Name,theItem->NameLen);
      if (theItem->Title && theItem->TitleLen)
         FREECHAR(theItem->Title,theItem->TitleLen);
      if ((theItem->Flags & HLP_STATIC) == 0) FREESTRUCT(HelpItem,theItem);
   }
}


/*
 *  FreeLineList()
 *
 *  For each line in the list,
 *    if the line has text, free it
 *    if the line is not static, free it
 */

static void FreeLineList(theLine)
HELPLINE *theLine;
{
   HELPLINE *tmpLine;
   
   while (theLine)
   {
      tmpLine = theLine;
      theLine = theLine->Next;
      if (tmpLine->Text && tmpLine->TextLen)
          FREECHAR(tmpLine->Text,tmpLine->TextLen);
      if ((tmpLine->Flags & HLP_STATIC) == 0) FREESTRUCT(HelpLine,tmpLine);
   }
}


/*
 *  FreeHelpList()
 *
 *  while ther are more topics in the list,
 *    Free an subtopics of the topic
 *    Free any help lines of the topic
 *    Free the topic itself
 */

static void FreeHelpList(theItem)
HELPITEM *theItem;
{
   HELPITEM *tmpItem;

   while (theItem)
   {
      FreeHelpList(theItem->SubTopics);
      FreeLineList(theItem->Text);
      tmpItem = theItem;
      theItem = theItem->Next;
      FreeHelpItem(tmpItem);
   }
}


/*
 *  ClearHelp()
 *
 *  If the default help was changed by the file,
 *    free the (new) default help and reset the default to the standard one
 *  Free all topics in the help tree
 *  If the help file is open, close it and mare it as unopen
 */

void ClearHelp()
{
   if (DefaultHelpTopic != &DefaultHelp)
   {
      FreeHelpList(DefaultHelpTopic);
      DefaultHelpTopic = &DefaultHelp;
   }
   FreeHelpList(&HelpRoot);
   if (HelpFile) fclose(HelpFile);
   HelpFile = NULL;
}


/*
 *  CanClearText()
 *
 *  While there are more topics
 *    if the topic is not in use and its text is loaded
 *      if the topic has text
 *        free the lines of text and clear the pointer
 *        mark the topic as not having its text loaded
 *        indicate that lines have been freed
 *    if the topic has sub-topics,
 *      do the same for the sub-topics
 *    otherwise
 *      while there are no more topics in the current list, go on to the parent
 *      go on to the next topic
 */

static int CanClearText(theItem)
HELPITEM *theItem;
{
   int status = FALSE;
   
   while (theItem)
   {
      if ((theItem->Flags & (HLP_INUSE|HLP_LOADED)) == HLP_LOADED)
      {
         if (theItem->Text)
         {
            FreeLineList(theItem->Text);
            theItem->Text = NULL;
            theItem->Flags &= ~HLP_LOADED;
            status = TRUE;
         }
      }
      if (theItem->SubTopics)
      {
         theItem = theItem->SubTopics;
      } else {
         while (theItem->Next == NULL && theItem->Parent)
            theItem = theItem->Parent;
         theItem = theItem->Next;
      }
   }
   return(status);
}


/*
 *  CanClearHelpLines()
 *
 *  Try to clear lines of text from the main help tree and the default help
 *  tree, and return TRUE if memory was freed.
 */

int CanClearHelpLines()
{
   int status = FALSE;
   
   status = CanClearText(&HelpRoot);
   if (DefaultHelpTopic != &DefaultHelp)
      status |= CanClearText(DefaultHelpTopic);
   return(status);
}


/*
 *  CanClearHelpTopics()
 *
 *  If the help system is not active,
 *    if there are lines of text in the miain list, or topics in the main list
 *        or the main list has a title,
 *      Clear all the help topics, and close the file
 *      Set the pointers to NULL.
 *      indicate that the clear was successful
 */

int CanClearHelpTopics()
{
   int status = FALSE;

   if (!HelpActive)
   {
      if (HelpRoot.Text || HelpRoot.SubTopics || HelpRoot.Title)
      {
         ClearHelp();
         HelpRoot.Text = NULL;
         HelpRoot.SubTopics = NULL;
         HelpRoot.Title = NULL;
         status = TRUE;
      }
   }
   return(status);
}


/*
 *  NewHelpItem()
 *
 *  Get a new help topic structure; if successful
 *    If there is a name for the topic,
 *      try to allocate space for the name
 *      if successful, copy the name, otherwise free the help item
 *    otherwise clera the name area
 *    If the help item was allocated OK,
 *      determine its file position and parent
 *      mark the fact that text errors have not be reported for this item
 */

static HELPITEM *NewHelpItem(s)
char *s;
{
   HELPITEM *theItem;
   
   if (NEWSTRUCT(HelpItem,theItem))
   {
      if (s)
      {
         theItem->NameLen = strlen(s);
         if (NEWCHAR(theItem->Name,theItem->NameLen))
         {
            strcpy(theItem->Name,s);
         } else {
            FREESTRUCT(HelpItem,theItem);
            theItem = NULL;
         }
      } else {
         theItem->NameLen = 0;
         theItem->Name = NULL;
      }
      if (theItem)
      {
         theItem->FilePos = ftell(HelpFile);
         theItem->Parent = CurrentNode;
         TextNotReported = TRUE;
      }
   }
   return(theItem);
}


/*
 *  NewHelpLine()
 *
 *  Allocate a new help line structure; if successful,
 *    get space for the line of text (remove the trailing new-line), and
 *    copy the line of text.
 */

static HELPLINE *NewHelpLine(s)
char *s;
{
   HELPLINE *theLine;
   short len;
   
   if (NEWSTRUCT(HelpLine,theLine))
   {
      len = strlen(s);
      if (len > 0 && s[len-1] == '\n') s[--len] = 0;
      if (NEWCHAR(theLine->Text,len))
      {
         strcpy(theLine->Text,s);
         theLine->TextLen = len;
      } else {
         FREESTRUCT(HelpLine,theLine);
         theLine = NULL;
      }
   }
   return(theLine);
}


/*
 *  ReadHelpLine()
 *
 *  If there have been no file errors,
 *    If we can read the next line from the file
 *      increment the line count
 *    Otherwise
 *      if we're at the end of the file, indicate that
 *      otherwise report the error that occured
 */

static void ReadHelpLine()
{
   if (noError)
   {
      if (fgets(Line,MAXBUFFER,HelpFile))
      {
         LineCount++;
      } else {
         if (feof(HelpFile))
         {
            EndOfFile = TRUE;
         } else {
            DosError("Read","Help Line");
            noError = FALSE;
         }
      }
   } else {
      EndOfFile = TRUE;
   }
}


/*
 *  OpenHelp()
 *
 *  Go back to the initial directory for a moment and try to open the
 *  help file there.  If unsuccessful, try to open the file in HELP:
 *
 *  If the file was found,
 *    clear the line count, and other flags, and start at the top of the tree
 *    read the first line of the file
 *  Otherwise report the trouble openning the file
 */

static int OpenHelp()
{
   SwapLocks(); HelpFile = fopen(HELPNAME,"r"); SwapLocks();
   if (HelpFile == NULL) HelpFile = fopen(HelpName,"r");

   if (HelpFile)
   {
      LineCount = 0;
      EndOfFile = FALSE; EndHelpSeen = FALSE;
      noError = TRUE; TextNotReported = TRUE; ErrorCount = 0;
      CurrentLevel = 0;
      CurrentNode = &HelpRoot;
      ReadHelpLine();
   } else {
      DosError("Open",HelpName);
   }
   return(HelpFile != NULL);
}


/*
 *  The valid help file commands
 */

static char *HelpCommand[] = {"Title:","Link:","Help:","EndHelp:"};
#define HT_UNKNOWN  0
#define HT_LEVEL    1
#define HT_TITLE    2
#define HT_LINK     3
#define HT_HELP     4
#define HT_END      5

#define HT_COUNT    4


/*
 *  ParseHelp()
 *
 *  Find the end of the first word on the line, and separate it
 *  Clear the trailing new-line character, if any
 *  If the first character is a digit
 *    calculate the level that is specified
 *    if there are non-digits following the level number,
 *      set up for an error
 *    otherwise
 *      indicate that a valid level was provided
 *      move the string pointer on to the beginning of the topic name
 *  Otherwise (not a digit)
 *    see if the first word on the line matches one of the commands
 *    if so, indicate which one, and set the string pointer to the beginning
 *    of parameterfor the command
 */

static int ParseHelp(Level,s)
int *Level;
char **s;
{
   int HelpType = HT_UNKNOWN;
   short i;
   char *s1;

   *s = s1 = &Line[1];
   while (*s1 > ' ') s1++;
   *s1++ = 0;
   while (*s1 && *s1 != '\n') s1++;
   *s1++ = 0; *s1 = 0;

   if (**s >= '0' && **s <= '9')
   {
      *Level = 0;
      while (**s >= '0' && **s <= '9')
      {
         *Level = (*Level) * 10 + (**s) - '0';
         (*s)++;
      }
      if (**s)
      {
         *s = &Line[1];
      } else {
         HelpType = HT_LEVEL;
         (*s)++;
      }
   } else {
      for (i=0; i<HT_COUNT && HelpType == HT_UNKNOWN; i++)
      {
         if (stricmp(HelpCommand[i],*s) == 0)
         {
            HelpType = i + HT_TITLE;
            *s += strlen(*s) + 1;
         }
      }
   }
   return(HelpType);
}


/*
 *  DoHelpLine()
 *
 *  Find the type of command that was given and do the right thing:
 *
 *  LEVEL:
 *    if the specified level is not the next lower level or is too small,
 *      give an error message
 *    otherwise
 *      find the help node that is the parent of the required topic level
 *      get a new topic structure; if successful
 *        link it into the topic list of the parent topic
 *        set the current node and level
 *      otherwise give an error
 *
 *  TITLE:
 *    if the current topic is linked give an error
 *    otherwise if the current topic already has a title, give an error
 *    otherwise
 *       if a title was supplied as a parameter,
 *         make a copy of the title, if possible (error if not)
 *       otherwise report the missing title parameter
 *
 *  LINK:
 *    if the topic already has a link, report an error
 *    otherwise, if the topic already has text lines, give an error
 *    otherwise
 *      if the topic has a title, warn that it will be ignored, and free it.
 *      copy the link string to the title area, if possible.
 *
 *  HELP:
 *    if the default help has not already been changed,
 *      get a new help topic; if successful
 *        set up the new item as the new default help root
 *      otherwise report an error
 *    otherwise report that HELP has already appeared in the file
 *
 *  END:
 *    check for proper use of ENDHELP, and if OK, go back to
 *    using the main help tree at the top level.
 */

static void DoHelpLine()
{
   int Level;
   char *s;
   HELPITEM *theItem;

   switch(ParseHelp(&Level,&s))
   {
      case HT_LEVEL:
         if (Level > CurrentLevel+1 || Level < 1)
         {
            HelpError("Invalid Help Topic Level '%d'",Level);
         } else {
            while (Level <= CurrentLevel && CurrentNode->Parent)
            {
               CurrentNode = CurrentNode->Parent;
               CurrentLevel--;
            }
            theItem = NewHelpItem(s);
            if (theItem)
            {
               theItem->Prev = NULL;
               theItem->Next = CurrentNode->SubTopics;
               if (theItem->Next) theItem->Next->Prev = theItem;
               CurrentNode->SubTopics = theItem;
               CurrentNode = theItem;
               CurrentLevel++;
            } else {
               HelpError("Can't Get Memory for Help Topic");
            }
         }
         break;

      case HT_TITLE:
         if (CurrentNode->Flags & HLP_LINKED)
         {
            HelpError("LINKed Topics can not have TITLES");
         } else if (CurrentNode->Title) {
            HelpError("Topic already has a TITLE");
         } else {
            CurrentNode->TitleLen = strlen(s);
            if (CurrentNode->TitleLen)
            {
               if (NEWCHAR(CurrentNode->Title,CurrentNode->TitleLen))
                  strcpy(CurrentNode->Title,s);
                 else
                  HelpError("Can't Get Memory for Topic Title");
            } else {
               HelpError("TITLE Text String is Blank");
            }
         }
         break;

      case HT_LINK:
         if (CurrentNode->Flags & HLP_LINKED)
         {
            HelpError("Topic Already has a LINK");
         } else if (CurrentNode->Flags & HLP_HASTEXT) {
            HelpError("Topics with Text can not be LINKed");
         } else {
            if (CurrentNode->Title)
            {
               HelpError("LINK will cause TITLE to be Ignored");
               FREECHAR(CurrentNode->Title,CurrentNode->TitleLen);
            }
            CurrentNode->TitleLen = strlen(s);
            if (CurrentNode->TitleLen)
            {
               if (NEWCHAR(CurrentNode->Title,CurrentNode->TitleLen))
               {
                  strcpy(CurrentNode->Title,s);
                  CurrentNode->Flags |= HLP_LINKED;
               } else {
                  HelpError("Can't Get Memory for LINK");
               }
            } else {
               HelpError("LINK String is Blank");
            }
         }
         break;

      case HT_HELP:
         if (DefaultHelpTopic == &DefaultHelp)
         {
            theItem = NewHelpItem(NULL);
            if (theItem)
            {
               theItem->Next = theItem->Prev = NULL;
               theItem->Parent = NULL;
               CurrentNode->SubTopics = NULL;
               CurrentNode = theItem;
               CurrentLevel = 0;
               DefaultHelpTopic = theItem;
            } else {
               HelpError("Can't Get Memory for Default Help Topic");
            }
         } else {
            HelpError("Duplicate HELP Command Ignored");
         }
         break;

      case HT_END:
         if (DefaultHelpTopic == &DefaultHelp)
         {
            HelpError("ENDHELP Before HELP");
         } else if (EndHelpSeen) {
            HelpError("Duplicate ENDHELP Command Ignored");
         } else {
            CurrentNode = &HelpRoot;
            CurrentLevel = 0;
            EndHelpSeen = TRUE;
         }
         break;

      case HT_UNKNOWN:
         HelpError("Unknown HELP Command '%s'",s);
         break;
   }
}


/*
 *  LoadHelp()
 *
 *  If the help file is already open, return OK
 *  otherwise
 *    put up the info message
 *    and try to open the help file; if OK
 *      while there are more lines in the file,
 *        if the line starts with the command symbol
 *          do the command it specifies
 *        otherwise
 *          if the current topic is linked report the error
 *          otherwise mark the topic as having text
 *        read the next line frlom the file
 *      if an error occured, indicate that the file was not all read
 *    clear the info message
 */

int LoadHelp()
{
   int status = FALSE;
   
   if (HelpFile)
   {
      status = TRUE;
   } else {
      DoInfoMessage("Loading Help Topics");
      if (OpenHelp())
      {
         while (!EndOfFile)
         {
            if (Line[0] == '*' && Line[1] != '*')
            {
               DoHelpLine();
            } else if (Line[0] != '!') {
               if ((CurrentNode->Flags & HLP_LINKED) && TextNotReported)
               {
                  HelpError("LINKed Topics can not have Text");
                  TextNotReported = FALSE;
               } else {
                  CurrentNode->Flags |= HLP_HASTEXT;
               }
            }
            ReadHelpLine();
         }
         if (noError = FALSE) DoError("Help Topics Not Completely Loaded");
         status = TRUE;
      }
      ClearInfoMessage();
   }
   return(status);
}


/*
 *  LoadItem()
 *
 *  If the specified topic is not already loaded and it has text to be loaded
 *    find the start of the topic in the help file,
 *    if that was not possible, give an error
 *    otherwise
 *      while we're not done with the topic,
 *        read the next line of text
 *        if we reached the end of file, we're done,
 *        otherwise
 *          if the line is not a command or a comment
 *            skip duplicate '*' ('**' means '*' as first character of line)
 *            get a new text line structure
 *            if allocated OK,
 *              link the text line into the topics text list
 *            otherwise give an error and quit
 *          otherwise if the line is a command
 *            check whether it is one that ends the help topic
 *      mark the topic as having its text loaded
 *      let the user know if it was not completely loaded
 */

void LoadItem(theItem)
HELPITEM *theItem;
{
   int NotDone = TRUE;
   int Level;
   char *s;
   HELPLINE *theLine;

   if ((theItem->Flags & (HLP_LOADED|HLP_HASTEXT)) == HLP_HASTEXT)
   {
      if (fseek(HelpFile,theItem->FilePos,0) == -1)
      {
         DosError("Find","Help Text");
      } else {
         noError = TRUE;
         EndOfFile = FALSE;
         EndHelpSeen = FALSE;
         while (NotDone)
         {
            ReadHelpLine(); s = &Line[0];
            if (EndOfFile)
            {
               NotDone = FALSE;
            } else {
               if ((Line[0] != '*' || Line[1] == '*') && Line[0] != '!')
               {
                  if (Line[0] == '*') s++;
                  theLine = NewHelpLine(s);
                  if (theLine)
                  {
                     theLine->Prev = NULL;
                     theLine->Next = theItem->Text;
                     if (theLine->Next) theLine->Next->Prev = theLine;
                     theItem->Text = theLine;
                  } else {
                     DoError("Can't Get Memory for Help Text");
                     NotDone = FALSE; noError = FALSE;
                  }
               } else if (Line[0] == '*') {
                  switch(ParseHelp(&Level,&s))
                  {
                     case HT_LEVEL:
                     case HT_HELP:
                     case HT_END:
                        NotDone = FALSE;
                        break;
                  }
               }
            }
         }
         theItem->Flags |= HLP_LOADED;
         if (noError == FALSE) DoError("Text Not Completely Loaded");
      }
   }
}

