/*
 *  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:  cError.c         Handles error and info requesters.
 *                          Also handles safe memory allocation.
 */


#include "Cells.h"
#include "cError.h"
#include "cBoxes.h"
#include <libraries/dosextens.h>

extern struct IntuiMessage *GetMsg();
extern int DelayedShift;

#define MEM_DANGER      (16*1024)           /* when to really get mad */
#define MEM_WARNING     (32*1024)           /* when to warn about low memory */
#define MEM_OK          (MEM_WARNING+1)     /* memory OK over this amount */
static int ErrorLevel = MEM_OK;             /* current memory warning level */

static char *SavedTitle;                    /* saved screen title */
static int InfoReqActive;                   /* TRUE if INFO message up */
static APTR OldPrWindow;                    /* old process window pointer */
static struct Process *ourProcess;          /* the CELLS process */


/*
 *  The strucutres for the Error, Info, and Question requesters
 */

static short PanelBox[]  = FBOX(ER_PANELW,ER_PANELH);
static short FrameBox[]  = FBOX(ER_FRAMEW,ER_FRAMEH);
static short ButtonBox[] = FBOX(ER_BUTTONW,ER_BUTTONH);

static struct Border ERBorder[] =
{
      #define NEXTBORDER(n)     &ERBorder[n+2]
      #define BORDERNAME        ERBorder
   OUTBRDR(ERB_PANEL, 0,0, PanelBox),
      #undef  NEXTBORDER
      #define NEXTBORDER(n)     NULL
   INBRDR (ERB_FRAME, ER_FRAMEX,ER_FRAMEY, FrameBox),
   OUTBRDR(ERB_BUTTON, 0,0, ButtonBox)
};

static struct IntuiText ERText[] =
{
   ITEXT("OK", 7,3),
   {ERRORPEN,FOREGROUND,JAM2, ER_TITLEX,ER_TITLEY,
      NULL, NULL, &ERText[ERT_MESS1]},
   ITEXT(NULL, 0,ER_MESS1Y),
   ITEXT(NULL, 0,ER_MESS2Y),
   ITEXT("Yes", 3,3),
   ITEXT("No",  7,3),
};

static struct Gadget ERGadget[] =
{
   {NULL, ER_BUTTONX,ER_BUTTONY, ER_BUTTONW,ER_BUTTONH, GADGHCOMP,
      RELVERIFY, BOOLGADGET|REQGADGET, (APTR)&ERBorder[ERB_BUTTON],
      NULL, &ERText[ERT_OK], 0, NULL, ER_OK, NULL},
   {&ERGadget[2], ER_BUTTONX,ER_BUTTONY, ER_BUTTONW,ER_BUTTONH, GADGHCOMP,
      RELVERIFY, BOOLGADGET|REQGADGET, (APTR)&ERBorder[ERB_BUTTON],
      NULL, &ERText[ERT_YES], 0, NULL, ER_YES, NULL},
   {NULL, ER_BUTTONX-ER_BUTTONW,ER_BUTTONY, ER_BUTTONW,ER_BUTTONH, GADGHCOMP,
      RELVERIFY, BOOLGADGET|REQGADGET, (APTR)&ERBorder[ERB_BUTTON],
      NULL, &ERText[ERT_NO], 0, NULL, ER_NO, NULL},
};

static struct Requester ERRequest =
{
   NULL, (BOARDW-ER_PANELW)/2,(BOARDH-ER_PANELH)/2, ER_PANELW,ER_PANELH,
   0,0, &ERGadget[0], &ERBorder[ERB_PANEL], &ERText[ERT_TITLE], 0, FOREGROUND,
   NULL, NULL, NULL
};


/*
 *  SetErrorText()
 *
 *  Set the requesters title and message strings.
 *  Break a long message string into two parts (at a space), center the
 *    two parts horizontally and make them look good vertically.
 *  Center single line message properly
 */

static void SetErrorText(Title,Message)
char *Title;
char *Message;
{
   register short i;
   short Len = strlen(Message);

   ERText[ERT_TITLE].IText = Title;
   ERText[ERT_MESS1].IText = Message;
   if (Len > 30)
   {
      ERText[ERT_MESS1].NextText = &ERText[ERT_MESS2];
      ERText[ERT_MESS1].TopEdge  = ER_MESS1Y;
      ERText[ERT_MESS2].TopEdge  = ER_MESS2Y;
      for (i=30; i>0 && Message[i] != ' '; i--);
      if (i == 0) i = 30;
      Message[i++] = 0;
      ERText[ERT_MESS1].LeftEdge = (ER_PANELW - CHAR_WIDTH*(i-1)) / 2;
      ERText[ERT_MESS2].LeftEdge = (ER_PANELW - CHAR_WIDTH*(Len-i)) / 2;
      ERText[ERT_MESS2].IText = &Message[i];
   } else {
      ERText[ERT_MESS1].LeftEdge = (ER_PANELW - CHAR_WIDTH*Len) / 2;
      ERText[ERT_MESS1].TopEdge  = ER_MESSY;
      ERText[ERT_MESS1].NextText = NULL;
   }
}


/*
 *  WaitForGadget()
 *
 *  As long as no gadget is pressed
 *    For each message in the window's UserPort,
 *      store the delayed shift,
 *      if the message is a gadget up,
 *        do the Gadget routine with the given gadget
 *      reply to the message
 *    If we are not yet done, wait for additional messages
 */

static void WaitForGadget(DoGadget)
int (*DoGadget)();
{
   struct IntuiMessage *theMessage;
   short NotDone = TRUE;
   struct MsgPort *thePort = myWindow->UserPort;
   long SignalMask = (1 << thePort->mp_SigBit);

   while (NotDone)
   {
      while (theMessage = GetMsg(thePort))
      {
         DelayedShift = theMessage->Qualifier & SHIFTKEYS;
         if (theMessage->Class == GADGETUP)
            NotDone = (*DoGadget)(theMessage->IAddress,NotDone);
         ReplyMsg(theMessage);
      }
      if (NotDone) Wait(SignalMask);
   }
}


/*
 *  ClearInfoMessage()
 *
 *  If the Info requester is up, remove the requester and clear the flag.
 */

void ClearInfoMessage()
{
   if (InfoReqActive)
   {
      EndRequest(&ERRequest,myWindow);
      InfoReqActive = FALSE;
   }
}


/*
 *  DoInfoMessage()
 *
 *  Clear any previous info message,
 *  Set the message and title strings, and remove the requester's gadgets
 *  Put up the requester, if possible.
 */

void DoInfoMessage(s,x1,x2,x3)
char *s,*x1,*x2,*x3;
{
   static char Message[80];
   
   ClearInfoMessage();
   sprintf(Message,s,x1,x2,x3);
   SetErrorText("Please Be Patient:",Message);
   ERRequest.ReqGadget = NULL;
   if (Request(&ERRequest,myWindow)) InfoReqActive = TRUE;
}


/*
 *  CheckForOK
 *
 *   If the gadget ID is the OK gadget, then we're done
 */

static int CheckForOK(theGadget,NotDone)
struct Gadget *theGadget;
int NotDone;
{
   if (theGadget->GadgetID == ER_OK) NotDone = FALSE;
   return(NotDone);
}


/*
 *  DoError()
 *
 *  Clear the info requester if it is up
 *  Prepare the error message, and set the requester title and gadgets
 *  Put up the requester, if possible.
 *  If not, try to free HELP information and try again.
 *  If the requester could be openned,
 *    wait for the OK button to be pressed, and then end the requester
 *  Otherwise
 *    Remake the error message in case it was divied into two lines.
 *    put the message into the screen title and save the old title.
 */  

void DoError(s,x1,x2,x3)
char *s,*x1,*x2,*x3;
{
   char Message[80];
   int ReqSet;

   ClearInfoMessage();
   sprintf(Message,s,x1,x2,x3);
   SetErrorText("Error Report:",Message);
   ERRequest.ReqGadget = &ERGadget[0];
   ReqSet = Request(&ERRequest,myWindow);
   if (!ReqSet)
   {
      if (CanClearHelpLines()) ReqSet = Request(&ERRequest,myWindow);
      if (!ReqSet && CanClearHelpTopics())
         ReqSet = Request(&ERRequest,myWindow);
   }
   if (ReqSet)
   {
      WaitForGadget(&CheckForOK);
      EndRequest(&ERRequest,myWindow);
   } else {
      sprintf(Message,s,x1,x2,x3);
      if (SavedTitle == NULL) SavedTitle = myWindow->ScreenTitle;
      SetWindowTitles(myWindow,-1,Message);
   }
}


/*
 *  ClearError()
 *
 *  If the message was a mouse button, gadget up or down, or a window
 *  inactivation event, and we had previously saved a window title, 
 *  put back the old title, and clear the saved title.
 */

void ClearError(theMessage)
struct IntuiMessage *theMessage;
{
   switch (theMessage->Class)
   {
      case MOUSEBUTTONS:
      case GADGETUP:
      case GADGETDOWN:
      case INACTIVEWINDOW:
         if (SavedTitle) SetWindowTitles(myWindow,-1,SavedTitle);
         SavedTitle = NULL;
         break;
   }
}

static int YesOrNo;


/*
 *  CheckForYesNo()
 *
 *  If either button is pressed, we're done.  Record the correct YesOrNo
 *  value for a YES being pressed.
 */

static int CheckForYesNo(theGadget,NotDone)
struct Gadget *theGadget;
int NotDone;
{
   switch(theGadget->GadgetID)
   {
      case ER_YES:
         YesOrNo = TRUE;
      case ER_NO:
         NotDone = FALSE;
         break;
   }
   return(NotDone);
}


/*
 *  DoQuestion()
 *
 *  Clear the info requester, if any is up.
 *  set the requester title and message strings, and set up the gadget list.
 *  Clear the YesOrNo answer.
 *  Put up the requester, if possible.
 *    If successful, wait for the Yes or No buttons to be pressed
 *    and then remove the requester
 *  return the button that was pressed.
 */

int DoQuestion(s,x1,x2,x3)
char *s,*x1,*x2,*x3;
{
   char Message[80];

   ClearInfoMessage();
   sprintf(Message,s,x1,x2,x3);
   SetErrorText("Confirmation Requested:",Message);
   ERRequest.ReqGadget = &ERGadget[1];
   YesOrNo = FALSE;
   if (Request(&ERRequest,myWindow))
   {
      WaitForGadget(&CheckForYesNo);
      EndRequest(&ERRequest,myWindow);
   }
   return(YesOrNo);
}

#undef AllocMem
static int BufferErrors,ErrorReported;


/*
 *  myAllocMem()
 *
 *  Attempt to allocate the requested block of memory.
 *  Check the remaining memory available.
 *  If there is little memory left, or if there was not enough to get the
 *      requested chunk,
 *    Try to clear the help text lines that aren't in use, and try
 *      to allocate the memory again, if necessary.
 *    If there's still very little memory,
 *      Try to free the help topics as well, and then try to get the
 *        memory again, if necessary.
 *  If there is a dangerously small amount of memory,
 *    and we haven't already said so, then give a memory warning, and
 *    record that we did so.
 *  Otherwise, if there is little memory left,
 *    and we haven't already mentioned it, do so and record that we did.
 *  Otherwise, note that there is sufficient memory again.
 */

APTR myAllocMem(n,Flags)
int n;
ULONG Flags;
{
   long Mem;
   APTR theByte;
   extern APTR AllocMem();
   extern long AvailMem();
   
   theByte = AllocMem(n,Flags);
   Mem = AvailMem(0);
   if (Mem < MEM_WARNING || theByte == NULL)
   {
      if (CanClearHelpLines())
      {
         if (theByte == NULL) theByte = AllocMem(n,Flags);
         Mem = AvailMem(0);
      }
      if (Mem < MEM_DANGER || theByte == NULL)
      {
         if (CanClearHelpTopics())
         {
            if (theByte == NULL) theByte = AllocMem(n,Flags);
            Mem = AvailMem(0);
         }
      }
   }
   if (Mem < MEM_DANGER)
   {
      if (ErrorLevel > MEM_DANGER)
         DoError("Danger: Only %dK Memory Free!",Mem/1024);
      ErrorLevel = MEM_DANGER;
   } else if (Mem < MEM_WARNING) {
      if (ErrorLevel > MEM_WARNING)
         DoError("Low On Memory: %dK Remaining",Mem/1024);
      ErrorLevel = MEM_WARNING;
   } else {
      ErrorLevel = MEM_OK;
   }
   return(theByte);
}


/*
 *  SetErrorWindow()
 *
 *  Set the process error screen to be the CELLS screen, but record the
 *  old window pointer so we can replace it later.
 */

void SetErrorWindow(theWindow)
APTR theWindow;
{
   extern struct Process *FindTask();
   
   ourProcess = FindTask(NULL);
   OldPrWindow = ourProcess->pr_WindowPtr;
   ourProcess->pr_WindowPtr = theWindow;
}


/*
 *  ResetErrorWindow()
 *
 *  Put back the old process error screen pointer, if we changed it above.
 */

void ResetErrorWindow()
{
   if (ourProcess) ourProcess->pr_WindowPtr = OldPrWindow;
}
