/*
 *  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:  cDoHelp.c            Handles the Help facility
 */


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

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


extern char HlpNameBuf[];                   /* Help name gadget buffer */
int HelpActive;                             /* TRUE if help requester is up */
HELPITEM *DefaultHelpTopic = &DefaultHelp;  /* HELP button on HELP requester */

static HELPITEM *CurrentRoot;       /* root of current Help tree */
static HELPITEM *CurrentNode;       /* current topic in Help tree */
static HELPITEM *OldNode;           /* topic in use before HELP was pressed */
static int HelpOnHelp;              /* TRUE if HELP pressed during Help */
static int HelpMode;                /* Text or Topic mode */
   #define HM_TEXT      0
   #define HM_TOPICS    1

/*
 *  return values from keyword match test
 */

#define NOMATCH     0
#define MATCHED     1
#define MORETOPIC   2


/*
 *  PrefixMatch()
 *
 *  Compare two keyword strings:
 *  find the first character that does not match (or the end of the string)
 *  If we matched all of string 1, return MATCHED,
 *  Otherwise, if we matched at least one character,
 *    if the last matched character was a space, go back to that space
 *    otherwise, if the unmatched character was a space, skip to the
 *      first non-space.
 *    if we matched up to a space,
 *      set the return string to the unmatched character,
 *      if there are more un-matched characters, return MORETOPIC
 *      otherwise return completely matched (MATCHED)
 */

static int PrefixMatch(Src,Target)
char **Src,*Target;
{
   char c1,c2;
   char *s = *Src;
   int status = NOMATCH;
   
   c1 = toupper(*s); c2 = toupper(*Target);
   while (c1 && c1 == c2)
   {
      s++; Target++;
      c1 = toupper(*s); c2 = toupper(*Target);
   }
   if (c1 == 0)
   {
      status = MATCHED;
      *Src = s;
   } else if (s > *Src) {
      if (*s != ' ' && *(s-1) == ' ') s--;
      else if (*s == ' ') while (*(s+1) == ' ') s++;
      if (*s == ' ')
      {
         s++; *Src = s;
         if (*s) status = MORETOPIC; else status = MATCHED;
      }
   }
   return(status);
}


/*
 *  FindHelp()
 *
 *  Starting at the bottom of the item list containing the specified item,
 *  while there are more items and we have not found the one we want,
 *    match the rest of the name agains the test topic
 *    If an exact match, we're done.
 *    if there is more to the topic,
 *      save the topic (if one was not already saved, or this one matches more)
 *      then go on to the "next" topic
 *    If no match (or more topic)
 *      go to the next item (the previous one in the list)
 *      if we're at the end of the list and there was a partial match
 *        starting with the rest of the topic name
 *        and looking at the subtopics of the matched topic
 *          (at the end of the list)
 *        keep looking for more matches
 *  If not found go back to the last topic matched.
 *  If there's still more topics unmatched, give a warning
 *  return the last matched item.
 */

static HELPITEM *FindHelp(theItem,Name)
HELPITEM *theItem;
char *Name;
{
   HELPITEM *Parent = theItem;
   HELPITEM *LastMatch = NULL;
   char *RestOfTopics = NULL;
   char *Remainder;
   int NotFound = TRUE;

   theItem = Parent->SubTopics;
   if (theItem) while (theItem->Next) theItem = theItem->Next;
   while (theItem && NotFound)
   {
      Remainder = Name;
      switch(PrefixMatch(&Remainder,theItem->Name))
      {
         case MATCHED:
            NotFound = FALSE;
            Name = Remainder;
            break;

         case MORETOPIC:
            if (LastMatch == NULL || Remainder > RestOfTopics)
            {
               LastMatch = theItem;
               RestOfTopics = Remainder;
            }
         case NOMATCH:
            theItem = theItem->Prev;
            if (theItem == NULL && LastMatch)
            {
               Name = RestOfTopics; RestOfTopics = NULL;
               Parent = LastMatch; LastMatch = NULL;
               theItem = Parent->SubTopics;
               if (theItem) while (theItem->Next) theItem = theItem->Next;
            }
            break;
      }
   }
   if (NotFound) theItem = Parent;
   if (Name && Name[0]) DoError("Can't Find Topic '%s'",Name);
   return(theItem);
}


/*
 * SetHelpPen()
 *
 *  Set the pen color of the IntuiTexts for the Help list gadget
 *  (depending on whether we're in Text Mode or Topic Mode).
 */

static void SetHelpPen(thePen)
int thePen;
{
   int i;

   for (i=0; i<HlpListInfo.Height; HlpListInfo.IText[i++].FrontPen = thePen);
}


/*
 *  SetHelpText()
 *
 *  If the topic's text lines are not loaded, load them.
 *  If there are text lines,
 *    Unset the previous nodes INUSE flag,
 *    Set the current node to this one, and set its INUSE flag
 *    Set the requester buttons and titles properly
 *    and if the Help requester is already up, unselect any items
 *    set the IText pens, and set the list to showing the text lines
 *    If the requester is already active,
 *      clear the name string,
 *      refresh the name, title and topics gadgets,
 *      (refresh the topic gadget twice if it is selected, since it inverts)
 *    set TEXT mode
 *  Otherwise
 *    give a warning about no text items
 */

static void SetHelpText(theItem)
HELPITEM *theItem;
{
   if ((theItem->Flags & HLP_LOADED) == 0) LoadItem(theItem);
   if (theItem->Text)
   {
      CurrentNode->Flags &= ~HLP_INUSE;
      CurrentNode = theItem; CurrentNode->Flags |= HLP_INUSE;
      HLPGADGET[HLP_TOPICS].GadgetText->IText = "Topics";
      HLPGADGET[HLP_TOPICS].GadgetID = HLP_TOPICS;
      if (theItem->Title)
         HLPGADGET[HLP_TITLE].GadgetText->IText = theItem->Title;
        else
         HLPGADGET[HLP_TITLE].GadgetText->IText = theItem->Name;
      HLPGADGET[HLP_TITLE].GadgetText->LeftEdge = (HLP_TITLEW -
         strlen(HLPGADGET[HLP_TITLE].GadgetText->IText)*CHAR_WIDTH) / 2;
      if (ActiveRequest == &HLPRequest) UnsetListSelect(&HlpListInfo,TRUE);
      SetHelpPen(TEXTPEN);
      SetList(&(HLPGADGET[HLP_LIST]),theItem->Text,NULL);
      if (ActiveRequest == &HLPRequest)
      {
         SetStringInfo(&HLPGADGET[HLP_NAME],"");
         RefreshRequest(&HLPRequest,HLP_NAME,NULL);
         RefreshRequest(&HLPRequest,HLP_TITLE,NULL);
         RefreshRequest(&HLPRequest,HLP_TOPICS,NULL);
         if (HLPGADGET[HLP_TOPICS].Flags & SELECTED)
            RefreshRequest(&HLPRequest,HLP_TOPICS,NULL);
      }
      HelpMode = HM_TEXT;
   } else {
      DoError("No Text for this Help Topic");
   }
}

/*
 *  SetHelpTopics()
 *
 *  If the item has no sub-topics, go to the parent for a topic list
 *  If there are sub-topics
 *    Clear the INUSE flag of the previous node,
 *    Set the current node and set its INUSE flag
 *    Set the gadget names, title, etc.
 *    If the Help requester is already active, unselect any items
 *    Set the IText pens
 *    Set the gadget list to the list of topics
 *    If the help request is active
 *      clear the name gadget,
 *      refresh the Name, Title, and Topic gadgets
 *      (refresh the topic gadget twice if it is selected, since it inverts)
 *    set the mode to TOPICS
 *  Otherwise
 *    display an error message
 */

static void SetHelpTopics(theItem,SelectedItem)
HELPITEM *theItem;
HELPITEM *SelectedItem;
{
   while (theItem->SubTopics == NULL && theItem->Parent)
      theItem = theItem->Parent;
   if (theItem->SubTopics)
   {
      CurrentNode->Flags &= ~HLP_INUSE;
      CurrentNode = theItem; CurrentNode->Flags |= HLP_INUSE;
      HLPGADGET[HLP_TOPICS].GadgetText->IText = " Text ";
      HLPGADGET[HLP_TOPICS].GadgetID = HLP_TEXT;
      HLPGADGET[HLP_TITLE].GadgetText->IText = "Double-Click to Pick a Topic:";
      HLPGADGET[HLP_TITLE].GadgetText->LeftEdge = (HLP_TITLEW-30*CHAR_WIDTH)/2;
      if (ActiveRequest == &HLPRequest) UnsetListSelect(&HlpListInfo,TRUE);
      SetHelpPen(TOPICPEN);
      SetList(&(HLPGADGET[HLP_LIST]),theItem->SubTopics,SelectedItem);
      if (ActiveRequest == &HLPRequest)
      {
         SetStringInfo(&HLPGADGET[HLP_NAME],"");
         RefreshRequest(&HLPRequest,HLP_NAME,NULL);
         RefreshRequest(&HLPRequest,HLP_TITLE,NULL);
         RefreshRequest(&HLPRequest,HLP_TOPICS,NULL);
         if (HLPGADGET[HLP_TOPICS].Flags & SELECTED)
            RefreshRequest(&HLPRequest,HLP_TOPICS,NULL);
      }
      HelpMode = HM_TOPICS;
   } else {
      DoError("No Sub-Topics Available for this Topic");
   }
}


/*
 *  SetHelp()
 *
 *  If an item was specified,
 *    if the item is a link to another topic,
 *      find the linked item in the current tree,
 *      if it can't be found, display an error and go back to the current node
 *    Load the topics text if necessary
 *    if there is text for this topic, set TEXT mode else set TOPIC mode
 *  Otherwise (no topic)
 *    Clear the title and the item list
 */

static void SetHelp(theItem)
HELPITEM *theItem;
{
   if (theItem)
   {
      if (theItem->Flags & HLP_LINKED)
      {
         theItem = FindHelp(CurrentRoot,theItem->Title);
         if (theItem == NULL)
         {
            theItem = CurrentNode;
            DoError("Target of LINK not Found");
         }
      }
      if ((theItem->Flags & HLP_LOADED) == 0) LoadItem(theItem);
      if (theItem->Text)
         SetHelpText(theItem);
        else
         SetHelpTopics(theItem,CurrentNode);
   } else {
      HLPGADGET[HLP_TITLE].GadgetText->IText = NULL;
      if (ActiveRequest == &HLPRequest)
         RefreshRequest(&HLPRequest,HLP_TITLE,NULL);
      SetList(&(HLPGADGET[HLP_LIST]),NULL,NULL);
   }
}


/*
 *  DoParent()
 *
 *  if there is a parent to the current item
 *    set the parent's help text or topics, as needed
 *  otherwise display an error message
 */

static void DoParent()
{
   if (CurrentNode->Parent)
   {
      if (HelpMode == HM_TEXT)
         SetHelpText(CurrentNode->Parent);
        else
         SetHelpTopics(CurrentNode->Parent,CurrentNode);
   } else {
      DoError("There is No Parent for this Help Item");
   }
}


/*
 *  HLPGadgetUp()
 *
 *  Do the right thing for each gadget up message:
 *
 *    DONE:
 *      Clear the INUSE flag, clear the HelpActive flag, and remove
 *      the requester.  Inverse the HELP button and reset the pointer color.
 *
 *    PARENT:
 *      if SHIFT pressed, go to the help root, otherwise go to the parent.
 *      activate the help name gadget.
 *
 *    TOPICS:
 *      Set to the topic list of the current node.
 *
 *    TEXT:
 *      Set to the help text of the current node.
 *
 *    MORE:
 *      If there are more items not shown below,
 *        if we're in text mode,
 *          set the selected item to the last one on the previous page
 *          (so that if there's not enough to fill an entire new page,
 *          we can still see where the last page ended - the last line will
 *          flash selected for a moment)
 *        scroll down by a page
 *      Otherwise (at the bottom of the list)
 *        if in text mode, show topics, otherwise go to the parent
 *        if text mode, delay a little, and activate the name gadget
 *          this will deselect the last line of text selected above.
 *
 *    NAME:
 *      if topics were selected (or typed)
 *        find the selected item and display it
 *      otherwise (name gadget it blank)
 *        go to the parent
 *      reactivate the name gadget.
 *
 *    SLIDER:
 *      stop sliding the list.
 */

static void HLPGadgetUp(theGadget,theMessage)
struct Gadget *theGadget;
struct IntuiMessage *theMessage;
{
   HELPITEM *theItem;

   switch(theGadget->GadgetID)
   {
      case HLP_DONE:
         CurrentNode->Flags &= ~HLP_INUSE;
         HelpActive = FALSE;
         RemoveRequest(&HLPRequest);
         InvertGadget(&cGadget[ID_HELP]);
         if (MouseMoveType == MM_NONE && !Running)
            SetPointerColor(PenInUse);
         break;

      case HLP_PARENT:
         if (theMessage->Qualifier & SHIFTKEYS)
            SetHelp(CurrentRoot);
           else
            DoParent();
         if (HelpMode == HM_TEXT) ActivateName(&HlpListInfo);
         break;

      case HLP_TOPICS:
         SetHelpTopics(CurrentNode,CurrentNode);
         break;

      case HLP_TEXT:
         SetHelpText(CurrentNode);
         ActivateName(&HlpListInfo);
         break;

      case HLP_MORE:
         if (HlpListInfo.TopItem + HlpListInfo.Height < HlpListInfo.ListLen)
         {
            if (HelpMode == HM_TEXT)
               HlpListInfo.SelectedItem = HlpListInfo.Height-1;
            DoListPageDown(&HlpListInfo);
         } else {
            if (HelpMode == HM_TEXT)
               SetHelpTopics(CurrentNode,CurrentNode);
              else
               DoParent();
         }
         if (HelpMode == HM_TEXT)
         {
            Delay(6L);
            ActivateName(&HlpListInfo);
         }
         break;

      case HLP_NAME:
         if (HlpNameBuf[0])
         {
            theItem = FindHelp(CurrentNode,HlpNameBuf);
            if (theItem) SetHelp(theItem);
         } else {
            DoParent();
         }
         ActivateName(&HlpListInfo);
         break;

      case HLP_SLIDE:
         EndListSlider(theGadget->UserData);
         break;
   }
}


/*
 * HLPGadgetDown()
 *
 *  Do the right thing for each gadget down event:
 *
 *    LIST:
 *      If the mode is TOPICS,
 *        Find the item selected and set to it,
 *      Otherwise activate the name gadget
 *
 *    HELP:
 *      if already showing help on help,
 *        restore the CurrentRoot to the root of the Help tree
 *        set the node to the Old Node,
 *        clear the HelpOnHelp flag
 *      otherwise
 *        save the current node for later
 *        set the root to the default help
 *        display the root of the default help
 *        set the HelpOnHelp flag
 *
 *    Arrows:
 *      Scroll the list.
 *
 *    SLIDER:
 *      start sliding the list.
 *
 *    NAME:
 *      Unselect any selected topics.
 */

void HLPGadgetDown(theGadget,theMessage)
struct Gadget *theGadget;
struct IntuiMessage *theMessage;
{
   HELPITEM *theItem;

   switch(theGadget->GadgetID)
   {
      case HLP_LIST:
         if (HelpMode == HM_TOPICS)
         {
            theItem = (HELPITEM *)DoListHit(theGadget->UserData,theMessage);
            if (theItem) SetHelp(theItem);
         } else {
            ActivateName(&HlpListInfo);
         }
         break;

      case HLP_HELP:
         if (HelpOnHelp)
         {
            CurrentRoot = &HelpRoot;
            SetHelp(OldNode);
            HelpOnHelp = FALSE;
         } else {
            OldNode = CurrentNode;
            CurrentRoot = DefaultHelpTopic;
            SetHelp(CurrentRoot);
            HelpOnHelp = TRUE;
         }
         break;

      case HLP_UARROW:
         DoListScrollUp(theGadget->UserData,theGadget,theMessage);
         break;

      case HLP_DARROW:
         DoListScrollDown(theGadget->UserData,theGadget,theMessage);
         break;

      case HLP_SLIDE:
         StartListSlider(theGadget->UserData);
         break;

      case HLP_NAME:
         UnsetListSelect(theGadget->UserData,TRUE);
         break;
   }
}


/*
 *  DoHelp()
 *
 *  If the help requester is not already showing,
 *    invert the HELP button and restore the pointer colors
 *    Set the HelpActive flag
 *    If we can (or already have) loaded the help tree
 *      set the current root and node, and display to root item
 *      make sure the HELP button is not selected, clear the name gadget,
 *      and then activate the requester
 *    If not loaded (or the requester is not activated)
 *      clean up.
 */

void DoHelp()
{
   extern void DoListMouse();
   int LoadedOK = FALSE;

   if (ActiveRequest != &HLPRequest)
   {
      InvertGadget(&cGadget[ID_HELP]);
      RestorePointerColor();
      HelpActive = TRUE;
      if (LoadHelp())
      { 
         CurrentRoot = CurrentNode = &HelpRoot; SetHelp(CurrentNode);
         HLPGADGET[HLP_HELP].Flags &= ~SELECTED; HelpOnHelp = FALSE;
         SetStringInfo(&(HLPGADGET[HLP_NAME]),"");
         AddRequest(&HLPRequest,HLPGadgetDown,HLPGadgetUp,DoListMouse);
         if (ActiveRequest) LoadedOK = TRUE;
      }
      if (LoadedOK == FALSE)
      {
         InvertGadget(&cGadget[ID_HELP]);
         if (MouseMoveType == MM_NONE && !Running)
            SetPointerColor(PenInUse);
         HelpActive = FALSE;
      }
   }
}


/*
 *  ABTGadgetUp()
 *
 *  If the OK button is pressed,
 *    remove the requester and clearn up
 */

static void ABTGadgetUp(theGadget,theMessage)
struct Gadget *theGadget;
struct IntuiMEssage *theMessage;
{
   if (theGadget->GadgetID == ABT_OK)
   {
      RemoveRequest(&ABTRequest);
      InvertGadget(&cGadget[ID_HELP]);
      if (MouseMoveType == MM_NONE && !Running)
         SetPointerColor(PenInUse);
   }
}


/*
 *  DoAbout()
 *
 *  If the about box is not already active,
 *    Invert the ABOUT gadget and restore the pointer.
 *    set the title and the free memory counter.
 *    open the requester
 *    if it could not be activated, clean up
 */

void DoAbout()
{
   extern long AvailMem();

   if (ActiveRequest != &ABTRequest)
   {
      InvertGadget(&cGadget[ID_HELP]);
      RestorePointerColor();
      sprintf(ABTTEXT[ABTT_TITLE].IText,"About%s",myWindow->ScreenTitle);
      sprintf(ABTTEXT[ABTT_FREE].IText,"Free Memory:  %dK",AvailMem(0)/1024);
      AddRequest(&ABTRequest,NULL,ABTGadgetUp,NULL);
      if (ActiveRequest == NULL)
      {
         InvertGadget(&cGadget[ID_HELP]);
         if (MouseMoveType == MM_NONE && !Running)
            SetPointerColor(PenInUse);
      }
   }
}
