/*
 *  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:  cMain.c          Main wait loop and Intuition stuff.
 */


#include "cGadget.h"
#include "cRequest.h"
#include "cReqLP.h"

#undef AllocMem

#define IBMW    (LP_PNAMEW-2)               /* size of View image bit map */
#define IBMH    (LP_PNAMEH-2)

extern struct Window *OpenWindow();
extern struct Screen *OpenScreen();
extern struct IntuiMessage *GetMsg();
extern APTR OpenLibrary();
extern struct TextFont *OpenDiskFont();
extern UBYTE *AllocMem();
extern PLANEPTR AllocRaster();

UBYTE *ResetArray,*UndoArray,*CurGen,*NewGen;           /* the state arrays */

static struct TextAttr cTextAttr = {"Moonstone.font", 8, 0, 0};
static struct TextFont *myFont;
static short MouseX,MouseY;             /* for buffered mouse move stuff */


/*
 *  The CELLS screen
 */

static struct NewScreen NewScreen =
{
   0,0, SCREENW,SCREENH, SCREEND, BACKGROUND,FOREGROUND, 0, CUSTOMSCREEN, NULL,
   " Cellular Automata  v1.2", NULL, NULL
};
struct Screen *myScreen;


/*
 *  The CELLS window
 */

static struct NewWindow NewWindow =
{
   WINDOWX,WINDOWY, WINDOWW,WINDOWH, BACKGROUND,FOREGROUND,
   RAWKEY| MOUSEBUTTONS| GADGETUP| GADGETDOWN| ACTIVEWINDOW|
      INACTIVEWINDOW| REQSET| DISKINSERTED| DISKREMOVED,
   SMART_REFRESH| NOCAREREFRESH| BACKDROP| BORDERLESS| ACTIVATE|
      RMBTRAP| REPORTMOUSE,
   NULL, NULL, NULL, NULL, NULL, 20,10, -1,-1, WBENCHSCREEN
};
struct Window *myWindow;
struct RastPort *wrp,*rp,*irp;    /* the window RP, DBuffer rp, and View RP */
static struct RastPort RP,iRP;    /* fake RPs for DBuffer and View image */
static struct BitMap BM,iBM;      /* BitMaps for same */
int DBufMode = TRUE;              /* TRUE if DBuffer Mode is one */
int BufferMouse;                  /* TRUE if collecting mouse moves for later */


/*
 *  FreeBM()
 *
 *  Free the allocated planes of the Double Buffer BitMap
 *  If the Image Bitmap planes were allocated, free them (they are all in
 *  a contiguous block, to match both the BitMap and Image structures)
 */

static void FreeBM()
{
   short i;
   
   for (i=0; i<SCREEND; i++)
      if (BM.Planes[i]) FreeRaster(BM.Planes[i],BOARDW,BOARDH);
   if (iBM.Planes[0]) FreeRaster(iBM.Planes[0],IBMW,IBMH*SCREEND);
}


/*
 *  DoExit()
 *
 *  Main exit routine - prints an error message, if necessary, and then
 *  cleans up any allocated memory, closes windows and screens, 
 *  closes libraries, etc., then exits with a proper return value.
 */

void DoExit(s,x1,x2,x3)
char *s, *x1,*x2,*x3;
{
   int status = OK_EXIT;

   if (s)
   {
      printf(s,x1,x2,x3);
      printf("\n");
      status = ERROR_EXIT;
   }
   ClearHelp();
   ClearDirectoryList();
   ClearLists(TRUE);
   ClearLibs();
   FreeBlock();
   FreeBM();
   ResetErrorWindow();
   RestoreStartingDir();
   if (myWindow)        CloseWindow(myWindow);
   if (myScreen)        CloseScreen(myScreen);
   if (ResetArray)      FreeMem(ResetArray,ARRAYSIZE);
   if (UndoArray)       FreeMem(UndoArray,ARRAYSIZE);
   if (CurGen)          FreeMem(CurGen,ARRAYSIZE);
   if (NewGen)          FreeMem(NewGen,ARRAYSIZE);
   if (myFont)          CloseFont(myFont);
   if (IntuitionBase)   CloseLibrary(IntuitionBase);
   if (GfxBase)         CloseLibrary(GfxBase);
   if (DiskfontBase)    CloseLibrary(DiskfontBase);
   exit(status);
}


/*
 *  CheckLibOpen()
 *
 *  Attempt to open the specified version of the specified library.
 *  If a failure, report ythe error and exit, otherwise return the library
 *  pointer.
 */

void CheckLibOpen(lib,name,rev)
APTR *lib;
char *name;
int rev;
{
   extern APTR OpenLibrary();
   
   if ((*lib = OpenLibrary(name,(ULONG)rev)) == NULL)
      DoExit("Can't open '%s'",name);
}


/*
 *  OpenMyWindow
 *
 *  If the screen has been openned, open the window on that screen
 *  Try to open the window
 *  If not OK, exit with an error
 *  Otherwise, set the window rast port,
 *  add the gadgets to the window and refresh them (this avoids the
 *  extra refresh Intuition makes when it opens a window with gadgets)
 *  Set the initial board size and clear the screen
 *  Set the slider positions.
 */

static void OpenMyWindow()
{
   if (myScreen)
   {
      NewWindow.Screen = myScreen;
      NewWindow.Type   = CUSTOMSCREEN;
   }
   myWindow = OpenWindow(&NewWindow);
   if (myWindow == NULL) DoExit("Can't open my window");
   wrp = myWindow->RPort;
   AddGList(myWindow,&cGadget[0],-1,GADGET_COUNT,NULL);
   RefreshGadgets(&cGadget[0],myWindow,NULL);
   SetBoardSize(CELLSIZE);
   ClearScreen(TRUE);
   UpdateSliders();
}


/*
 *  OpenMyScreen()
 *
 *  Try to get the CELLS font (Moonstone).
 *  If successful, set the screen to use that font,
 *  Otherwise, warn the user about the missing font
 *  Try to open the screen
 *  If not OK, exit with an error
 *  Otherwise, load the colors and remove the screen title bar (we want the
 *  CELLS window to be able to show over top of its border)
 *  Reset the screen pen color in case NL-Daemon is running
 */

static void OpenMyScreen()
{
   myFont = OpenDiskFont(&cTextAttr);
   if (myFont)
      NewScreen.Font = &cTextAttr;
     else
      printf("Can't Open %s -- Things may look ugly!\n",cTextAttr.ta_Name);
   myScreen = OpenScreen(&NewScreen);
   if (myScreen == NULL) DoExit("Can't open my screen");
   LoadColors();
   ShowTitle(myScreen,FALSE);
   myScreen->DetailPen = BACKGROUND;
}


/*
 *  SetupRP()
 *
 *  Initialize the DBuffer and View image rastports, and their pointers
 *  Initialize the BitMaps for them
 *  Attempt to allocate board-sized rasters for the DBuffer
 *  Attempt to allocate contiguous rasters for the View image
 *   (it will be used by an Image structure during View mode)
 *  Set the rast ports to use the bitmaps
 */

static void SetupRP()
{
   short i;

   InitRastPort(&RP); InitRastPort(&iRP);
   rp = &RP; irp = &iRP;
   InitBitMap(&BM,SCREEND,BOARDW,BOARDH);
   InitBitMap(&iBM,SCREEND,IBMW,IBMH);
   for (i=0; i<SCREEND; i++)
   {
      BM.Planes[i] = AllocRaster(BOARDW,BOARDH);
      if (BM.Planes[i] == NULL)
         DoExit("Can't Allocate Raster %d of size %d,%d",i,BOARDW,BOARDH);
   }
   iBM.Planes[0] = AllocRaster(IBMW,IBMH*SCREEND);
   if (iBM.Planes[0] == NULL)
      DoExit("Can't Allocate Raster for Image Data");
   for (i=1; i<SCREEND; i++)
      iBM.Planes[i] = iBM.Planes[i-1] + RASSIZE(IBMW,IBMH);
   RP.BitMap = &BM; iRP.BitMap = &iBM;
}


/*
 *  SetDBufMode()
 *
 *  Set to the specified mode
 *  If turning DBuf mode ON
 *    use the DBuffer Rast Port, and copy the screen to the buffer
 *  Otherwise
 *    use the window Rast Port directly
 */

void SetDBufMode(theMode)
int theMode;
{
   DBufMode = theMode;
   if (theMode == TRUE)
   {
      rp = &RP;
      UpdateBuffer();
   } else {
      rp = wrp;
   }
}


/*
 *  SetupArrays()
 *
 *  Allocate the arrays needed for the state information
 *  (Reset is used by the RESET button, Undo by the UNDO button,
 *   CurGen is the current states as shown on the screen, NewGen is
 *   the next generation being calculated, and is used by SELECT and
 *   MOVE/COPY/PART to determine the selected cells)
 *  If any of the arrays could not be allocated, exit with an error
 *  Otherwise, clear the important arrays.
 */

static void SetupArrays()
{
   ResetArray = AllocMem(ARRAYSIZE,0);
   UndoArray  = AllocMem(ARRAYSIZE,0);
   CurGen     = AllocMem(ARRAYSIZE,0);
   NewGen     = AllocMem(ARRAYSIZE,0);
   if (ResetArray == NULL || UndoArray == NULL ||
       CurGen == NULL || NewGen == NULL)
          DoExit("Not enough memory for Generation Arrays");
   ClearGrid(ResetArray);
   ClearGrid(UndoArray);
   ClearGrid(CurGen);
}


/*
 *  MoveMouse()
 *
 *  If a requester is up, do the requester mouse routine
 *  Otherwise do the regular mouse routine
 *  Clear the remembered mouse position
 */

static void MoveMouse(Mx,My,theMessage)
short Mx,My;
struct IntuiMessage *theMessage;
{
   if (ActiveRequest)
     DoReqMouseMove(Mx,My,theMessage);
    else
     DoMouseMove(Mx,My,theMessage);
   MouseX = MouseY = 0;
}


/*
 *  DoMessage()
 *
 *  Clear any error messages (in the title bar)
 *  Set the gadgets to the proper shifted or un-shifted mode
 *  If there are stored mouse moves and the current message is NOT another
 *    mouse move, then do the stored mouse move before going on
 *
 *  For each type of message, do the right thing:
 *
 *    RAWKEY:
 *      Do the kayboard routine
 *
 *    MOUSEBUTTONS:
 *      Do the mouse button routine
 *
 *    MOUSEMOVE:
 *      If mouse moves are being collected and combined (to save time)
 *        save the new mouse position
 *      otherwise
 *        do the mouse move right away
 *
 *    GADGETDOWN:
 *      Get the gadget in question
 *      If the gadget is from a requester, do the requester routine
 *      otherwise do the gadget down routine
 *
 *    GADGETUP:
 *      Get the gadget in question
 *      If the gadget is from a requester, do the requester routine
 *      otherwise do the gadget up routine
 *
 *   REQSET:
 *     Do the request set routine for the active requester
 *
 *   INACTIVEWINDOW:
 *     Do the window inactivate routine
 *
 *   DISKREMOVED or INSERTED:
 *     Do the proper disk routine
 */

short DoMessage(theMessage,NotDone)
struct IntuiMessage *theMessage;
short NotDone;
{
   struct Gadget *theGadget;

   ClearError(theMessage);
   ShiftGadgets(theMessage->Qualifier & SHIFTKEYS);
   if (theMessage->Class != MOUSEMOVE && (MouseX || MouseY))
      MoveMouse(MouseX,MouseY,NULL);
   switch (theMessage->Class)
   {
      case RAWKEY:
         NotDone = DoKey(theMessage->Code,theMessage,NotDone);
         break;

      case MOUSEBUTTONS:
         DoMouseButton(theMessage->Code,theMessage->MouseX,
           theMessage->MouseY,theMessage);
         break;

      case MOUSEMOVE:
         if (BufferMouse)
         {
            MouseX = theMessage->MouseX;
            MouseY = theMessage->MouseY;
         } else {
            MoveMouse(theMessage->MouseX,theMessage->MouseY,theMessage);
         }
         break;

      case GADGETDOWN:
         theGadget = (struct Gadget *)theMessage->IAddress;
         if (theGadget->GadgetType & REQGADGET)
            DoReqGadgetDown(theGadget,theMessage);
           else
            DoGadgetDown(theGadget,theMessage);
         break;

      case GADGETUP:
         theGadget = (struct Gadget *)theMessage->IAddress;
         if (theGadget->GadgetType & REQGADGET)
            NotDone = DoReqGadgetUp(theGadget,theMessage,NotDone);
           else
            NotDone = DoGadgetUp(theGadget,theMessage,NotDone);
         break;

      case REQSET:
         DoReqSet(ActiveRequest);
         break;

      case INACTIVEWINDOW:
         DoInactive(theMessage->IDCMPWindow,theMessage);
         break;
         
      case DISKINSERTED:
         DoDiskInserted(theMessage);
         break;

      case DISKREMOVED:
         DoDiskRemoved(theMessage);
         break;
   }
   return(NotDone);
}


/*
 *  WaitForAction()
 *
 *  While there are more actions to be done,
 *    While there are messages in the message port
 *      Do the messages, and reply to them
 *    If we are still not done and there are no more messages
 *      if there are buffered mouse moves, do them
 *      update the shifted status of the keys (in case the changes was delayed)
 *      wait for the next message
 */

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

   while (NotDone)
   {
      while (theMessage = GetMsg(thePort))
      {
         if (NotDone) NotDone = DoMessage(theMessage,NotDone);
         ReplyMsg(theMessage);
      }
      if (NotDone)
      {
         if (MouseX || MouseY) MoveMouse(MouseX,MouseY,NULL);
         UpdateShiftKeys();
         Wait(SignalMask);
      }
   }
}


/*
 *  CheckForAction_)
 *
 *  Do any actions that may be in the message queue, but don't wait for
 *  new messages (this is called when CELLS is executing a circuit)
 */

short CheckForAction(NotDone)
short NotDone;
{
   struct IntuiMessage *theMessage;
   struct MsgPort *thePort = myWindow->UserPort;

   while (theMessage = GetMsg(thePort))
   {
      if (NotDone) NotDone = DoMessage(theMessage,NotDone);
      ReplyMsg(theMessage);
   }
   return(NotDone);
}


/*
 *  main()
 *
 *  Open the requeired libraries
 *  Get the inital directory for replacement when we are done
 *  Attempt to read a color file, if any
 *  Allocate and set up the state arrays, rast ports and bit maps
 *  Open the screen and window
 *  Set the CELLS screen as the one where System Requests should appear
 *  If a file name was provided on the command line, try to load it
 *  Set the pointer to the drawing color
 *  Do the main action loop
 *  When that loop exits, we are all done, so exit with no error
 */

void main(argc,argv)
int argc;
char *argv[];
{
   CheckLibOpen(&IntuitionBase,"intuition.library",INTUITION_REV);
   CheckLibOpen(&GfxBase,"graphics.library",GRAPHICS_REV);
   CheckLibOpen(&DiskfontBase,"diskfont.library",DISKFONT_REV);
   SaveStartingDir();
   ReadColorFile();
   SetupArrays();
   SetupRP();
   OpenMyScreen();
   OpenMyWindow();
   SetErrorWindow(myWindow);
   if (argc > 1)
   {
      InvertGadget(&cGadget[ID_LOAD]);
      ReadFile(argv[1],FALSE);
   }
   SetPointerColor(PenInUse);
   WaitForAction();
   DoExit(NULL);
}
