
#include <exec/memory.h>
#include <exec/execbase.h>
#include <graphics/gfxbase.h>
#include <intuition/intuitionbase.h>
#include <libraries/iffparse.h>
#include <utility/tagitem.h>
#include <cybergraphics/cybergraphics.h>

#define __USE_SYSBASE 42

#include <proto/cybergraphics.h>
#include <proto/exec.h>
#include <proto/graphics.h>
#include <proto/intuition.h>
#include <proto/utility.h>

#include <string.h>

#include "btd.h"

struct GfxBase *GfxBase;
struct IntuitionBase *IntuitionBase;
struct Library *UtilityBase,*CyberGfxBase;

#define TPTAG(o) (BTD_Client+(o))

#define TP_Toasters TPTAG(0)

#define MAX_TOASTERS  20L
#define DEF_TOASTERS   2L

#define NUM_IMAGES 4

#define IMAGE_WIDTH  64
#define IMAGE_HEIGHT 64
#define IMAGE_WIDBUF (IMAGE_WIDTH+20)
#define IMAGE_HEIBUF (IMAGE_HEIGHT+20)

extern UBYTE *BytePackedFrames[];

#define ABS(x)   __builtin_abs(x)
#define MIN(a,b) __builtin_min(a,b)
#define MAX(a,b) __builtin_max(a,b)

#define NUM_COLORS 16
#define NUM_PHASES ((NUM_IMAGES*2)-2)

struct BTDInteger ToasterIntParams[] =
 {
  TP_Toasters,"Toasters",BTDPT_INTEGER,DEF_TOASTERS,1L,MAX_TOASTERS,TRUE,
 };

struct BTDNode *ToasterParams[] =
 {
  &ToasterIntParams[0].BI_Node,
  NULL
 };

struct BTDInfo ToasterInfo =
 {
  BTDI_Revision,MAKE_ID('T','O','S','T'),
  "Toaster","The Must of Blankers","Markus Illenseer and Matthias Scheler",
  ToasterParams
 };

struct IToaster
 {
  LONG delay;
  LONG x, y;
  LONG old_x,old_y;
  LONG xspeed,yspeed;
  LONG phase;
  LONG xcol,ycol;
 };

struct Toasters /* structure for a swarm, including the wasp */
 {
  struct BTDDrawInfo *BTDDrawInfo;
  WORD Width;   /* Width and */
  WORD Height;  /* Height of the used area */
  WORD NumToaster; /* total number of the Toasters*/
  struct IToaster *Toasters;
  struct ToasterImage *Images[NUM_PHASES];
  LONG RandN,RandF,RandI;
 };

UBYTE CMap[(NUM_COLORS-1)*3] =
 {
  0x33,0x33,0x33,0x55,0x55,0x55,0x88,0x55,0x22,0x66,0x66,0x66,0x77,0x77,0x44,
  0x55,0x55,0x66,0xEE,0x55,0x22,0x88,0x88,0x88,0x99,0x99,0x99,0xBB,0xBB,0xBB,
  0xCC,0xCC,0xCC,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xEE,0xEE,0xEE,0xFF,0xFF,0xFF
 };

struct ToasterImage
 {
  WORD ti_Width,ti_Height;
  struct BitMap ti_BitMap;
  UBYTE *ti_ChunkyData;
 };

/* library stuff */

char MyBlankerName[] = "toaster.btd";
char MyBlankerID[]   = "Toaster Blanker V" VERSION "." REVISION " for BTD";

LONG MyBlankerLibInit(void)

{
 if (GfxBase=(struct GfxBase *)OpenLibrary("graphics.library",37L))
  {
   if (IntuitionBase=(struct IntuitionBase *)OpenLibrary("intuition.library",37L))
    {
     if (UtilityBase=OpenLibrary("utility.library",37L))
      {
       CyberGfxBase=OpenLibrary("cybergraphics.library",40L);
       return TRUE;
      }

     CloseLibrary (&IntuitionBase->LibNode);
    }
   CloseLibrary (&GfxBase->LibNode);
  }
 return FALSE;
}

void MyBlankerLibFree(void)

{
 if (CyberGfxBase) CloseLibrary (CyberGfxBase);
 CloseLibrary (UtilityBase);
 CloseLibrary (&IntuitionBase->LibNode);
 CloseLibrary (&GfxBase->LibNode);
}

/* random generator */

void __regargs InitRandom(struct Toasters *TP,ULONG Instance)

{
 ULONG Time[2];

 CurrentTime (&Time[0],&Time[1]);
 TP->RandN=(LONG)Time[0];

 if (Time[1]<1024L) Time[1]|=1;
 else Time[1]>>=10;
 Time[1]^=Instance;

 TP->RandF=4*Time[1]+1;
 TP->RandI=2*Time[1]+1;
}

WORD __regargs Random(struct Toasters *TP,WORD Max)

{
 TP->RandN=TP->RandF*TP->RandN+TP->RandI;
 if (TP->RandN<0L) TP->RandN=-TP->RandN;

 return (WORD)(TP->RandN%Max);
}

/* implementation of library functions */

struct BTDInfo *QueryMyBlanker(void)

{
 return &ToasterInfo;
}

void __regargs DeleteToasterImage(struct ToasterImage *TI)

{
 if (TI->ti_ChunkyData) FreeVec (TI->ti_ChunkyData);
 else
  {
   ULONG Index,Size;

   Size=TI->ti_BitMap.BytesPerRow*TI->ti_BitMap.Rows;
   for (Index=0L; Index<TI->ti_BitMap.Depth; Index++)
    FreeMem (TI->ti_BitMap.Planes[Index],Size);
  }

 FreeVec (TI);
}

struct ToasterImage __regargs *ConvertToasterImage(UBYTE *From,UWORD *RemapTab,
                                                   struct RastPort *RastPort,LONG Chunky)

{
 struct ToasterImage *Target;

 if ((Target=AllocVec(sizeof(struct ToasterImage),MEMF_PUBLIC))==NULL) return NULL;

 Target->ti_Width=IMAGE_WIDTH;
 Target->ti_Height=IMAGE_HEIGHT;

 if (Chunky)
  {
   ULONG Pixels;

   Pixels=Target->ti_Width*Target->ti_Height;
   if (Target->ti_ChunkyData=AllocVec(Pixels,MEMF_PUBLIC))
    {
     UBYTE *To,Nibble;

     To=Target->ti_ChunkyData;
     while (Pixels)
      {
       Nibble=*From>>4;
       if (Nibble) *To++=RemapTab[Nibble];
       else *To++=BTD_BgPen;

       Nibble=(*From++)&15;
       if (Nibble) *To++=RemapTab[Nibble];
       else *To++=BTD_BgPen;

       Pixels-=2L;
      }
    }
   else
    {
     FreeVec (Target);
     Target=NULL;
    }
  }
 else
  {
   ULONG Index,Size;
   LONG Depth,X,Y;

   Target->ti_ChunkyData=NULL;

   Depth=1L;
   for (Index=1L; Index<NUM_COLORS; Index++)
    while (RemapTab[Index]>=(1L<<Depth)) Depth++;

   InitBitMap (&Target->ti_BitMap,Depth,Target->ti_Width,Target->ti_Height);

   Size=Target->ti_BitMap.BytesPerRow*Target->ti_BitMap.Rows;
   for (Index=0L; Index<Target->ti_BitMap.Depth; Index++)
    if ((Target->ti_BitMap.Planes[Index]=AllocMem(Size,MEMF_CHIP|MEMF_CLEAR))==NULL)
     {
      while (Index--) FreeMem (Target->ti_BitMap.Planes[Index],Size);
      FreeVec (Target);
      return NULL;
     }

   RastPort->BitMap=&Target->ti_BitMap;

   if (GfxBase->LibNode.lib_Version>=40)
    {
     ULONG Pixels;
     UBYTE *Data;

     Pixels=Target->ti_Width*Target->ti_Height;
     if (Data=AllocVec(Pixels,MEMF_PUBLIC))
      {
       UBYTE *To,Nibble;

       To=Data;
       while (Pixels)
        {
         Nibble=*From>>4;
         if (Nibble) *To++=RemapTab[Nibble];
         else *To++=BTD_BgPen;

         Nibble=(*From++)&15;
         if (Nibble) *To++=RemapTab[Nibble];
         else *To++=BTD_BgPen;

         Pixels-=2L;
        }
       WriteChunkyPixels (RastPort,0,0,Target->ti_Width-1,Target->ti_Height-1,Data,IMAGE_WIDTH);

       FreeVec (Data);
       return Target;
      }
    }

   for (Y=0L; Y<Target->ti_Height; Y++)
    for (X=0L; X<Target->ti_Width; X+=2)
     {
      UBYTE Nibble;

      Nibble=*From>>4;
      if (Nibble)
       {        SetAPen (RastPort,RemapTab[Nibble]);
        (void)WritePixel(RastPort,X,Y);
       }

      Nibble=(*From++)&15;
      if (Nibble)
       {        SetAPen (RastPort,RemapTab[Nibble]);
        (void)WritePixel(RastPort,X+1,Y);
       }
     } 
  } 

 return Target;
}

void __regargs SafeEraseRect(struct BTDDrawInfo *BTDDrawInfo,
                             LONG Left,LONG Top,
                             LONG Right,LONG Bottom)

{
 if ((Left<BTDDrawInfo->BDI_Width)&&(Right>=MAX(Left,0L))&&
     (Top<BTDDrawInfo->BDI_Height)&&(Bottom>=MAX(Top,0L)))
  EraseRect (BTDDrawInfo->BDI_RPort,
             BTDDrawInfo->BDI_Left+MAX(Left,0L),
             BTDDrawInfo->BDI_Top+MAX(Top,0L),
             BTDDrawInfo->BDI_Left+MIN(Right,BTDDrawInfo->BDI_Width-1),
             BTDDrawInfo->BDI_Top+MIN(Bottom,BTDDrawInfo->BDI_Height-1));
}

void __regargs DrawToasterImage(struct BTDDrawInfo *BTDDrawInfo,
                               struct ToasterImage *TI,
                               LONG XPos,LONG YPos)

{
 LONG Left,Top,Right,Bottom;

 Left=MAX(XPos,0L);
 Top=MAX(YPos,0L);
 Right=MIN(XPos+TI->ti_Width,BTDDrawInfo->BDI_Width);
 Bottom=MIN(YPos+TI->ti_Height,BTDDrawInfo->BDI_Height);

 if ((Left<Right)&&(Top<Bottom))
  if (TI->ti_ChunkyData)
   (void)WritePixelArray(TI->ti_ChunkyData,Left-XPos,Top-YPos,TI->ti_Width,
                         BTDDrawInfo->BDI_RPort,Left,Top,
                         Right-Left,Bottom-Top,RECTFMT_LUT8);
  else
   BltBitMapRastPort (&TI->ti_BitMap,Left-XPos,Top-YPos,
                      BTDDrawInfo->BDI_RPort,Left,Top,
                      Right-Left,Bottom-Top,0xC0);
}

LONG __regargs TestExtent(struct Toasters *TP,LONG Start,LONG Me,LONG x,LONG y)

{
 LONG Index;
  
 for (Index=Start; Index<TP->NumToaster; Index++)
  if ((Index!=Me)&&!TP->Toasters[Index].delay&&
      (ABS(x-TP->Toasters[Index].x)<IMAGE_WIDBUF)&&
      (ABS(y-TP->Toasters[Index].y)<IMAGE_HEIBUF)) return Index;
  
 return -1L;
}

BOOL __regargs FindLaunchPos(struct Toasters *TP,LONG Index)

{
 LONG x,y;
  
 x=TP->Width-IMAGE_WIDTH;
 y=IMAGE_HEIGHT+Random(TP,TP->Height);
  
 FOREVER
  {
   if (x<=(2*IMAGE_WIDTH)) return FALSE;
    
   if (TestExtent(TP,0,Index,x,y)==-1L)
    {
     TP->Toasters[Index].x=TP->Toasters[Index].old_x=x;
     TP->Toasters[Index].y=TP->Toasters[Index].old_y=y;
     TP->Toasters[Index].xspeed=Random(TP,3)+1;
     TP->Toasters[Index].yspeed=Random(TP,2)+1;
     TP->Toasters[Index].phase=Random(TP,NUM_IMAGES);

     return TRUE;
    }
    
   if (y>(-IMAGE_HEIGHT)) y-=IMAGE_HEIGHT;
   else x-=IMAGE_WIDTH;
  }
}

LONG __regargs ActXSpeed(struct Toasters *TP,LONG Index)

{
 if (TP->Toasters[Index].xcol==-1L) return TP->Toasters[Index].xspeed;
 else return ActXSpeed(TP,TP->Toasters[Index].xcol);
}

LONG __regargs ActYSpeed(struct Toasters *TP,LONG Index)

{
 if (TP->Toasters[Index].ycol==-1L) return TP->Toasters[Index].yspeed;
 else return ActXSpeed(TP,TP->Toasters[Index].ycol);
}

void EndMyBlanker(struct Toasters *TP)

{
 ULONG Index;

 for (Index=0L; Index<NUM_IMAGES; Index++)
  if (TP->Images[Index]) DeleteToasterImage (TP->Images[Index]);
 FreeVec (TP);
}

struct Toasters *InitMyBlanker(struct TagItem *TagList)

{
 struct BTDDrawInfo *BTDDrawInfo;
 ULONG *Error,Dummy,Instance,Index;
 LONG Chunky,NumToaster;
 struct Toasters *TP;
 struct RastPort TempRastPort;
 UBYTE *CMapPtr;

 if ((BTDDrawInfo=(struct BTDDrawInfo *)
                   GetTagData(BTD_DrawInfo,NULL,TagList))==NULL) return NULL;
 Error=(ULONG *)GetTagData(BTD_Error,(ULONG)&Dummy,TagList);

 Chunky=(CyberGfxBase==NULL)?FALSE:GetCyberMapAttr(BTDDrawInfo->BDI_RPort->BitMap,CYBRMATTR_ISCYBERGFX);
 Instance=GetTagData(BTD_Instance,0L,TagList);
 NumToaster=GetTagData(TP_Toasters,DEF_TOASTERS,TagList);

 if ((TP=AllocVec(sizeof(struct Toasters)+sizeof(struct IToaster)*NumToaster,MEMF_PUBLIC|MEMF_CLEAR))==NULL)
  {
   *Error=BTDERR_Memory;
   return NULL;
  }
 TP->Toasters=(struct IToaster *)&TP[1];

 InitRastPort (&TempRastPort);
 for (Index=0L; Index<NUM_IMAGES; Index++)
  if ((TP->Images[Index]=ConvertToasterImage(BytePackedFrames[Index],
                                             BTDDrawInfo->BDI_Pens-1,
                                             &TempRastPort,Chunky))==NULL)
   {
    EndMyBlanker (TP);

    *Error=BTDERR_Memory;
    return NULL;
   }
 for (; Index<NUM_PHASES; Index++)
  TP->Images[Index]=TP->Images[NUM_PHASES-Index];

 TP->BTDDrawInfo=BTDDrawInfo;
 TP->Width=BTDDrawInfo->BDI_Width;
 TP->Height=BTDDrawInfo->BDI_Height;
 TP->NumToaster=NumToaster;

 InitRandom (TP,Instance);

 for (Index=0L, CMapPtr=CMap; Index<(NUM_COLORS-1L); Index++)
  {
   BTDDrawInfo->BDI_Red[BTDDrawInfo->BDI_Pens[Index]]=*CMapPtr++;
   BTDDrawInfo->BDI_Green[BTDDrawInfo->BDI_Pens[Index]]=*CMapPtr++;
   BTDDrawInfo->BDI_Blue[BTDDrawInfo->BDI_Pens[Index]]=*CMapPtr++;
   BTDDrawInfo->BDI_Changed[BTDDrawInfo->BDI_Pens[Index]]=TRUE;
  }

 for (Index=0L; Index<NumToaster; Index++)
  if (!FindLaunchPos(TP,Index)) TP->Toasters[Index].delay=30;

 return TP;
}

void AnimMyBlanker(struct Toasters *TP)

{
 struct BTDDrawInfo *BTDDrawInfo;
 LONG i,j;

 BTDDrawInfo=TP->BTDDrawInfo;

 for (i=0; i<TP->NumToaster; i++)
  if (TP->Toasters[i].delay==0L)
   {
    TP->Toasters[i].old_x=TP->Toasters[i].x;
    TP->Toasters[i].old_y=TP->Toasters[i].y;
    TP->Toasters[i].x-=TP->Toasters[i].xspeed;
    TP->Toasters[i].y+=TP->Toasters[i].yspeed;
    TP->Toasters[i].xcol=-1;
    TP->Toasters[i].ycol=-1;
   }

 for (i=0; i<TP->NumToaster; i++)
  {
   if (TP->Toasters[i].delay==0L)
    {
     j=-1;
     while ((j=TestExtent(TP,j+1,i,TP->Toasters[i].x,TP->Toasters[i].y))>=0)
      {
       if (ABS(TP->Toasters[j].old_x-TP->Toasters[i].old_x)<(IMAGE_WIDBUF))
        {
         if (TP->Toasters[i].y<TP->Toasters[j].y) TP->Toasters[i].ycol=j;
         if (TP->Toasters[i].xspeed==TP->Toasters[j].xspeed) TP->Toasters[i].xspeed++;
        }
       else
        {
         if (TP->Toasters[i].x > TP->Toasters[j].x) TP->Toasters[i].xcol=j;
         if (TP->Toasters[i].yspeed==TP->Toasters[j].yspeed) TP->Toasters[i].yspeed++;
        }
       if (ABS(TP->Toasters[j].old_y-TP->Toasters[i].old_y)<(IMAGE_HEIBUF))
        {
         if (TP->Toasters[i].x > TP->Toasters[j].x) TP->Toasters[i].xcol=j;
         if (TP->Toasters[i].yspeed==TP->Toasters[j].yspeed) TP->Toasters[i].yspeed++;
        }
      }
    }
  }

 for (i=0; i<TP->NumToaster; i++)
  {
   if (TP->Toasters[i].delay==0L)
    {
     TP->Toasters[i].x=TP->Toasters[i].old_x-ActXSpeed(TP,i);
     TP->Toasters[i].y=TP->Toasters[i].old_y + ActYSpeed(TP,i);
    }
  }
          
 for (i=0; i<TP->NumToaster; i++)
  {
   if (TP->Toasters[i].delay==0L)
    {
     j=-1;
     while ((j=TestExtent(TP,j+1,i,TP->Toasters[i].x,TP->Toasters[i].y))>=0)
      {
       if (ABS(TP->Toasters[j].old_x-TP->Toasters[i].old_x)<(IMAGE_WIDBUF))
        if (TP->Toasters[i].x > TP->Toasters[j].x) TP->Toasters[i].x=TP->Toasters[i].old_x;
        else
         if (TP->Toasters[i].y<TP->Toasters[j].y) TP->Toasters[i].y=TP->Toasters[i].old_y;

       if (ABS(TP->Toasters[j].old_y-TP->Toasters[i].old_y)<(IMAGE_HEIBUF))
        if (TP->Toasters[i].y<TP->Toasters[j].y)
         TP->Toasters[i].y=TP->Toasters[i].old_y;
      }
    }
  }

 for (i=0; i<TP->NumToaster; i++)
  if (TP->Toasters[i].delay==0L)
   {
    TP->Toasters[i].phase=(TP->Toasters[i].phase+1)%NUM_PHASES;

    SafeEraseRect (BTDDrawInfo,
                   TP->Toasters[i].x+IMAGE_WIDTH,
                   TP->Toasters[i].old_y,
                   TP->Toasters[i].x+IMAGE_WIDTH+TP->Toasters[i].xspeed,
                   TP->Toasters[i].old_y+IMAGE_HEIGHT);
    SafeEraseRect (BTDDrawInfo,
                   TP->Toasters[i].old_x,
                   TP->Toasters[i].old_y,
                   TP->Toasters[i].old_x+IMAGE_WIDTH,
                   TP->Toasters[i].old_y+TP->Toasters[i].yspeed);

    DrawToasterImage (BTDDrawInfo,TP->Images[TP->Toasters[i].phase],
                     TP->Toasters[i].x,TP->Toasters[i].y);

    if ((TP->Toasters[i].x<=(-IMAGE_WIDTH-1))||
        (TP->Toasters[i].y>=TP->Height)) TP->Toasters[i].delay=Random(TP,50);
   }
  else
   if (--TP->Toasters[i].delay==0L)
    TP->Toasters[i].delay=FindLaunchPos(TP,i)?0:30;

 WaitTOF();
}

ULONG PenCountMyBlanker(struct TagItem *TagList)

{
 return NUM_COLORS-1L;
}
