#include "pz.h"

/* Ali Ozer
** Main user interface/action loop for IFF2PCS
** AND a whole lot more...
** This file shouldn't be this long, but unfortunately, it is...
** It is also a bit messy (too many extern references, ack!)
** Nov 1987
*/
    
/* We assume several things:
**  puzph = puzpw
**  picx = integer multiple of (puzpw)
**  picy = integer multiple of (puzph)
*/
extern int puzpn, puzdepth;         /* These are the parameters for a piece */
extern int picx, picy;              /* Where the picture is */
extern int picw, pich;              /* The size of the picture */
extern int textcolor;
extern int bordercolor;
extern int normcolor;
extern int nonzerocolor;
struct BitMap bgbm, rotbm;    /* Background bitmap, rotation bitmap */

extern struct PopUp_Menu pzmenu;

unsigned long starttime;      /* In seconds */

int minmousex, maxmousex, minmousey, maxmousey;

int puzph, puzpw;
int picxbase, picybase; /* Simply picx-puzpw/2 and picy-puzph/2 */

extern struct BitMap picbm; 

struct BitMap   *wbm;
struct RastPort *wrp;

struct Window *picwin;
struct Screen *picscr;

int winoffset;  /* Offset of the puzzle area within the window */

int numpieces, numx, numy;

int toppiece, bottompiece;

/* Number of pieces will be (picw / puzpw) * (pich / puzph)
*/

struct piecestr {
  int xloc, yloc;             /* Pixel location on screen */
  int next, prev;             /* Next piece, in top to bottom sorted order */  
  int rotation;               /* 0 normal, 1 ninety cw, 2 upsidedown, 3 90ccw */
  int ingrid;                 /* True if piece is in the puzzle somewhere... */
} *pieces;                    /* Bottompiece -> Prev -> Prev -> ... -> -1  */

ubyte *gridinfo;  /* Whether a grid piece is occupied or not... */

DrawGrid ()
{
    long x = picx-1;
    long y = picy-1;
    int cnt;

    SetAPen (wrp, (long)bordercolor);  
    for (cnt = 0; cnt <= numy; cnt++) {
      Move (wrp, x, y); Draw (wrp, x + picw + 1, y);
      Move (wrp, x, y+1); Draw (wrp, x + picw + 1, y+1);
      y += puzph;
    };

    y = picy-1;
    for (cnt = 0; cnt <= numx; cnt++) {
      Move (wrp, x, y); Draw (wrp, x, y + pich + 1);
      Move (wrp, x+1, y); Draw (wrp, x+1, y + pich + 1);
      x += puzpw;
    };
}


static ubyte tmp[8];  /* Temp for rotates... */

Rotate (plane, nbytes)     /* n is the side of the square in bytes... */
ubyte *plane;
int nbytes;
{
   int cnt, side, n, nbytec = nbytes << 3;
   ubyte *s1, *s2, *s3, *s4;

   for (n = nbytes; n > 1; n -= 2) {
     s1 = plane;
     s2 = plane + n - 1;
     s4 = s1 + nbytec * (n - 1);
     s3 = s4 + n - 1;
     for (side = 0; side < n-1; side++) {
       for (cnt = 0; cnt < 8; cnt++) tmp[cnt] = s1[cnt * nbytes];
       flip (s4, nbytes, s1, nbytes);
       flip (s3, nbytes, s4, nbytes);
       flip (s2, nbytes, s3, nbytes);
       flip (&tmp[0], 1, s2, nbytes);
       s1++; s3--; s2 += nbytec; s4 -= nbytec;
     };
     plane += (nbytec + 1);
   }
}

 
/* Border modes */
#define NOBORDER     0
#define BORDER       1
#define HLBORDER     2  /* Highlighted border */
#define ERASE        3
#define JUSTBORDER   4


/* Come in with x = -1 to use the pieces default position...
*/
DrawPiece (pnum, x, y, border)
int border, x, y, pnum;
{
    long cnt;
    int rot;
    int bmx = (pnum % numx) * puzpw;   
    int bmy = (pnum / numx) * puzph;

    if (x == -1) {
      x = pieces[pnum].xloc;
      y = pieces[pnum].yloc;
    };

    if (border == ERASE || border == JUSTBORDER) {
      SetAPen (wrp, 0L);
      RectFill (wrp, (long)x, (long)y, (long)(x+puzpw-1), (long)(y+puzph-1));
    } else {
      if (pieces[pnum].rotation) {
         SavePuzBM (&picbm, bmx, bmy, &rotbm);  
         for (rot = 0; rot < pieces[pnum].rotation; rot++)
           for (cnt = 0; cnt < rotbm.Depth; cnt++) 
	      Rotate (rotbm.Planes[cnt], puzph >> 3);
         CopyFromBMToBM (&rotbm, 0, 0, wbm, x, y, puzpw, puzph);
      } else CopyFromBMToBM (&picbm, bmx, bmy, wbm, x, y, puzpw, puzph);
    };

    if (border != ERASE) {
      if (border != NOBORDER) {
        if (border == HLBORDER) SetAPen (wrp, (long)textcolor); 
        else SetAPen (wrp, (long)bordercolor);
        Move (wrp, (long)x,         (long)y);
        Draw (wrp, (long)x+puzpw-1, (long)y);
        Draw (wrp, (long)x+puzpw-1, (long)y+puzph-1);
        Draw (wrp, (long)x,         (long)y+puzph-1);
        Draw (wrp, (long)x,         (long)y);             
      }
    }
}


int RndInt();
int Min ();
int Max ();
unsigned long TimeInSecs ();

int PuzzleSolved ()
{
  int width = (picw >> 3);
  int planecnt, bytecnt, rowcnt;
  unsigned char *curplane;

  for (planecnt = 0; planecnt < wbm->Depth; planecnt++) {
    curplane = (unsigned char *)(wbm->Planes[planecnt]) + winoffset;
    for (rowcnt = 0; rowcnt < pich; rowcnt++) {
      for (bytecnt = 0; bytecnt < width; bytecnt++) {
        if (*(curplane+bytecnt)) return (false);
      }
      curplane += wbm->BytesPerRow;
    }
  }
  return (true);
}


CheckPuzzle ()
{
  int solved;
  unsigned long timedif = TimeInSecs() - starttime;
  static char *solvestr = "Time:  0:00:00   *** Puzzle is SOLVED ***";

  XORFromBMToBM (&picbm, 0, 0, wbm, picx, picy, picw, pich);
  if (timedif >= 360000) solvestr[5] = '0' + (timedif / 360000) % 10;
  if (timedif >= 36000)  solvestr[6] = '0' + (timedif / 36000) % 10;
  solvestr[7]  = '0' + (timedif / 3600) % 10;
  solvestr[9]  = '0' + (timedif / 600) % 6;
  solvestr[10] = '0' + (timedif / 60) % 10;
  solvestr[12] = '0' + (timedif / 10) % 6;
  solvestr[13] = '0' + (timedif % 10);
  
  SetWindowTitles (picwin, NULL, "Checking...");
  if (solved = PuzzleSolved()) {
    solvestr[14] = ' '; 
    XORFromBMToBM (&picbm, 0, 0, wbm, picx, picy, picw, pich);      
  } else solvestr[14] = '\0';
  SetWindowTitles (picwin, NULL, solvestr);
  Wait (1L << picwin->UserPort->mp_SigBit);
  if (solved == false) XORFromBMToBM (&picbm, 0, 0, wbm, picx, picy, picw, pich);
  SetWindowTitles (picwin, NULL, PROGNAME);
}

      
    
void MakePuzzle ()
{
  int cnt, miny, maxy, minx, maxx;

  /* Puzzle piece size. Puzpn is the power of two size of each piece.
  ** For now, pieces have to be square, so we set puzph = puzpw.
  */

  /* Minimum and maximum locations for puzzle pieces */
  miny = picy + pich + 4; 
  maxy = picwin->Height - puzph - 4;
  minx = 2;
  maxx = picwin->Width - puzpw - 2;

  minmousex = 0;
  maxmousex = picscr->Width - puzpw - 1;
  minmousey = picscr->BarHeight + 1;
  maxmousey = picscr->Height - puzph - 1;

  if (((pieces = (struct piecestr *)
         AllocMem ((long)(numpieces * sizeof(struct piecestr)), 0L)) == NULL) ||
      ((gridinfo = (ubyte *) 
         AllocMem ((long)(((numx * numy) >> 3) + 1), MEMF_CLEAR)) == NULL)) {
    FreeBMs ();
    Panic ("No memory for bitmaps");
  };

  DrawGrid ();

  for (cnt = 0; cnt < numpieces; cnt++) {
    /* piecexnum = cnt % numx   AND   pieceynum = cnt / numx */
    pieces[cnt].ingrid = false;
    pieces[cnt].rotation = RndInt (0, 3);
    pieces[cnt].next = cnt + 1;
    pieces[cnt].prev = cnt - 1;
    pieces[cnt].xloc = RndInt (minx, maxx);
    pieces[cnt].yloc = RndInt (miny, maxy);
  };

  pieces[numpieces-1].next = -1;
  bottompiece = numpieces - 1;
  toppiece = 0;

  /* Shuffle some so that we don't always get the same order... */
  for (cnt = 0; cnt < (numpieces>>3); cnt++) 
    PutPieceAtTop (RndInt(0, numpieces-1));

  DrawAllPieces ();
}



PutPieceAtTop (piece)
int piece;
{
  if (pieces[piece].next != -1) 
     pieces[pieces[piece].next].prev = pieces[piece].prev;
  else 
     bottompiece = pieces[piece].prev;
  if (pieces[piece].prev != -1) 
     pieces[pieces[piece].prev].next = pieces[piece].next;
  else
     toppiece = pieces[piece].next;
  pieces[piece].next = toppiece;
  pieces[toppiece].prev = piece;
  toppiece = piece;
  pieces[piece].prev = -1;
}


SeeIfInGrid (piece, newx, newy)
int piece, newx, newy;
{
  if ((newx <= picx + picw) && (newx >= picx - puzpw) &&
      (newy <= picy + pich) && (newy >= picy - puzph)) {
     if (newx < picxbase) newx = picxbase; 
     else if (newx >= picxbase + picw) newx = picxbase + picw - 1;
     if (newy < picybase) newy = picybase;
     else if (newy >= picybase + pich) newy = picybase + pich - 1;
     newx = ((newx - picxbase) / puzpw) * puzpw + picx;
     newy = ((newy - picybase) / puzph) * puzph + picy;
     if (GridBoxOccupied (newx, newy, -1) == false) {
       GridBoxOccupied (newx, newy, true);
       pieces[piece].ingrid = true;
       pieces[piece].xloc = newx;
       pieces[piece].yloc = newy;
     } else SeeIfInGrid (piece, pieces[piece].xloc, pieces[piece].yloc);
  } else {
     pieces[piece].ingrid = false;
     pieces[piece].xloc = newx;
     pieces[piece].yloc = newy;
  }
}
    
    
/* Draws pieces that overlap the specified area in any way. Then grows the
** specified area by the bounding box of the new piece. A rather inefficient
** way of fixing up the picture after a change... The argument notpiece
** specifies a piece (if any) that does not need to be redrawn. Set to -1 if
** no such piece.
*/
/*
ReDrawThePiecesAt (x1, x2, y1, y2, notpiece)
int x1, x2, y1, y2, notpiece;
{
  int cnt = bottompiece;
  cnt = bottompiece;
  while (cnt != -1) {
    if ((pieces[cnt].xloc <= x2) && (pieces[cnt].xloc+puzpw-1 >= x1) &&
        (pieces[cnt].yloc <= y2) && (pieces[cnt].yloc+puzph-1 >= y1) &&
        (cnt != notpiece)) {
      DrawPiece (cnt, -1, -1, BORDER);
      x1 = Min (x1, pieces[cnt].xloc);
      x2 = Max (x2, pieces[cnt].xloc+puzpw-1);
      y1 = Min (y1, pieces[cnt].yloc);
      y2 = Max (y2, pieces[cnt].yloc+puzph-1);
    };
    cnt = pieces[cnt].prev;
  }
}
*/


ReDrawThePiecesAt (x1, x2, y1, y2, notpiece)
int x1, x2, y1, y2, notpiece;
{
  int cnt = bottompiece, i, tmpprev;
  for (i = 0; i < numpieces; i++) {
    tmpprev = pieces[cnt].prev;      
    if ((pieces[cnt].xloc <= x2) && (pieces[cnt].xloc+puzpw-1 >= x1) &&
        (pieces[cnt].yloc <= y2) && (pieces[cnt].yloc+puzph-1 >= y1) &&
        (cnt != notpiece)) {
      DrawPiece (cnt, -1, -1, BORDER);
      PutPieceAtTop (cnt);
    };
    cnt = tmpprev;
  }
}


ErasePiece (piece)
int piece;
{
  if (pieces[piece].ingrid == true) {
    DrawPiece (piece, -1, -1, JUSTBORDER);
    GridBoxOccupied (pieces[piece].xloc, pieces[piece].yloc, false);
    pieces[piece].ingrid = false;
  } else {
    DrawPiece (piece, -1, -1, ERASE);
    ReDrawThePiecesAt (pieces[piece].xloc, pieces[piece].xloc + puzph - 1,
                       pieces[piece].yloc, pieces[piece].yloc + puzpw - 1, 
                       piece);
  }
}

  
DrawAllPieces ()
{
  int cnt = bottompiece;
  while (cnt != -1) {
    DrawPiece (cnt, -1, -1, BORDER);
    cnt = pieces[cnt].prev;
  };
}


FreeBMs ()
{
  FreeBM (&bgbm);
  FreeBM (&rotbm);
  if (gridinfo != NULL) FreeMem (gridinfo, (long)(((numx * numy) >> 3) + 1));
  if (pieces != NULL) FreeMem (pieces, (long)(numpieces * sizeof(struct piecestr)));
}


/* Come in with x, y to test and possibly set the mode of the grid at location
** x, y. If mode is true or false, then the new mode is set. If mode is -1, then
** the old mode is returned...
*/
int GridBoxOccupied (x, y, mode)
int x, y, mode;
{
  int index;
  ubyte bit;

  x = (x - picx) / puzpw;  
  y = (y - picy) / puzph;
  index = (y * numx + x) >> 3;
  bit   = (1 << ((y * numx + x) & 7));
  if (mode == true) gridinfo[index] |= bit;
  else if (mode == false) gridinfo[index] &= ~bit;
  else if ((gridinfo[index] & bit) != 0) return (true);
  else return (false);
}
  

/* SavePuzBM will save the indicated section of bm in tmpbm; RestorePuzBM
** will bring that section back into the indicated section in bm.
*/
SavePuzBM (bm, x, y, tmpbm)
struct BitMap *bm, *tmpbm;
int x, y;
{
  CopyFromBMToBM (bm, x, y, tmpbm, 0, 0, puzpw, puzph);
}


RestorePuzBM (bm, x, y, tmpbm)
struct BitMap *bm, *tmpbm;
int x, y;
{
  CopyFromBMToBM (tmpbm, 0, 0, bm, x, y, puzpw, puzph);
}


int PuzzlePieceAt (x, y)
int x, y;
{
  int i = toppiece;
  
  while (i != -1) {
    if ((pieces[i].xloc-1 <= x) &&
        (pieces[i].xloc+puzpw >= x) &&
        (pieces[i].yloc-1 <= y) &&
        (pieces[i].yloc+puzph >= y)) return(i);
    i = pieces[i].next;
  }
  return(-1);  /* Not on any piece... */
}


RotatePiece (piece)
int piece;
{
  if (++(pieces[piece].rotation) > 3) pieces[piece].rotation = 0;
}


GetAndHandleEvents (win)
struct Window *win;
{
  struct IntuiMessage *msg;
  int mousemoved, selected;
  ULONG class;
  USHORT code;
  int x, y, lastx, lasty, deltax, deltay, restorex, restorey, selectedpiece;

  picwin = win;
  picscr = win->WScreen;
  
  wbm = &(picscr->BitMap);
  wrp = win->RPort;

  CopyFromBMToBM (&picbm, 0, 0, wbm, picx, picy, picw, pich);      

  SetDrMd (wrp, JAM1);

  if ((puzpn = GetDifficulty(win)) == 0) Panic (NULL);

  SetAPen (wrp, 0L);
  RectFill (wrp, 0L, (long)picy, (long)(win->Width-1), (long)(win->Height-1));

  puzph = puzpw = (1 << puzpn); 

  picxbase = picx - (puzpw >> 1);
  picybase = picy - (puzph >> 1);

  pich = (pich >> puzpn) << puzpn;
  picw = (picw >> puzpn) << puzpn;

  numx = (picw / puzpw);
  numy = (pich / puzph);

  numpieces = numx * numy;

  winoffset = picy * wbm->BytesPerRow + (picx >> 3);  /* Offset in plane, in bytes */  

  if (InitBM (&bgbm, puzph, puzpw, puzdepth) == false || 
      InitBM (&rotbm, puzph, puzpw, puzdepth) == false) {
    FreeBMs ();
    Panic ("Out of memory!");
  };

  MakePuzzle ();

  selectedpiece = -1;

  starttime = TimeInSecs ();

  while (1) {
 
    mousemoved = false;

    while (msg = (struct IntuiMessage *) GetMsg (win->UserPort)) {

      class = msg->Class;
      code  = msg->Code;
      x     = msg->MouseX; 
      y     = msg->MouseY;

      ReplyMsg (msg);
  
      switch (class) {

	case MENUPICK:
	  switch (ITEMNUM(code)) {
            case 0: /* Show */ break;
            case 1: /* New */  break;
            case 2: /* Quit */
	      FreeBMs ();
              return;  /* For now, MENUUP signifies EXIT */
              break;
          };
	  break;
              
	case MOUSEMOVE: 
	  mousemoved = true; 
	  break;

        case MOUSEBUTTONS: 
          switch (code) {
            case SELECTDOWN: 
	      lastx = x;
              lasty = y;
	      if ((selectedpiece = PuzzlePieceAt (x, y)) != -1) {
                ReportMouse (TRUE, win);  /* Start telling us about mouse loc */
		lastx  = pieces[selectedpiece].xloc;
		deltax = lastx - x;
		lasty  = pieces[selectedpiece].yloc;
		deltay = lasty - y;
                ErasePiece (selectedpiece);
                SavePuzBM (wbm, lastx, lasty, &bgbm);      /* Save background */
                DrawPiece (selectedpiece, -1, -1, HLBORDER);
	        SavePuzBM (wbm, lastx, lasty, &rotbm);     /* Piece picked up */
	        mousemoved = true;
              };
              break;
            case SELECTUP: 
	      if (selectedpiece != -1) {
                ReportMouse (FALSE, win); 
                RestorePuzBM (wbm, lastx, lasty, &bgbm);
		PutPieceAtTop (selectedpiece);
		SeeIfInGrid (selectedpiece, x+deltax, y+deltay);
                DrawPiece (selectedpiece, -1, -1, 
                    (pieces[selectedpiece].ingrid == true ? NOBORDER : BORDER));
		selectedpiece = -1;
                mousemoved = false;
              };
              break;
            case MENUDOWN: 
	      if (selectedpiece != -1) {
		RotatePiece (selectedpiece);
                DrawPiece (selectedpiece, lastx, lasty, HLBORDER);
	        SavePuzBM (wbm, lastx, lasty, &rotbm);     /* Piece picked up */
              } else 
                switch (PopUp (&pzmenu, win)) {
		 case CHECKCMD:
		  CheckPuzzle ();
                  break;               
                 case SHOWCMD: 
     	          XORFromBMToBM (&picbm, 0, 0, wbm, picx, picy, picw, pich);
   	          XORFromBMToBM (wbm, picx, picy, &picbm, 0, 0, picw, pich);	
       	          XORFromBMToBM (&picbm, 0, 0, wbm, picx, picy, picw, pich);
		  Wait (1L << win->UserPort->mp_SigBit);
     	          XORFromBMToBM (&picbm, 0, 0, wbm, picx, picy, picw, pich);
   	          XORFromBMToBM (wbm, picx, picy, &picbm, 0, 0, picw, pich);	
       	          XORFromBMToBM (&picbm, 0, 0, wbm, picx, picy, picw, pich);
		  break;
		 case NEWCMD:  
/*                SetWindowTitles (picwin, NULL, "Option not available yet");
**                Wait (1L << picwin->UserPort->mp_SigBit);
**                SetWindowTitles (picwin, NULL, PROGNAME);
*/                break;
                 case HELPCMD: 
		  if (DoAboutBox (bordercolor, textcolor) == false) {
                    SetWindowTitles (picwin, NULL, COPYRIGHT);
                    Wait (1L << picwin->UserPort->mp_SigBit);
                    SetWindowTitles (picwin, NULL, PROGNAME);
                  };
                  break;
		 case QUITCMD: 
	          FreeBMs ();
                  return; 
                  break;
                 default:
                  break;
		};
	      break;
            case MENUUP: 
	      break;
            default:
	      break;
          }; 
	  break;
  
        default: 
	  break;
      }
       
    }

    if ((mousemoved == true) && (selectedpiece != -1)) {
        restorex = lastx;
        restorey = lasty;
        lastx = x + deltax;
        lasty = y + deltay;
        if (lastx < minmousex) lastx = minmousex;
        else if (lastx > maxmousex) lastx = maxmousex;
        if (lasty < minmousey) lasty = minmousey;
        else if (lasty > maxmousey) lasty = maxmousey;
        deltax = lastx - x;
        deltay = lasty - y;        
        WaitBOVP (&(picscr->ViewPort));
        RestorePuzBM (wbm, restorex, restorey, &bgbm);
        SavePuzBM (wbm, lastx, lasty, &bgbm);
        CopyFromBMToBM (&rotbm, 0, 0, wbm, lastx, lasty, puzpw, puzph);
	Delay (2L);
    };

    Wait (1L << win->UserPort->mp_SigBit);

  }

}


/* Gadgets to get the difficulty from the user... 
*/

#define GADWIDTH  120
#define GADHEIGHT 12
#define GADX      400
#define GADY      300
#define NUMGADS   4

static struct Gadget pzgad[NUMGADS];

static struct Gadget samplegad = {
  NULL, GADX, 0, GADWIDTH, GADHEIGHT,
  GADGHBOX, RELVERIFY, BOOLGADGET, NULL, NULL, NULL, 0L, NULL, 0, NULL};

static char *gadtext[NUMGADS] = {
  "Real easy", "Not so easy", "Difficult", "Quit"};
 
int GetDifficulty ()
{
  int cnt;
  struct IntuiMessage *msg;

  SetAPen (wrp, (long)nonzerocolor);

  Move (wrp, (long)GADX-8, (long)GADY-8);
  Text (wrp, "Select difficulty level:", 24L);

  for (cnt = 0; cnt < NUMGADS; cnt++) {
    pzgad[cnt] = samplegad;  /* Structure copy */
    pzgad[cnt].TopEdge = GADY+cnt*(GADHEIGHT+2)+(cnt == NUMGADS-1 ? 8 : 0);
    if (cnt != NUMGADS-1) {
      pzgad[cnt].GadgetID   = DIFF_EASY - cnt;
      pzgad[cnt].NextGadget = &pzgad[cnt+1];
    };
    Move (wrp, (long)pzgad[cnt].LeftEdge+10, (long)pzgad[cnt].TopEdge+8);
    Text (wrp, gadtext[cnt], (long)strlen(gadtext[cnt]));
  }

  AddGList (picwin, pzgad, -1L, -1L, NULL);
/*RefreshGList (picwin->FirstGadget, picwin, NULL, -1L);*/

  cnt = -1;
  while (true) {
    while (msg = (struct IntuiMessage *)GetMsg(picwin->UserPort)) {
      if (msg->Class == GADGETUP && msg->IAddress != NULL) 
        cnt = ((struct Gadget *)(msg->IAddress))->GadgetID;
      ReplyMsg (msg);
      if (cnt != -1) {
         RemoveGList (picwin, pzgad, -1L);
         return (cnt); 
      }
    }
    Wait (1L << picwin->UserPort->mp_SigBit);
  }
}

 
    
