/*
 * Display.c
 *   Read an ILBM file and display as a screen/window until closed.
 *   Simulated close gadget in upper left corner of window.
 *   Clicking below title bar area toggles screen bar for dragging.
 *   Handles normal and HAM ILBM's
 *   Does automatic color cycling (see note below)
 *   Accepts optional 2nd CLI arg for display time in seconds
 *
 *  By Carolyn Scheppner   CBM  03/15/86
 *
 *  Modified 09/02/86 - Only global frame is iFrame
 *                      Use message->MouseX and Y
 *                      Wait() for IDCMP
 *  Modified 10/15/86 - For HAM
 *                      Name changed from SeeILBM to ViewILBM
 *  Modified 11/01/86 - Revised for linkage with myreadpict.c
 *  Modified 11/18/86 - For Astartup ... Amiga.lib, LC.lib linkage
 *  Modified 12/12/86 - Added color cycling at request of Mimetics
 *  Modified 01/06/87 - Tab toggles cycling
 *  Modified 03/03/87 - Recognizes RNG_NORATE (36) as non-active DP CRNG
 *                      Changed name to Display
 *  Modified 03/13/87 - Accepts display time in seconds as 2nd CLI arg
 *
 *     This viewer automatically cycles any ILBM that contains cycling
 *     chunks (CCRT or CRNG) which are marked as active and do not
 *     have a CRNG cycle rate of 36. (To DPaint, rate 36 = don't cycle)
 *     Note that by default, DPaint saves its pics with CRNG (cycling)
 *     chunks flagged as active and with a rate not equal to 36.
 *     This can cause this viewer to cycle DPaint pics which were
 *     not meant to be cycled.  To de-activate the CRNG chunks in
 *     a DPaint pic, either resave the pic after setting the cycle
 *     speed for each range to the lowest position, or use the
 *     "uncycle" program. (usage:  uncycle DPaintPicName)
 *
 *
 * Based on ShowILBM.c, readpict.c    1/86
 *  By Jerry Morrison, Steve Shaw, and Steve Hayes, Electronic Arts.
 *  This software is in the public domain.
 *
 * >>NOTE<<: This example must be linked with additional IFF rtn files.
 *           See linkage information below.
 *
 * The display portion is specific to the Commodore Amiga computer.
 *
 * Linkage Information:
 *  (NOTE: All modules including iff stuff compiled with -v on LC2)
 *
 * FROM     AStartup.obj,Display.o,myreadpict.o,iffr.o,ilbmr.o,unpacker.o
 * TO       Display
 * LIBRARY  Amiga.lib, LC.lib
 * 
 */

#include <exec/types.h>
#include <exec/memory.h>
#include <exec/tasks.h>
#include <libraries/dos.h>
#include <libraries/dosextens.h>
#include <workbench/startup.h>
#include <intuition/intuition.h>
#include <graphics/gfxbase.h>

#include "iff/ilbm.h"
#include "myreadpict.h"


/* For wbStdio rtns */
extern LONG stdin, stdout, stderr;  /* in Astartup.obj */
char conSpec[] = "CON:0/40/640/140/";
BOOL wbHasStdio = NULL;


/* general usage pointers */
struct GfxBase       *GfxBase;
struct IntuitionBase *IntuitionBase;
struct IntuiMessage  *message;

/* Globals for displaying an image */
struct Screen   *screen1;
struct Window   *window1;
struct RastPort *rport1;
struct ViewPort *vport1;

struct BitMap   tBitMap;      /* Temp BitMap struct for small pics  */

/* For WorkBench startup */    
extern struct WBStartup *WBenchMsg;
BOOL   fromWB;
struct FileLock *startLock, *newLock;

/* Other globals */
int   i, error;
BYTE  c;
BOOL  TBtoggle, Done;
ULONG signals, wSig, class, code, pBytes;
SHORT mouseX, mouseY;

char u1[]  = "\n>>> Display <<<   v1  C. Scheppner  CBM  03/87\n";
char u2a[] = "\nUsage: Display ilbmfile [time]\n";
char u2b[] = "\nUsage: Click this icon, SHIFT and DoubleClick on pic\n";
char u3[]  = "Click toggles bar, Tab toggles cycling, Close upper left\n";

/* Structures for new Screen, new Window */

struct   TextAttr       TextFont = {
   "topaz.font",                    /* Font Name   */
   TOPAZ_EIGHTY,                    /* Font Height */
   FS_NORMAL,                       /* Style       */
   FPF_ROMFONT,                     /* Preferences */
   };

struct   NewScreen      ns = {
   0, 0,                                  /* LeftEdge and TopEdge   */
   0, 0,                                  /* Width and Height       */
   0,                                     /* Depth                  */
   1, 0,                                  /* DetailPen and BlockPen */
   NULL,                                  /* Special display modes  */
   CUSTOMSCREEN,                          /* Screen Type            */
   &TextFont,                             /* Use my font            */
   " <- Close here after clicking below",                  /* Title */
   NULL,                                  /* No gadgets yet         */
   NULL,                                  /* Ptr to CustomBitmap    */
   };

struct   NewWindow      nw = {
   0, 0,                                  /* LeftEdge and TopEdge */
   0, 0,                                  /* Width and Height */
   -1, -1,                                /* DetailPen and BlockPen */
   MOUSEBUTTONS|VANILLAKEY,               /* IDCMP Flags */
   ACTIVATE
   |BACKDROP
   |BORDERLESS,                           /* Flags */
   NULL, NULL,                            /* Gadget and Image pointers */
   NULL,                                  /* Title string */
   NULL,                                  /* Put Screen ptr here */
   NULL,                                  /* SuperBitMap pointer */
   0, 0,                                  /* MinWidth and MinHeight */
   0, 0,                                  /* MaxWidth and MaxHeight */
   CUSTOMSCREEN,                          /* Type of window */
   };

USHORT  allBlack[maxColorReg] = {0};

/* For alloc to define new pointer */
#define PDATASZ 12
UWORD   *pdata;

#ifndef MIN
#define MIN(a,b) ((a)<(b)?(a):(b))
#endif  MIN

extern char *IFFPMessages[];
ILBMFrame   iFrame;       /* my global frame */


/* Cycle Task stuff */
#define CYCLETIME  16384L
#define REVERSE    0x02
#define ACTIVE     0x01

extern VOID cycleTask();
char *cyTaskName = "v2cyTask";
struct Task *cyTask;

/* Data shared with cycleTask */
CrngChunk       *cyCrngs;
struct ViewPort *cyVport;
int    cyRegs, cyCnt;
USHORT cyMap[maxColorReg];
LONG   cyClocks[maxCycles];
LONG   cyRates[maxCycles];
BOOL   CycleOn, PrepareToDie;

/* For optional time delay */
struct Task *mainTask;
BOOL   TimerOn;
LONG   tSigNum = -1;
ULONG  tSig, dTimer;


/*****************************************************************/
main(argc, argv)
int argc;
char **argv;
   {
   LONG            file;
   IFFP            iffp = NO_FILE;
   struct WBArg    *arg;  
   char            *filename;

   fromWB = (argc==0) ? TRUE : FALSE;
   TimerOn = FALSE;

   if((argc>1)&&(*argv[1] != '?'))  /* Passed filename via command line  */
      {
      filename = argv[1];
      if(argc==3)
         {
         TimerOn = TRUE;
         dTimer = 60 * atoi(argv[2]);
         }
      }
   else if ((argc==0)&&(WBenchMsg->sm_NumArgs > 1))
      {                        /* Passed filename via  WorkBench */
      arg = WBenchMsg->sm_ArgList;
      arg++;
      filename   = (char *)arg->wa_Name;
      newLock    = (struct FileLock *)arg->wa_Lock;
      startLock  = (struct FileLock *)CurrentDir(newLock);
      }
   else            /* From WB or CLI, no filename or ? */
      {
      usage();
      cleanexit(" "); /* Space forces my wait for keypress on WB exit */
      }

   if(!(GfxBase = (struct GfxBase *)OpenLibrary("graphics.library",0)))
      cleanexit("Can't open graphics");

   if(!(IntuitionBase=
          (struct IntuitionBase *)OpenLibrary("intuition.library",0)))
      cleanexit("Can't open intuition");

   if(!(file = Open(filename, MODE_OLDFILE)))
      cleanexit("Picture file not found");

   iffp = myReadPicture(file,&iFrame);
   Close(file);

   if (!(iffp == IFF_DONE))
      cleanexit("Not an IFF ILBM");


   error = DisplayPic(&iFrame);
   if(error)  cleanexit("Can't open screen or window");

   if(pdata = (UWORD *)AllocMem(PDATASZ,MEMF_CHIP|MEMF_CLEAR))
      {
      pdata[2] = 0x8000;  /* 1 pixel */
      SetPointer(window1,pdata,1,16,0,0);
      }

   /* Set up cycle/timer task */

   mainTask = (struct Task *)FindTask(NULL);
   if((tSigNum = AllocSignal(-1)) == -1)  cleanexit("Can't alloc timerSig");
   tSig = 1 << tSigNum;

   initCycle(&iFrame,vport1);
   cyTask = (struct Task *)CreateTask(cyTaskName,0,cycleTask,4000);
   if(!cyTask)  cleanexit("Can't create cycling task");
   CycleOn = TRUE;

   TBtoggle   = FALSE;      /* Title bar toggle */
   Done       = FALSE;      /* Close flag       */

   wSig = 1<<window1->UserPort->mp_SigBit;
   tSig = 1<<tSigNum;

   while (!Done)
      {
      signals = Wait(wSig|tSig);
      if (signals & wSig)  chkmsg();
      if (signals & tSig)  Done = TRUE;
      }
   cleanup();
   }


initCycle(ptFrame,vp)
ILBMFrame *ptFrame;
struct ViewPort *vp;
   {
   int k;

   CycleOn  = FALSE;
   PrepareToDie = FALSE;
   cyCrngs  = ptFrame->crngChunks;
   cyVport  = vp;
   cyRegs   = ptFrame->nColorRegs;
   cyCnt    = ptFrame->cycleCnt;

   for(k=0; k<cyRegs; k++)
      {
      cyMap[k] = ptFrame->colorMap[k];
      }

   /* Init Rates and Clocks */
   for(k=0; k<cyCnt; k++)
      {
      /* In DPaint CRNG, rate = RNG_NORATE (36) means don't cycle */
      if(cyCrngs[k].rate == RNG_NORATE)
         {
         cyCrngs[k].rate = 0;
         cyCrngs[k].active &= ~ACTIVE;
         }

      if((cyCrngs[k].active & ACTIVE)&&(cyCrngs[k].rate))
         {
         cyRates[k] = cyCrngs[k].rate;
         }
      else
         {
         cyRates[k] = 0;  /* Means don't cycle to my cycleTask */
         }
      cyClocks[k] = 0;
      }
   }


VOID cycleTask()
   {
   int    k, i, j;
   UBYTE  low, high;
   USHORT cyTmp;
   BOOL   Cycled;

   while(!PrepareToDie)
      {
      WaitTOF();
      if(CycleOn)
         {
         Cycled = FALSE;
         for(k=0; k<cyCnt; k++)
            {
            if(cyRates[k])  /* cyRate 0 = inactive */
               {
               cyClocks[k] += cyRates[k];
               if(cyClocks[k] >= CYCLETIME)
                  {
                  Cycled = TRUE;
                  cyClocks[k] -= CYCLETIME;
                  low = cyCrngs[k].low;
                  high= cyCrngs[k].high;
                  if(cyCrngs[k].active & REVERSE)  /* Reverse cycle */
                     {
                     cyTmp = cyMap[low];
                     for(i=low,j=low+1; i < high; i++,j++)
                        {
                        cyMap[i] = cyMap[j];
                        }
                     cyMap[high] = cyTmp;
                     }
                  else     /* Forward cycle */
                     {
                     cyTmp = cyMap[high];
                     for(i=high,j=high-1; i > low; i--,j--)
                        {
                        cyMap[i] = cyMap[j];
                        }
                     cyMap[low] = cyTmp;
                     }
                  }
               }
            }
         if(Cycled)
            {
            LoadRGB4(cyVport,cyMap,cyRegs);
            }
         }
      if(TimerOn)
         {
         if(--dTimer == 0)  Signal(mainTask,tSig);
         }
      }
   PrepareToDie = FALSE;
   while(TRUE);  /* Busy wait to die */
   }


chkmsg()
   {
   while(message=(struct IntuiMessage *)GetMsg(window1->UserPort))
      {
      class = message->Class;
      code  = message->Code;
      mouseX = message->MouseX;
      mouseY = message->MouseY;

      ReplyMsg(message);
      switch(class)
         {
         case MOUSEBUTTONS:
            if ((code == SELECTDOWN)&&
                  (mouseX < 10)&&(mouseY<10))
               {
               Done = TRUE;
               }
            else if ((code == SELECTDOWN)&&
                       ((mouseY>10)||(mouseX>10))&&
                          (TBtoggle==FALSE))
               {
               TBtoggle = TRUE;
               ShowTitle(screen1,TRUE);
               ClearPointer(window1);
               }
            else if ((code == SELECTDOWN)&&
                       (mouseY>10)&&(TBtoggle==TRUE))
               {
               TBtoggle = FALSE;
               ShowTitle(screen1,FALSE);
               SetPointer(window1,pdata,1,16,0,0);
               }
            break;
         case VANILLAKEY:
            if(code==0x09)  /* Tab toggles Cycle */
               {
               if(CycleOn)
                  {
                  CycleOn = FALSE;
                  WaitTOF();        /* Make sure cyTask saw FALSE */
                  WaitBOVP(vport1);
                  LoadRGB4(vport1,iFrame.colorMap,maxColorReg);
                  }
               else
                  {
                  initCycle(&iFrame,vport1);
                  CycleOn = TRUE;
                  }
               }
            break;
         default:
            break;
         }
      }
   }


usage()
   {
   if ((fromWB)&&(! wbHasStdio))  wbHasStdio = openStdio(conSpec);

   if ((!fromWB)||(wbHasStdio))
      {
      Write(stdout,u1,strlen(u1));
      if(!fromWB)  Write(stdout,u2a,strlen(u2a));
      else Write(stdout,u2b,strlen(u2b));
      Write(stdout,u3,strlen(u3));
      }
   }

cleanexit(s)
   char  *s;
   {
   if (*s)
      {
      if ((fromWB)&&(! wbHasStdio))  wbHasStdio = openStdio(conSpec);

      if ((!fromWB)||(wbHasStdio))
         {
         Write(stdout,s,strlen(s));
         Write(stdout,"\n",1);
         }
      if (wbHasStdio)
         {
         Write(stdout,"\nPRESS RETURN TO EXIT\n",22);
         while (getchar() != '\n');
         }
      }
   cleanup();
   if (wbHasStdio) closeStdio();
   exit();
   }


cleanup()
   {
   if(cyTask)
      {
      CycleOn = FALSE;
      PrepareToDie = TRUE;
      while(PrepareToDie);
      DeleteTask(cyTask);
      }

   /* Free timer signal */
   if (tSigNum > -1)  FreeSignal(tSigNum);

   /* Note - tBitMap planes were deallocated in DisplayPic() */
   if (window1)
      {
      while(message=(struct IntuiMessage *)GetMsg(window1->UserPort))
         {
         ReplyMsg(message);
         }
      CloseWindow(window1);
      }
   if (screen1) CloseScreen(screen1);
   if (pdata)   FreeMem(pdata,PDATASZ);
   if (IntuitionBase) CloseLibrary(IntuitionBase);
   if (GfxBase)       CloseLibrary(GfxBase);
   if (newLock != startLock)  CurrentDir(startLock);
   }


strlen(s)
char *s;
   {
   int i = 0;
   while(*s++) i++;
   return(i);
   }



/** getBitMap() *********************************************************
 *
 * Open screen or temp bitmap.
 *   Returns ptr destBitMap  or  0 = error
 *
 *************************************************************************/
struct BitMap *getBitMap(ptilbmFrame)
   ILBMFrame *ptilbmFrame;
   {
   int     i, nPlanes, plsize;
   SHORT  sWidth, sHeight, dWidth, dHeight;
   struct BitMap *destBitMap;

   sWidth  = ptilbmFrame->bmHdr.w;
   sHeight = ptilbmFrame->bmHdr.h;
   dWidth  = ptilbmFrame->bmHdr.pageWidth;
   dHeight = ptilbmFrame->bmHdr.pageHeight;
   nPlanes = MIN(ptilbmFrame->bmHdr.nPlanes, EXDepth);

   ns.Width  = dWidth;
   ns.Height = dHeight;
   ns.Depth  = nPlanes;

   if (ptilbmFrame->foundCAMG)
      {
      ns.ViewModes = ptilbmFrame->camgChunk.ViewModes;
      }
   else
      {
      if (ptilbmFrame->bmHdr.pageWidth <= 320)
         ns.ViewModes = 0;
      else
         ns.ViewModes = HIRES;

      if (ptilbmFrame->bmHdr.pageHeight > 256)
         ns.ViewModes |= LACE;
      }

   if ((screen1 = (struct Screen *)OpenScreen(&ns))==NULL)    return(0);

   vport1 = &screen1->ViewPort;
   LoadRGB4(vport1, &allBlack[0], ptilbmFrame->nColorRegs);

   if((ptilbmFrame->camgChunk.ViewModes)&(HAM))  setHam(screen1,FALSE);

   nw.Width  = dWidth;
   nw.Height = dHeight;
   nw.Screen = screen1;

   if ((window1 = (struct Window *)OpenWindow(&nw))==NULL)
      {
      CloseScreen(screen1);
      screen1 = NULL;
      return(0);
      }

   ShowTitle(screen1, FALSE);

   if ((sWidth == dWidth) && (sHeight == dHeight))
      {
      destBitMap = (struct BitMap *)screen1->RastPort.BitMap;
      }
   else
      {
      InitBitMap( &tBitMap,
                  nPlanes,
                  sWidth,
                  sHeight);

      plsize = RowBytes(ptilbmFrame->bmHdr.w) * ptilbmFrame->bmHdr.h;
      if (tBitMap.Planes[0] =
       (PLANEPTR)AllocMem(nPlanes * plsize, MEMF_CHIP))
         {
         for (i = 1; i < nPlanes; i++)
            tBitMap.Planes[i] = (PLANEPTR)tBitMap.Planes[0] + plsize*i;
         destBitMap = &tBitMap;
         }
      else
         {
         CloseWindow(window1);
         window1 = NULL;
         CloseScreen(screen1);
         screen1 = NULL;
         return(0);  /* can't allocate temp BitMap */
         }
      }
   return(destBitMap);          /* destBitMap allocated */
   }


/** DisplayPic() *********************************************************
 *
 * Display loaded bitmap.  If tBitMap, first transfer to screen.
 *
 *************************************************************************/
DisplayPic(ptilbmFrame)
   ILBMFrame *ptilbmFrame;
   {
   int    i, row, byte, nrows, nbytes;
   struct BitMap  *tbp, *sbp; /* temp and screen BitMap ptrs */
   UBYTE  *tpp, *spp;         /* temp and screen plane ptrs  */

   if (tBitMap.Planes[0])     /* transfer from tBitMap if nec. */
      {
      tbp = &tBitMap;
      sbp = screen1->RastPort.BitMap;
      nrows  = MIN(tbp->Rows, sbp->Rows);
      nbytes = MIN(tbp->BytesPerRow, sbp->BytesPerRow);

      for (i = 0; i < sbp->Depth; i++)
         {
         tpp = (UBYTE *)tbp->Planes[i];
         spp = (UBYTE *)sbp->Planes[i];
         for (row = 0; row < nrows; row++)
            {
            tpp = tbp->Planes[i] + (row * tbp->BytesPerRow);
            spp = sbp->Planes[i] + (row * sbp->BytesPerRow);
            for (byte = 0; byte < nbytes; byte++)
               {
               *spp++ = *tpp++;
               }
            }
         }
      /*  Can now deallocate the temp BitMap  */
      FreeMem(tBitMap.Planes[0],
                 tBitMap.BytesPerRow * tBitMap.Rows * tBitMap.Depth);
      }

   vport1 = &screen1->ViewPort;
   LoadRGB4(vport1, ptilbmFrame->colorMap, ptilbmFrame->nColorRegs);
   if((ptilbmFrame->camgChunk.ViewModes)&(HAM))  setHam(screen1,TRUE);

   return(0);
   }


/* setHam --- For toggling HAM so HAM pic invisible while loading */
setHam(scr,toggle)
struct Screen *scr;
BOOL   toggle;
   {
   struct ViewPort *vp;
   struct View     *v;

   vp = &(scr->ViewPort);
   v = (struct View *)ViewAddress();
   Forbid();
   if(toggle)
      {
      v->Modes  |= HAM;
      vp->Modes |= HAM;
      }
   else
      {
      v->Modes  &= ~HAM;
      vp->Modes &= ~HAM;
      }
   MakeScreen(scr);
   RethinkDisplay();
   Permit();
   }


/* wbStdio.c --- Open an Amiga stdio window under workbench
 *               For use with AStartup.obj
 */

openStdio(conspec)
char *conspec;
   {
   LONG wfile;
   struct Process *proc;
   struct FileHandle *handle;

   if (wbHasStdio)  return(1);

   if (!(wfile = Open(conspec,MODE_NEWFILE)))  return(0);
   stdin  = wfile;
   stdout = wfile;
   stderr = wfile;
   handle = (struct FileHandle *)(wfile << 2);
   proc = (struct Process *)FindTask(NULL);

   proc->pr_ConsoleTask = (APTR)(handle->fh_Type);
   proc->pr_CIS = (BPTR)stdin;
   proc->pr_COS = (BPTR)stdout;
   return(1);
   }

closeStdio()
   {
   struct Process *proc;
   struct FileHandle *handle;

   if (! wbHasStdio) return(0);

   if (stdin > 0)  Close(stdin);
   stdin  = -1;
   stdout = -1;
   stderr = -1;
   handle = (struct FileHandle *)(stdin << 2);
   proc = (struct Process *)FindTask(NULL);
   proc->pr_ConsoleTask = NULL;
   proc->pr_CIS = NULL;
   proc->pr_COS = NULL;
   wbHasStdio = NULL;
   }

