/*
 *  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:  cDoMouse.c       Handles Mouse Move and Button events
 */


#include "cGadget.h"

int MouseMoveType = MM_NONE;            /* type of mouse move expected */
int ColorInUse    = BRDR_WIREC;         /* what color is in use */
int PenInUse      = WIRE;               /* what state is to be written */

static int StartingColor;               /* variables for right-mouse effect */
static int ColorCount;
static short ColorX,ColorY;
static int CurrentColor;

static int OldSecs,OldMics;             /* double click times */


/*
 *  StartMouseMoves()
 *
 *   Start reporting mouse moves.  They  will be handled according to the
 *   MouseMoveType variable.
 */

void StartMouseMoves()
{
   ModifyIDCMP(myWindow,myWindow->IDCMPFlags | MOUSEMOVE);
}


/*
 *  StopMouseMoves()
 *
 *  Stop reporting mouse move events.
 */

void StopMouseMoves()
{
   ModifyIDCMP(myWindow,myWindow->IDCMPFlags & ~MOUSEMOVE);   
}


/*
 *  InBoard()
 *
 *  Returns TRUE if the mouse position is inside the active grid area, and
 *  calculates the grid position (relative to GridX and GridY) of the mouse.
 */

int InBoard(Mx,My,x,y)
short Mx,My;
short *x,*y;
{
   int Inside = FALSE;

   if (Mx >= BoardX && Mx <= BoardW &&
       My >= BoardY && My <= BoardH)   
   {
      Inside = TRUE;
      if (x) *x = (Mx - BoardX) / CellSize;
      if (y) *y = (My - BoardY) / CellSize;
   }
   return(Inside);
}


/*
 *  RestrictToBoard()
 *
 *  Calculate the nearest grid cell to the mouse position, not going
 *  outside of the active screen area.
 */

void RestrictToBoard(Mx,My,x,y)
short Mx,My;
short *x,*y;
{
   if (Mx < BoardX) Mx = BoardX; else if (Mx > BoardW) Mx = BoardW;
   if (My < BoardY) My = BoardY; else if (My > BoardH) My = BoardH;
   *x = (Mx - BoardX) / CellSize;
   *y = (My - BoardY) / CellSize;
}


/*
 *  SetPenColor()
 *
 *  If the pen color is not alread the active color,
 *    Reset the current color's border pen
 *    Calculate the new color and pen
 *    Highlight the new color's gadget
 *    Relink the color gadgets so that the selected one is drawn last
 *    Redraw the gadgets (i.e., change the highlighted color)
 *    Set the mouse pointer to the selected color
 */

void SetPenColor(theID)
int theID;
{
   short i,j;

   if (theID != ColorInUse)
   {
      cBorder[ColorInUse].FrontPen = BOXPEN;
      ColorInUse = theID - ID_BLANKC + BRDR_BLANKC;
      PenInUse = ((struct Image *)(cGadget[theID].GadgetRender))->PlaneOnOff;
      cBorder[ColorInUse].FrontPen = HIGHPEN;
      for (i=BRDR_BLANKC,j=i-1; i<=BRDR_HEADC; i++)
      {
         if (i != ColorInUse)
         {
            cBorder[j].NextBorder = &cBorder[i];
            j = i;
         }
      }
      cBorder[j].NextBorder = &cBorder[ColorInUse];
      cBorder[ColorInUse].NextBorder = NULL;
      DrawBorder(wrp,cBorder[BRDR_BLANKC-1].NextBorder,0,0);
      SetPointerColor(PenInUse);
   }
}


/*
 *  ColorMouseCell()
 *
 *  If the mouse is within the active grid area,
 *    calculate the screen position of the cell
 *    if it is not the last cell colored,
 *      record it as the last one colored,
 *      if this is the second cell colored with an electron color,
 *        change the electron color temporarily
 *      set the pen color and drawing mode
 *      fill the grid cell with the color
 *      If double buffer mode is on, update BOTH buffers
 *      Set the color of the cell in the grid array
 *      record that changes have been made.
 */

void ColorMouseCell(MouseX,MouseY,Pen)
short MouseX,MouseY;
int Pen;
{
   short X,Y,x,y;

   if (InBoard(MouseX,MouseY,&x,&y))
   {
      X = x * CellSize + BoardX + 1;
      Y = y * CellSize + BoardY + 1;
      if (X != ColorX || Y != ColorY || Pen != CurrentColor)
      {
         ColorX = X; ColorY = Y; CurrentColor = Pen;
         if (ColorCount < 2)
         {
            ColorCount++;
            if (ColorCount == 2 && (Pen == TAIL || Pen == HEAD))
               Pen = (HEAD + TAIL) - Pen;
         }
         SetAPen(wrp,Pen);
         SetDrMd(wrp,JAM1);
         RectFill(wrp,X,Y,X+CellSize-2,Y+CellSize-2);
         if (DBufMode)
         {
            SetAPen(rp,Pen); SetDrMd(rp,JAM1);
            RectFill( rp,X,Y,X+CellSize-2,Y+CellSize-2);
         }
         SetCell(CurGen,x+GridX,y+GridY,Pen);
         Changed = TRUE;
      }
   }
}


/*
 *  ColorScreenCell()
 *
 *  If the specified cell is within the acrive area, calculate the screen
 *  position of the cell and color it in.
 */

void ColorScreenCell(X,Y,Pen)
short X,Y;
int Pen;
{
   int x,y;
       
   if (X >= 0 && X < GridW && Y >= 0 && Y < GridH)
   {
      x = BoardX + X * CellSize + 1,
      y = BoardY + Y * CellSize + 1;
      SetAPen(rp,Pen);
      SetDrMd(rp,JAM1);
      RectFill(rp,x,y,x+CellSize-2,y+CellSize-2);
   }
}


/*
 *  Array to determine what color to change use when the right mouse button
 *  is clicked
 */

static UWORD PenChange[4][4] =
{
   {ID_WIREC, ID_BLANKC, ID_HEADC, ID_TAILC},   /* Click w/o SelectDown */
   {ID_HEADC, ID_TAILC, ID_WIREC, ID_BLANKC},   /* Double-Click w/o Select */
   {ID_WIREC, ID_BLANKC, ID_WIREC, ID_WIREC},   /* Click with SelectDown */
   {ID_TAILC, ID_HEADC, ID_BLANKC, ID_BLANKC}   /* Double-Click with Select */
};


/*
 *  ChangePenColor()
 *
 *  Offset the index value if the left button is held down.
 *  If the click is a double click, update the array index, and
 *    clear the double click counter, otherwise record the double click values.
 *  Set the pen to the proper value, according to the array.
 */

static void ChangePenColor(Seconds,Micros,theMessage)
int Seconds,Micros;
struct IntuiMessage *theMessage;
{
   int i = 0;

   if (theMessage->Qualifier & IEQUALIFIER_LEFTBUTTON) i += 2;
   if (DoubleClick(OldSecs,OldMics,Seconds,Micros))
   {
      i++;
      OldSecs = OldMics = 0;
   } else {
      OldSecs = Seconds;
      OldMics = Micros;
   }
   SetPenColor(PenChange[i][ColorInUse-BRDR_BLANKC]);
}


/*
 *  DoMouseMove()
 *
 *  If a slider is active
 *    do the action for the proper slider,
 *  Otherwise,
 *    In DRAW mode:
 *      If the left button is down, fill the proper cell.
 *
 *    In SELECT mode:
 *      Do the selection routine.
 *
 *    In MOVE or COPY mode:
 *      Do the move or copy routine.
 */

void DoMouseMove(MouseX,MouseY,theMessage)
short MouseX,MouseY;
struct IntuiMessage *theMessage;
{
   if (SliderActive)
   {
      if (cGadget[ID_SLIDEH].Flags & SELECTED) DoSlideH();
      else if (cGadget[ID_SLIDEV].Flags & SELECTED) DoSlideV();
   } else {
      switch (MouseMoveType)
      {
         case MM_DRAW:
            if (theMessage->Qualifier & IEQUALIFIER_LEFTBUTTON)
               ColorMouseCell(MouseX,MouseY,PenInUse);
            break;

         case MM_SELECT:
            DoSelectMove(MouseX,MouseY,theMessage);
            break;

         case MM_MOVE:
         case MM_COPY:
            DoCopyMove(MouseX,MouseY);
            break;
      }
   }
}


/*
 *  DoDrawButton()
 *
 *  For a SELECTDOWN:
 *    if the circuit is not running and if the mouse button is within the grid,
 *      record the current grid for UNDO
 *      record the starting color and clear the cells-colored count.
 *      get mouse move reports in DRAW mode.
 *      color the selected cell
 *
 *  For a SELECTUP:
 *    if the circuit is not running,
 *      clear the last-colored variables, and stop the mouse move reports
 *
 *  For a MENUDOWN:
 *    Choose the proper new color
 *    clear the cells-colored count.
 *    If we're currently drawing (i.e., the left mouse button is down)
 *      change the color of the current cell.
 *
 *  For a MENUUP:
 *    If we're in draw mode,
 *      reset the pen color to it original value.
 */

void DoDrawButton(Code,MouseX,MouseY,theMessage)
USHORT Code;
short MouseX,MouseY;
struct IntuiMessage *theMessage;
{
   switch(Code)
   {
      case SELECTDOWN:
         if (!Running)
         {
            if (InBoard(MouseX,MouseY,NULL,NULL))
            {
               StartingColor = ColorInUse; ColorCount = 0;
               StartMouseMoves(); MouseMoveType = MM_DRAW;
               CopyGrid(CurGen,UndoArray);
               ColorMouseCell(MouseX,MouseY,PenInUse);
            }
         }
         break;

      case SELECTUP:
         if (!Running)
         {
            ColorX = GridW; ColorY = GridH; CurrentColor = 0;
            StopMouseMoves(); MouseMoveType = MM_NONE;
         }
         break;

      case MENUDOWN:
         ChangePenColor(theMessage->Seconds,theMessage->Micros,theMessage);
         ColorCount = 0;
         if (MouseMoveType == MM_DRAW)
            ColorMouseCell(MouseX,MouseY,PenInUse);
         break;

      case MENUUP:
         if (MouseMoveType == MM_DRAW)
         {
            SetPenColor(StartingColor - BRDR_BLANKC + ID_BLANKC);
            CurrentColor = PenInUse; ColorCount = 0;
         }
         break;
   }
}


/*
 *  DoMouseButton()
 *
 *  Call the proper mouse button routine depending on the type of mouse
 *  move we are expecting.
 */

void DoMouseButton(Code,MouseX,MouseY,theMessage)
USHORT Code;
short MouseX,MouseY;
struct IntuiMessage *theMessage;
{
   switch (MouseMoveType)
   {
      case MM_NONE:
      case MM_DRAW:
         DoDrawButton(Code,MouseX,MouseY,theMessage);
         break;

      case MM_SELECT:
         DoSelectButton(Code,MouseX,MouseY,theMessage);
         break;

      case MM_MOVE:
      case MM_COPY:
         DoCopyButton(Code,MouseX,MouseY,theMessage);
         break;
   }
}
