#include <exec/alerts.h>
#include <workbench/startup.h>
#include <devices/input.h>
#include <intuition/intuitionbase.h>

#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/graphics.h>
#include <proto/intuition.h>
#include <proto/timer.h>
#include <proto/icon.h>
#include <proto/commodities.h>

#include <MyStartup.h>
#include <MyLib.h>

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

long Defaults[5]=
   {
      0,0,
      1,
      3,0
   };

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

#define FONT_HEIGHT    7

struct SpriteImage
   {
      ULONG posctl;
      ULONG Data[FONT_HEIGHT+1];
      ULONG reserved;
   };

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

struct Library *GfxBase;
struct Library *IconBase;
struct Library *CxBase;
struct IntuitionBase *IntuitionBase;
struct Device *TimerBase;

CxObj *Broker, *MouseMoveObject, *LeftButtonUpObject;

struct SpriteImage chip SpriteImage[2];

struct SimpleSprite SimpleSprite=
   {
      (UWORD *)&SpriteImage[1],
      FONT_HEIGHT,
      0,0,
      -1
   };

UWORD FontData[FONT_HEIGHT*3]=
   {
      0xE4EE,0xAEEE,0xEE00,
      0xA422,0xA882,0xAA00,
      0xA422,0xA882,0xAA00,
      0xA4EE,0xEEE2,0xEE00,
      0xA482,0x22A2,0xA200,
      0xA482,0x22A2,0xA200,
      0xE4EE,0x2EE2,0xEE00
   };

struct EasyStruct EasyStruct=
   {
      sizeof(struct EasyStruct),
      0,
      "SpriteClock V1.1 Installation error",
      NULL,
      "Quit"
   };

struct NewBroker NewBroker=
   {
      NB_VERSION,
      "SpriteClock",
      "SpriteClock V1.1 09-Jan-93 C. Stieber",
      "Display a clock in a sprite",
      0,
      0,
      0,
      NULL,
      0
   };

char Version[]="$VER: SpriteClock 1.1 (09.01.93)";

struct SpriteImage *NewImage=&SpriteImage[0];
struct SpriteImage *OldImage=&SpriteImage[1];

struct timerequest timerequest;

WORD DeltaX, DeltaY;

struct
   {
      long *X;
      long *Y;
      long *SpriteNumber;
      long *DrawingColor;
      long *BackgroundColor;
   } Arguments=
   {
      &Defaults[0],
      &Defaults[1],
      &Defaults[2],
      &Defaults[3],
      &Defaults[4],
   };

struct RDArgs *RDArgs;

struct SignalSemaphore SpriteSemaphore;

WORD MouseX, MouseY;

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

void CloseAll(void)

{
   if (Broker)
      {
         ActivateCxObj(Broker,0);
         DeleteCxObjAll(Broker);
      }

   if (SimpleSprite.num!=-1) FreeSprite(SimpleSprite.num);

   if (timerequest.tr_node.io_Device)
      {
         AbortIO((struct IORequest *)&timerequest);
         WaitIO((struct IORequest *)&timerequest);
         CloseDevice((struct IORequest *)&timerequest);
      }

   ReplyMessages(NewBroker.nb_Port);

   if (RDArgs) FreeArgs(RDArgs);

   CloseLibrary(CxBase);
   CloseLibrary(GfxBase);
   CloseLibrary(IntuitionBase);
}

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

void __regargs DrawDigit(UWORD Digit, int BitPos)

{
   UWORD i;
   ULONG *FontPointer;
   ULONG FontMask;
   UWORD *DestPointer;
   UWORD DestValue;

   Digit=Digit*4;
   if (Digit<16)
      {
         FontMask=0xf0000000>>Digit;
         FontPointer=(ULONG *)FontData;
      }
   else
      {
         FontMask=0xf0000000>>(Digit-=16);
         FontPointer=(ULONG *)(FontData+1);
      }
   DestPointer=(UWORD *)(NewImage->Data);
   for (i=0; i<FONT_HEIGHT; i++)
      {
         DestValue=((*FontPointer & FontMask)<<Digit)>>(BitPos+16);
         if (*Arguments.DrawingColor & 1)
            {
               *DestPointer|=DestValue;
            }
         else
            {
               *DestPointer&=~DestValue;
            }
         DestPointer++;
         if (*Arguments.DrawingColor & 2)
            {
               *DestPointer|=DestValue;
            }
         else
            {
               *DestPointer&=~DestValue;
            }
         DestPointer++;
         FontPointer=(ULONG *)(((UWORD *)FontPointer)+3);
      }
}

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

void DrawTime(void)

{
   UWORD i;
   ULONG *DestPointer;
   ULONG Hour, Minute;

   DestPointer=NewImage->Data;
   Hour=(*Arguments.BackgroundColor & 1) ? 0xffff0000 : 0x00000000;
   if (*Arguments.BackgroundColor & 2) Hour|=0x0000ffff;
   for (i=0; i<FONT_HEIGHT; i++)
      {
         *DestPointer++=Hour;
      }
   Minute=timerequest.tr_time.tv_secs/60;
   if (!(Hour=(Minute/60)%12))
      {
         Hour=12;
      }
   timerequest.tr_time.tv_secs=Minute*60+60;
   timerequest.tr_time.tv_micro=0;
   Minute%=60;
   if (Hour>9)
      {
         DrawDigit(Hour/10,-1);
      }
   DrawDigit(Hour%10,2);
   DrawDigit(Minute/10,8);
   DrawDigit(Minute%10,12);
   if (*Arguments.DrawingColor & 1)
      {
         ((UWORD *)(NewImage->Data))[4]|=0x0200;
         ((UWORD *)(NewImage->Data))[10]|=0x0200;
      }
   else
      {
         ((UWORD *)(NewImage->Data))[4]&=~0x0200;
         ((UWORD *)(NewImage->Data))[10]&=~0x0200;
      }
   if (*Arguments.DrawingColor & 2)
      {
         ((UWORD *)(NewImage->Data))[5]|=0x0200;
         ((UWORD *)(NewImage->Data))[11]|=0x0200;
      }
   else
      {
         ((UWORD *)(NewImage->Data))[5]&=~0x0200;
         ((UWORD *)(NewImage->Data))[11]&=~0x0200;
      }
}

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

void RepositionSprite(void)

{
   ObtainSemaphore(&SpriteSemaphore);
   MoveSprite(NULL,&SimpleSprite,*Arguments.X>>1,*Arguments.Y>>1);
   ReleaseSemaphore(&SpriteSemaphore);
}

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

void ShowNewSprite(void)

{
   struct SpriteImage *Temp;

   ChangeSprite(NULL,&SimpleSprite,(void *)NewImage);
   RepositionSprite();
   Temp=NewImage;
   NewImage=OldImage;
   OldImage=Temp;
}

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

BOOL CheckMousePosition(void)

{
   DeltaX=MouseX-*Arguments.X;
   DeltaY=MouseY-*Arguments.Y;
   return(DeltaX>=0 && DeltaX<32 && DeltaY>=0 && DeltaY<(SimpleSprite.height<<1));
}

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

void GetMouse(void)

{
   ULONG IntuitionLock;

   IntuitionLock=LockIBase(0);
   MouseX=IntuitionBase->MouseX;
   MouseY=IntuitionBase->MouseY;
   UnlockIBase(IntuitionLock);
}

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

CxMsg * __asm __saveds LeftButtonDown(register __a0 CxMsg *Message)

{
   GetMouse();
   if (CheckMousePosition())
      {
         DisposeCxMsg(Message);
         Message=NULL;
         ActivateCxObj(MouseMoveObject,TRUE);
         ActivateCxObj(LeftButtonUpObject,TRUE);
      }
   return(Message);
}

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

CxMsg * __asm __saveds MouseMove(register __a0 CxMsg *Message)

{
   GetMouse();
   *Arguments.X=MouseX-DeltaX;
   *Arguments.Y=MouseY-DeltaY;
   RepositionSprite();
   return(Message);
}

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

CxMsg * __asm __saveds LeftButtonUp(register __a0 CxMsg *Message)

{
   DisposeCxMsg(Message);
   Message=NULL;
   ActivateCxObj(MouseMoveObject,FALSE);
   ActivateCxObj(LeftButtonUpObject,FALSE);
   return(Message);
}

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

void __regargs SimpleError(char *String)

{
   if (CommandLineLength)
      {
         PutStr(String);
         PutStr("\n");
      }
   else
      {
         EasyStruct.es_TextFormat=String;
         EasyRequestArgs(NULL,&EasyStruct,NULL,NULL);
      }
   CloseAll();
   exit(10);
}

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

void DosError(char *String, char *Filename)

{
   long ErrorCode;
   char ErrorBuffer[82];
   APTR Arguments[2];

   Arguments[0]=Filename;
   Arguments[1]=ErrorBuffer;
   if (!Fault(ErrorCode=IoErr(),NULL,ErrorBuffer,82))
      {
         sprintf(ErrorBuffer,"DOS-Error #%ld",ErrorCode);
      }
   if (CommandLineLength)
      {
         VPrintf(String,(long *)Arguments);
         PutStr("\n");
      }
   else
      {
         EasyStruct.es_TextFormat=String;
         EasyRequestArgs(NULL,&EasyStruct,NULL,Arguments);
      }
   CloseAll();
   exit(10);
}

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

void ProcessMessages(void)

{
   BOOL Quit;
   CxMsg *Message;

   Quit=FALSE;
   while (!Quit)
      {
         WaitPort(NewBroker.nb_Port);
         if (Message=(CxMsg *)GetMsg(NewBroker.nb_Port))
            {
               if ((struct Message *)Message==&timerequest)
                  {
                     GetSysTime(&timerequest.tr_time);
                     ObtainSemaphore(&SpriteSemaphore);
                     DrawTime();
                     ShowNewSprite();
                     ReleaseSemaphore(&SpriteSemaphore);
                     SendIO((struct IORequest *)&timerequest);
                  }
               else
                  {
                     if (CxMsgType(Message)==CXM_COMMAND)
                        {
                           switch(CxMsgID(Message))
                              {
                                 case CXCMD_DISABLE:   ActivateCxObj(Broker,FALSE);
                                                       ActivateCxObj(MouseMoveObject,FALSE);
                                                       ActivateCxObj(LeftButtonUpObject,FALSE);
                                                       break;
                                 case CXCMD_ENABLE:    ActivateCxObj(Broker,TRUE);
                                                       break;
                                 case CXCMD_UNIQUE:
                                 case CXCMD_KILL:      Quit=TRUE;
                                                       break;
                                 default:              break;
                              }
                        }
                     ReplyMsg((struct Message *)Message);
                  }
            }
      }
}

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

CxObj *AttachHandler(IX *InputXpression, CxMsg (* __asm __saveds Handler(register __a0 CxMsg *)))

{
   CxObj *Filter, *Custom;

   if (!(Filter=CxFilter(NULL)))
      {
         SimpleError("Unable to create a commodities filter object");
      }
   SetFilterIX(Filter,InputXpression);
   AttachCxObj(Broker,Filter);
   if (!(Custom=CxCustom(Handler,0)))
      {
         SimpleError("Unable to create a commodities custom object");
      }
   AttachCxObj(Filter,Custom);
   return(Filter);
}

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

void main(void)

{
   static IX InputXpression=
      {
         IX_VERSION,
         IECLASS_RAWMOUSE,
         IECODE_LBUTTON,0xffff,
         IEQUALIFIER_LEFTBUTTON,IEQUALIFIER_LEFTBUTTON | IEQUALIFIER_MIDBUTTON | IEQUALIFIER_RBUTTON,
         0
      };

   static char CommandTemplate[]="X/N/K,Y/N/K,SPRITE/N/K,DRAWING_COLOR=DC/N/K,BACKGROUND_COLOR=BC/N/K,PRIORITY=PRI/N/K";

   static char *MyToolTypes[]=
      {
         "X",
         "Y",
         "SPRITE",
         "DRAWING_COLOR",
         "BACKGROUND_COLOR",
         "PRIORITY"
      };

   int i;
   char *String;
   struct DiskObject *DiskObject;

   if (!(IntuitionBase=(struct IntuitionBase *)OpenLibrary(&"Unable to open intuition.library"[15],0)))
      {
         if (CommandLineLength)
            {
               PutStr("Unable to open intuition.library");
               PutStr("\n");
            }
         exit(10);
      }
   if (IntuitionBase->LibNode.lib_Version<37)
      {
         DisplayAlert(AT_Recovery,"\x00\x94\x10" "SpriteClock requires Kickstart V37 or later\x00",29);
         CloseLibrary((struct Library *)IntuitionBase);
         exit(10);
      }

   if (CommandLineLength)
      {
         if (!(RDArgs=ReadArgs(CommandTemplate,(long *)&Arguments,NULL)))
            {
               DosError("Error processing arguments: %s%s",NULL);
            }
      }
   else
      {
         if (!(IconBase=OpenLibrary(&"Unable to open icon.library"[15],0)))
            {
               SimpleError("Unable to open icon.library");
            }
         if (!(DiskObject=GetDiskObject(Startup.WBenchMsg->sm_ArgList->wa_Name)))
            {
               CloseLibrary(IconBase);
               DosError("Unable to read file %s:\n%s",Startup.WBenchMsg->sm_ArgList->wa_Name);
            }
         for (i=0; i<8; i++)
            {
               if (String=FindToolType(DiskObject->do_ToolTypes,MyToolTypes[i]))
                  {
                     StrToLong(String,&Defaults[i]);
                  }
            }
         FreeDiskObject(DiskObject);
         CloseLibrary(IconBase);
      }

   if (!(GfxBase=OpenLibrary(&"Unable to open graphics.library"[15],0)))
      {
         SimpleError("Unable to open graphics.library");
      }
   if (!(CxBase=OpenLibrary("commodities.library",37)))
      {
         SimpleError("Unable to open commodities.library V37");
      }

   if (GetSprite(&SimpleSprite,*Arguments.SpriteNumber)==-1)
      {
         SimpleError("Unable to allocate a sprite");
      }
   SpriteImage[0].reserved=SpriteImage[1].reserved=0;
   SpriteImage[0].Data[FONT_HEIGHT+1]=SpriteImage[1].Data[FONT_HEIGHT+1]=0;

   InitSemaphore(&SpriteSemaphore);
   NewBroker.nb_Port=&((struct Process *)FindTask(NULL))->pr_MsgPort;

   if (!(Broker=CxBroker(&NewBroker,NULL)))
      {
         SimpleError("Unable to create a commodities broker");
      }

   AttachHandler(&InputXpression,LeftButtonDown);

   InputXpression.ix_Code=IECODE_LBUTTON | IECODE_UP_PREFIX;
   InputXpression.ix_QualMask=0;
   LeftButtonUpObject=AttachHandler(&InputXpression,LeftButtonUp);

   InputXpression.ix_Class=IECLASS_RAWMOUSE;
   InputXpression.ix_CodeMask=0;
   MouseMoveObject=AttachHandler(&InputXpression,MouseMove);

   ActivateCxObj(LeftButtonUpObject,FALSE);
   ActivateCxObj(MouseMoveObject,FALSE);
   ActivateCxObj(Broker,TRUE);

   timerequest.tr_node.io_Message.mn_ReplyPort=NewBroker.nb_Port;
   if (OpenDevice(&"Unable to open timer.device"[15],UNIT_WAITUNTIL,(struct IORequest *)&timerequest,0))
      {
         SimpleError("Unable to open timer.device");
      }
   TimerBase=timerequest.tr_node.io_Device;
   timerequest.tr_node.io_Command=TR_ADDREQUEST;
   SendIO((struct IORequest *)&timerequest);

   ProcessMessages();
   
   CloseAll();
}
