/****************************************************************************

    Keeper -- written by Ray Lambert for Theta Systems, Inc.

    Copyright 1991 Commodore Business Machines, Inc.

    Purpose and Usage:

        Keeper is meant for use in a startup-sequence file to display a
    picture while booting.  Some applications are quite large and take a
    significant period of time to load.  Unless special steps are taken,
    an application such as this cannot display anything for the user to look
    at until the executable file is completely loaded.  This keeps the user
    waiting and the inactivity makes the load time seem even longer.  This
    practice is not acceptable for a platform such as CDTV were the task of
    keeping the user's attention and preventing the user from becoming bored
    is considered paramount.  Keeper was written to provide a solution to
    this problem.  Keeper should be invoked from your startup-sequence file
    (in the foreground: do not use RUN) with the name of an IFF.ILBM picture
    to be displayed.  Keeper loads the picture and then spawns off a tiny
    task which displays the picture and frees all the resources that are used
    to do so when it is time to remove the picture.  Keeper quits shortly
    after these things are done allowing the remainder of the startup-sequence
    to be executed.  Because keeper runs in the foreground while it is loading
    the picture, there will not be a problem with two processes trying to read
    from the same disk at once (this would slow the boot process considerably).
    Note that this can still occur however if a program gets run in the
    background before keeper is run, and that program reads from the disk
    (this should be avoided).  There are three ways to make keeper remove its
    picture.  First, if keeper is run a second time with the keyword 'QUIT' on
    its command line, keeper will cause a previous copy of itself to terminate.
    Second (and easier), the simple act of loading another View will cause
    keeper to terminate.  Keeper accomplishes this by periodically checking
    the GfxBase->ActiView variable to see if its View has been replaced.  Note
    that because of this it is dangerous for a program to save the active
    View during initialization and then try to restore it later.  Please use
    CloseWorkBench() and OpenWorkBench() instead if possible.  A third method
    of terminating keeper is by Signal()'ing it directly.  Keeper's task can
    be located like this:

                       task = FindTask("PicKeeper");

    and terminated like this:

                       Signal(task,SIGBREAKF_CTRL_C);

    Keeper can display any IFF picture including overscan.  Keeper
    automatically compensates for PAL by centering its View appropriately.
    Keeper CANNOT display pictures that require custom copper list code such
    as Dynamic HAM pics.  Keeper reads the CDTV preferences and uses the saved
    screen centering information found there.  If the preferences have not
    been set or are not available (not running on a baby) the centering data
    gets stolen from Intuition's View (actually it is the active View at the
    time keeper runs).

****************************************************************************/

#include <stdio.h>
#include "keeper.h"
#include <exec/memory.h>
#include <graphics/gfxbase.h>
#include <graphics/view.h>
#include <proto/exec.h>
#include <proto/graphics.h>
#include <proto/dos.h>
#include "cdtvprefs.h"

extern int keeper(struct View *, struct MemList *);

struct GfxBase *GfxBase=0;
struct Library *IntuitionBase=0;
struct View *v=0;
struct ViewPort *vp=0;
struct RasInfo *ri=0;
struct BitMap *bm=0;
struct ColorMap *cm=0;
struct BLITmap *pic=0;
struct MemList *ml=0;
BOOL vpmade = FALSE;
struct CDTVPrefs prefs;


ULONG mlsize;

struct MemList *BuildMemList(void)
{
  register struct MemList *ml;
  register struct MemEntry *me;
  register UWORD cnt;
  ULONG size;

  cnt = ((pic->d + 4) - 1);
  mlsize = (sizeof(struct MemList) + (cnt * sizeof(struct MemEntry)));
  unless( ml = (struct MemList *)AllocMem(mlsize,MEMF_PUBLIC|MEMF_CLEAR) )
    return(0);
  ml->ml_NumEntries = (cnt + 1);
  me = &ml->ml_ME[0];
/** include all of the blitplanes **/
  size = RASSIZE(pic->w,pic->h);
  for(cnt = 0; cnt < pic->d; cnt++)
    {
      me->me_Addr = (APTR)pic->p[cnt];
      me->me_Length = size;
      me++;
    }
/** include the View struct **/
  me->me_Addr = (APTR)v;
  me->me_Length = sizeof(struct View);
  me++;
/** include the ViewPort struct **/
  me->me_Addr = (APTR)vp;
  me->me_Length = sizeof(struct ViewPort);
  me++;
/** include the RasInfo struct **/
  me->me_Addr = (APTR)ri;
  me->me_Length = sizeof(struct RasInfo);
  me++;
/** include the BitMap struct **/
  me->me_Addr = (APTR)bm;
  me->me_Length = sizeof(struct BitMap);

  return(ml);
}


void say(char *msg)
{
  static BPTR console;
  unless( console ) unless( console = Output() ) return;
  Write(console,msg,strlen(msg));
}


void xit(char *msg, int rc)
{
  if (ml) FreeMem(ml,mlsize);
  if (pic) unloadILBM(pic);
  if (cm) FreeColorMap(cm);
  if (bm) FreeMem(bm,sizeof(struct BitMap));
  if (ri) FreeMem(ri,sizeof(struct RasInfo));
  if (vpmade)
    {
      if (v->LOFCprList) FreeCprList(v->LOFCprList);
      if (v->SHFCprList) FreeCprList(v->SHFCprList);
      FreeVPortCopLists(vp);
    }
  if (vp) FreeMem(vp,sizeof(struct ViewPort));
  if (v) FreeMem(v,sizeof(struct View));
  if (GfxBase) CloseLibrary(GfxBase);
  if (IntuitionBase) CloseLibrary(IntuitionBase);
  if (msg) say(msg);
  exit(rc);
}


#if DBG
struct BitDefs
{
  ULONG bd_bit;
  ULONG bd_mask;
  char *bd_text;
}
mode_bits[] =
{
  { (ULONG)HIRES,           (ULONG)HIRES,           "HIRES"           },
  { (ULONG)LACE,            (ULONG)LACE,            "LACE"            },
  { (ULONG)HAM,             (ULONG)HAM,             "HAM"             },
  { (ULONG)EXTRA_HALFBRITE, (ULONG)EXTRA_HALFBRITE, "EXTRA_HALFBRITE" },
  { (ULONG)SPRITES,         (ULONG)SPRITES,         "SPRITES"         },
  { (ULONG)DUALPF,          (ULONG)DUALPF,          "DUALPF"          },
  { (ULONG)PFBA,            (ULONG)PFBA,            "PFBA"            },
  { (ULONG)VP_HIDE,         (ULONG)VP_HIDE,         "VP_HIDE"         },
  { (ULONG)GENLOCK_AUDIO,   (ULONG)GENLOCK_AUDIO,   "GENLOCK_AUDIO"   },
  { (ULONG)GENLOCK_VIDEO,   (ULONG)GENLOCK_VIDEO,   "GENLOCK_VIDEO"   },
  { 0,                      0,                      0,                }
};

char *ShowBitDefs(char *buffer, ULONG fbits, struct BitDefs *bd)
{
  register char *buf, *p;
  BOOL flag = FALSE;  /* flag to output a "|" or not */
  if (fbits == NULL)
    {
      strcpy(buffer,"NULL");
      return(buffer);
    }
  buf = buffer;
  while(bd->bd_text != NULL)
    {
      if ((fbits & bd->bd_mask) == bd->bd_bit)
        {
          if (flag)
            *buf++ = '|';
          else
            flag = TRUE;
          for(p = bd->bd_text; *p; p++, buf++) *buf = *p;
        }
      bd++;
    }
  if (!flag)
    {
      sprintf(buffer,"0x%08X",fbits);
      return(buffer);
    }
  *buf = 0;
  return(buffer);
}


void showpicinfo(void)
{
  char s[200];
  sprintf(s,"Picture:  %hd X %hd X %hd, ",pic->w,pic->h,pic->d);
  say(s);
  ShowBitDefs(s,(pic->modes&(HIRES|LACE|HAM|EXTRA_HALFBRITE)),mode_bits);
  say(s);
  sprintf(s,"\nViewPort: %hd,%hd,%hd,%hd\n",v->DxOffset,v->DyOffset,vp->DWidth,vp->DHeight);
  say(s);
}
#endif


main(int argc, char *argv[])
{
  short normal, picture, maximum, i;

  unless( argc == 2 ) xit("Usage: keeper <picname | QUIT>\n",5);
  if (stricmp(argv[1],"QUIT") == 0)
    {
      register struct Task *t;
      if ( t = FindTask("PicKeeper") ) Signal(t,SIGBREAKF_CTRL_C);
      xit(0,0);
    }
  unless( IntuitionBase = OpenLibrary("intuition.library",0) ) xit(0,255);
  unless( GfxBase = (struct GfxBase *)OpenLibrary("graphics.library",0) )
    xit(0,255);
  unless( v = (struct View *)AllocMem(sizeof(struct View),MEMF_PUBLIC|MEMF_CLEAR) )
    xit("No memory for View\n",21);
  unless( vp = (struct ViewPort *)AllocMem(sizeof(struct ViewPort),MEMF_PUBLIC|MEMF_CLEAR) )
    xit("No memory for ViewPort\n",21);
  unless( ri = (struct RasInfo *)AllocMem(sizeof(struct RasInfo),MEMF_PUBLIC|MEMF_CLEAR) )
    xit("No memory for RasInfo\n",21);
  unless( bm = (struct BitMap *)AllocMem(sizeof(struct BitMap),MEMF_PUBLIC|MEMF_CLEAR) )
    xit("No memory for BitMap\n",21);
  unless( cm = GetColorMap(32) ) xit("No memory for ColorMap\n",21);
  unless( pic = loadILBM(argv[1]) ) xit("Unable to load picture\n",21);
  InitView(v);
  InitVPort(vp);
  v->ViewPort = vp;
  vp->RasInfo = ri;
  ri->BitMap = bm;
  BLITmap2BitMap(pic,bm);

  vp->ColorMap = cm;
  LoadRGB4(vp,pic->cmap,(1 << pic->d));

  maximum = 368;
  if (pic->modes & HIRES) maximum *= 2;
  vp->DWidth = min(pic->w,maximum);

  maximum = (GfxBase->DisplayFlags & PAL) ? 283 : 241;
  if (pic->modes & LACE) maximum *= 2;
  vp->DHeight = min(pic->h,maximum);

  vp->Modes = (pic->modes & (HIRES|LACE|HAM|EXTRA_HALFBRITE));
  v->Modes = (pic->modes & LACE);

  ReadPrefs(&prefs);

  normal = GfxBase->NormalDisplayColumns;
  if (normal >= 640) normal /= 2;       /* adjust to LORES pixels */
  picture = vp->DWidth;
  if (vp->Modes & HIRES) picture /= 2;  /* ditto                  */
  if (picture <= normal)
    v->DxOffset = (prefs.DisplayX + ( (normal - picture) / 2));
  else
    v->DxOffset = (prefs.DisplayX - ( (picture - normal) / 2));
/* correct X offset to prevent edge jitters (must be on 8 bit boundary) */
  i = (v->DxOffset % 8);
  if (i > 4)
    {
      v->DxOffset += (8 - i);
    }
  else
    {
      v->DxOffset -= i;
    }

  normal = GfxBase->NormalDisplayRows;
  if (normal >= 400) normal /= 2;       /* adjust to LORES pixels */
  picture = vp->DHeight;
  if (vp->Modes & LACE) picture /= 2;   /* ditto                  */
  if (picture <= normal)
    v->DyOffset = (prefs.DisplayY + ( (normal - picture) / 2));
  else
    v->DyOffset = (prefs.DisplayY - ( (picture - normal) / 2));

  MakeVPort(v,vp);
  MrgCop(v);
  vpmade = TRUE;

#if DBG
  showpicinfo();
#endif

  unless( ml = BuildMemList() ) xit("Couldn't create MemList\n",10);
  unless( keeper(v,ml) ) xit("Couldn't create task\n",10);

/* keeper will close all of these... */
  vpmade = FALSE;
  v = 0;
  vp = 0;
  ri = 0;
  bm = 0;
  cm = 0;
  pic->d = 0; /* keeper keeps the bit planes */
  ml = 0;
  GfxBase = 0;
  IntuitionBase = 0;
  xit(0,0);
}
