/*
 *  WICONCLOCK      A utility that works with WICONIFY to display a
 *                  clock icon on every wIconify screen.  The clock face
 *                  shows the current time, and updates as time passes.
 *
 *  Copyright 1990 by Davide P. Cervone, all rights reserved.
 *  You may use this code, provided this copyright notice is kept intact.
 */


#define INTUITION_PREFERENCES_H             /* don't need 'em */
#include <intuition/intuitionbase.h>
#include <devices/timer.h>
#include <libraries/dos.h>

#include "wIcon.h"

static char *Program = "wIconClock v1.1";
static char *Copyright =
   "Copyright (c) 1990 by Davide P. Cervone, all rights reserved";


struct IntuitionBase *IntuitionBase;
#define INTUITION_REV	0

#define DELAYSECS       1       /* How often to look for time changes */
#define DELAYMICS       0

#define MAXSCREENS      24      /* Largest number of screens expected */


#define STANDARD_TIME   1       /* 12 hour clock with AM/PM */
#define MILITARY_TIME   2       /* 24 hour clock */

#define EXIT_OK         0L
#define EXIT_ERROR      10L

#define REPORTFLAGS     WI_REPORTOPEN| WI_REPORTCLOSE| WI_REPORTAUTOREMOVE


/*
 *  Clock face image size   
 */

#define CLOCKWIDTH      40
#define CLOCKHEIGHT     19
#define CLOCKDEPTH      2
#define WORDSPERROW     3


static struct timerequest TimerRequest;     /* Timer IO request */
static struct MsgPort *TimerPort;           /* Timer IO reply port */
static int TimerOpen;                       /* TRUE if timer open */
static int TimerSet;                        /* TRUE if request pending */
static int OldPri;                          /* Old process priority */
static APTR ClockTask;                      /* Pointer to this task */
static int TimeType = STANDARD_TIME;        /* Time display type */

/*
 *  The clock face image data area
 */
extern USHORT ClockData[CLOCKDEPTH][CLOCKHEIGHT*WORDSPERROW];

/*
 *  The clock icon name area
 */
static char ClockName[] = "00:00 AM";

/*
 *  The clock icon's image structure
 */
static struct Image ClockImage =
   {0,0, CLOCKWIDTH,CLOCKHEIGHT,CLOCKDEPTH, &ClockData[0][0], 0x03,0, NULL};

/*
 *  The clock icon definition
 */
static WICON ClockIcon =
   {&ClockName[0], &ClockImage, NULL,NULL,
    600,15, WI_AUTOREMOVE| WI_NOORGANIZE, REPORTFLAGS, NULL};
static short ClockX,ClockY;

static WICONREF *Clock[MAXSCREENS];     /* wIconRefs of all open clocks */
static WICONREF *NewScreenClock;        /* Clock receiving NEWSCREEN reports */
static int ClockCount;                  /* How many are open */


extern WICONREF *wAddIcon();

/*
 *  HandData tells how to draw each hand.  Offset says how far to change
 *  the x position from our current position, and count says how many
 *  pixels in a row to use.  {0,0} ends the list.
 */
struct HandData {BYTE Offset,Count;};

/*
 *  The big hand has sixty positions, but these are brocken down into
 *  four sets (15 minute chuncks) which are symmetric either by a
 *  flip horizontally, vertically, or both.
 */

static struct HandData BigHand[16][6] =
{
   {{-1,2},{-1,2},{-1,2},{-2,4},{-2,4},{-1,2}},
   {{-1,2},{-1,2},{-1,3},{-1,3},{-1,3},{0,2}},
   {{-1,2},{-1,2},{-1,3},{-1,4},{0,3},{1,2}},
   {{-1,2},{-1,3},{0,3},{0,4},{1,3},{2,2}},
   {{-1,2},{0,2},{1,3},{1,4},{2,4},{4,1}},
   {{-1,2},{0,3},{1,4},{2,4},{4,3},{0,0}},
   {{-1,2},{0,3},{2,3},{3,4},{6,3},{0,0}},
   {{-1,2},{1,2},{3,3},{5,4},{7,3},{0,0}},
   {{-1,2},{1,3},{3,5},{6,4},{0,0},{0,0}},
   {{-1,3},{2,3},{4,4},{7,3},{0,0},{0,0}},
   {{-1,4},{2,4},{5,3},{8,2},{0,0},{0,0}},
   {{-1,5},{4,5},{7,5},{0,0},{0,0},{0,0}},
   {{-1,6},{3,7},{9,4},{0,0},{0,0},{0,0}},
   {{-1,5},{4,8},{0,0},{0,0},{0,0},{0,0}},
   {{-1,9},{8,5},{0,0},{0,0},{0,0},{0,0}},
   {{-1,14},{0,0},{0,0},{0,0},{0,0},{0,0}}
};


/*
 *  The little hand has positions for each hour and half-hour, and
 *  are brocken down in the same way as the big hand.
 */

static struct HandData LittleHand[7][6] =
{
   {{-1,2},{-1,2},{-2,4},{-1,2},{0,0},{0,0}},
   {{-1,2},{-1,3},{-1,3},{0,2},{0,0},{0,0}},
   {{-1,2},{0,3},{1,3},{2,2},{0,0},{0,0}},
   {{-1,3},{0,4},{2,3},{0,0},{0,0},{0,0}},
   {{-1,3},{1,4},{3,4},{0,0},{0,0},{0,0}},
   {{-1,5},{2,5},{0,0},{0,0},{0,0},{0,0}},
   {{-1,9},{0,0},{0,0},{0,0},{0,0},{0,0}}
};


/*
 *  FreeClocks()
 *
 *  For each clock in the clock array, remove the clock icon from the
 *  screen, and clear its array position.  Decrement the clock count as
 *  we go.
 */

static void FreeClocks()
{
   short i;
   
   for (i=0; i<MAXSCREENS && ClockCount; i++)
   {
      if (Clock[i]) wRemoveIcon(Clock[i]);
      Clock[i] = NULL;
      ClockCount--;
   }
}


/*
 *  Print()
 *
 *  Find the standard AmigaDOS output file and write the output string
 *  to the output file.  This is intended as a substitute for printf()
 *  when no formatting is required, and when you want a small executable.
 */

static void Print(s)
char *s;
{
   ULONG OutFile;
   extern ULONG Output();
   
   OutFile = Output();
   if (OutFile && s) Write(OutFile,s,strlen(s));
}


/*
 *  DoExit()
 *
 *  Print an error message, if one is give, and set the return status.
 *  Return the task priority to normal, and clean up any memory or
 *  other resources that were allocated.
 *  Exit with the correct return status.
 */

static void DoExit(message)
char *message;
{
   int status = EXIT_OK;

   if (message)
   {
      Print(message);
      Print("\n");
      status = EXIT_ERROR;
   }
   SetTaskPri(ClockTask,OldPri);
   if (TimerSet)            AbortIO(&(TimerRequest.tr_node));
   if (TimerOpen)           CloseDevice(&TimerRequest);
   if (TimerPort)           DeletePort(TimerPort);
   if (ClockCount)          FreeClocks();
   if (ClockIcon.IconPort)  DeletePort(ClockIcon.IconPort);
   if (IntuitionBase)       CloseLibrary(IntuitionBase);
   _exit(status);
}

#define ISSPACE(x)      (x == ' ' || x == '\t')
#define NOTSPACE(x)     (x != '\0' && x != ' ' && x != '\t')


/*
 *  ParseArgs()
 *
 *  If there was a parameter line,
 *  Skip the command (which is the first thing on the line).
 *  Skip any trailing spaces.
 *  Find the end-of-line and remove the new-line character and any
 *    trailing blanks.
 *  While there is more one the line
 *    Find the first space (ie, the end of the word).
 *    Mark the end of the word and skip any trailing spaces.
 *    If the word is one of the time specifiers, save the type,
 *    Otherwise, if the word is a position X,Y then save the position,
 *    Otherwise exit with the usage message.
 *    Continue looking at the rest of the line.
 */

static void ParseArgs(line)
char *line;
{
   char *s;
   int X,Y;

   if (line)
   {
      while (ISSPACE(*line)) line++;
      while (NOTSPACE(*line)) line++;
      while (ISSPACE(*line)) line++;
      for (s = line; *s && *s != '\n'; s++);
      if (*s) *s = 0; if (s > line) while (*(--s) == ' ') *s = 0;

      while (*line)
      {
         s = line; while (NOTSPACE(*s)) s++;
         if (*s)
         {
            *s++ = 0;
            while (ISSPACE(*s)) s++;
         }
         if (stricmp(line,"MILITARY") == 0) TimeType = MILITARY_TIME; else
         if (stricmp(line,"STANDARD") == 0) TimeType = STANDARD_TIME; else
         if (sscanf(line,"%ld,%ld",&X,&Y) == 2)
            ClockX = X, ClockY = Y;
         else DoExit("Usage:  wIconClock [MILITARY | STANDARD] [x,y]");

         line = s;
      }
   }
}
   

/*
 *  CheckLibOpen()
 *
 *  Call OpenLibrary() for the specified library, and check that the 
 *  open succeeded.
 */

static void CheckLibOpen(lib,name,rev)
APTR *lib;
char *name;
int rev;
{
   extern APTR OpenLibrary();
   
   if ((*lib = OpenLibrary(name,(ULONG)rev)) == NULL)
      DoExit("Can't Open Library");
}


/*
 *  SetupClock()
 *
 *  Get a pointer to the clock task (the current task) and change the
 *    task priority to 5 (so we update the screen a little faster).
 *  Get a port for the clock icons, if possible.
 *  Get a reply port for the timer requests, if possible.
 *  Open the timer device, and initialize the timer request.
 */

static void SetupClock()
{
   extern struct MsgPort *CreatePort();
   extern APTR FindTask();

   ClockTask = FindTask(NULL);
   OldPri = SetTaskPri(ClockTask,5);
   
   ClockIcon.IconPort = CreatePort(0,0);
   if (ClockIcon.IconPort == NULL) DoExit("Can't Create Icon Port\n");

   TimerPort = CreatePort(0,0);
   if (TimerPort == NULL) DoExit("Can't Create Timer Port\n");

   if (OpenDevice(TIMERNAME,UNIT_VBLANK,&TimerRequest,0) != NULL)
      DoExit("Can't Open Timer Device");
   TimerRequest.tr_node.io_Message.mn_ReplyPort = TimerPort;
   TimerRequest.tr_node.io_Command = TR_ADDREQUEST;
   TimerRequest.tr_node.io_Flags = 0;
   TimerOpen = TRUE;
}


/*
 *  UpdateTime()
 *
 *  Set the time string to the proper characters for the specified 
 *  time display type.  Trim the initial zero if the hour is only
 *  one digit long.
 */

static void UpdateTime(s,h,m)
char s[];
short h,m;
{
   switch(TimeType)
   {
      case STANDARD_TIME:
         s[5] = ' ';
         s[6] = (h >= 12)? 'P': 'A';
         h = h % 12; if (h == 0) h = 12;
         break;

      case MILITARY_TIME:
         s[5] = 0;
         break;
   }
   s[0] = '0' + (h / 10);
   s[1] = '0' + (h % 10);
   s[2] = ':';
   s[3] = '0' + (m / 10);
   s[4] = '0' + (m % 10);
   if (s[0] == '0') s++;
   ClockIcon.Name = s;
}


/*
 *  UpdateHand()
 *
 *  dx and dy specify the direction we are moving on the clock face,
 *  Hand is the hand array of the particular position we are using,
 *  SetIt specifies whether we are adding or removing the hand.
 *
 *  Start at the center of the clock.
 *  For each {offset,count} pair,
 *    Get the offset and count.
 *    If there is anything to do,
 *      Set the mask to the correct number of bits.
 *      Shift the mask by the correct offset for the dx direction.
 *      Divide the mask into three parts (one for each word in a row of
 *        the clock image data).
 *      If we are adding the had, clear those bits (to make the pen color 2)
 *      Otherwise set them (to make the pen color 3).
 *    Increment the y position in the correct direction.
 */

static void UpdateHand(dx,dy,Hand,SetIt)
short dx,dy;
struct HandData *Hand;
int SetIt;
{
   short y = (CLOCKHEIGHT-1)/2 * WORDSPERROW;
   short i;
   ULONG mask;
   UWORD m1,m2,m3;
   short count,offset;
   
   for (i=0; i<6; i++)
   {
      count  = Hand[i].Count;
      offset = Hand[i].Offset;
      if (count)
      {
         mask = ((1 << count) - 1);
         mask <<= (dx < 0)? (16 + offset): (16 - count - offset);
         m1 =  mask >> (CLOCKWIDTH/2);
         m2 = (mask >> (16 - (32-(CLOCKWIDTH/2)))) & 0xFFFF;
         m3 = (mask <<       (32-(CLOCKWIDTH/2)) ) & 0xFFFF;
         if (SetIt)
         {
            ClockData[0][y]   &= ~m1;
            ClockData[0][y+1] &= ~m2;
            ClockData[0][y+2] &= ~m3;
         } else {
            ClockData[0][y]   |=  m1;
            ClockData[0][y+1] |=  m2;
            ClockData[0][y+2] |=  m3;
         }
      }
      y += dy;
   }
}


/*
 *  UpdateFace()
 *
 *  Get the proper quadrant for the position of the big hand for the current
 *  time, and update the big hand.
 *  Now get the correct quadrant for the little hand:
 *    shift the minutes by 15 mintes since we want the little hand to
 *    change at a quarter after and a quarter of - that way the be one the
 *    hour from a quarter of to a quarter after, and on the half hour from
 *    a quarter after to a quarter to.
 *    Take the hour modulo 12 and multiply by two (every other hand is the
 *    hour position).  If the minutes are in the half-hour range, increment
 *    to the half-hour position.
 *    Finally, reduce by quadrants, as before.
 *  Update the clock face.
 */

static void UpdateFace(h,m,SetIt)
short h,m;
int SetIt;
{
   short dx,dy;
   short m1 = m;
   
   dx = 1; dy = -WORDSPERROW;
   if (m > 30) m -= 30, dx = -1, dy = WORDSPERROW;
   if (m > 15) m = 30 - m, dy = -dy;
   UpdateHand(dx,dy,&BigHand[m][0],SetIt);
   
   m1 += 15; if (m1 >= 60) m1 -= 60, h++;
   h %= 12; h *= 2; if (m1 > 29) h++;
   dx = 1; dy = -WORDSPERROW;
   if (h > 12) h -= 12, dx = -1, dy = WORDSPERROW;
   if (h > 6) h = 12 - h, dy = -dy;
   UpdateHand(dx,dy,&LittleHand[h][0],SetIt);
}


/*
 *  UpdateClocks()
 *
 *  Clear the clock position so the icons will remain where they are.
 *  For each clock that has been openned,
 *    Update the clock to the new face iamge,
 *    If the clock is the one receiving NEWSCREEN reports, add its report flags.
 */

static void UpdateClocks()
{
   short i,count;

   ClockIcon.x = ClockIcon.y = 0;
   for (i=0, count=ClockCount; i<MAXSCREENS && count; i++)
   {
      if (Clock[i])
      {
         wUpdateIcon(Clock[i],&ClockIcon); count--;
         if (Clock[i] == NewScreenClock)
            wModifyReport(Clock[i],REPORTFLAGS | WI_REPORTNEWSCREEN);
      }
   }
}


/*
 *  SetNewScreenClock()
 *
 *  We only want one clock to report NEWSCREEN events, otherwise we would
 *  be adding too many new clocks as new screens open (one for each clock
 *  already open).  So only the first clock in the list is marked to
 *  receive NEWSCREEN message.
 *
 *  Skip any blank array elements, and if we have found a clock,
 *  mark it to receive NEWSCREEN messages.
 */

static void SetNewScreenClock()
{
   short i;
   
   for (i=0; i<MAXSCREENS && Clock[i] == NULL; i++);
   if (i < MAXSCREENS)
   {
      wModifyReport(Clock[i],REPORTFLAGS | WI_REPORTNEWSCREEN);
      NewScreenClock = Clock[i];
   }
}


/*
 *  FinishClock()
 *
 *  Look through the clock list for the given clock,
 *  If found,
 *    Clear the array element for re-use, and cancel the loop.
 *    Decrement the clock count, and if this is the last one, we're done.
 *    If the clock was the one receiving NEWSCREEN messages, we need to choose
 *      another one to get them.
 */

static int FinishClock(theClock)
WICONREF *theClock;
{
   short i;
   int NotDone = TRUE;
   
   for (i=0; i<MAXSCREENS; i++)
   {
      if (Clock[i] == theClock)
      {
         Clock[i] = NULL; i = MAXSCREENS;
         if (--ClockCount == 0) NotDone = FALSE; else
         if (theClock == NewScreenClock) SetNewScreenClock();
      }
   }
   return(NotDone);
}


/*
 *  NewClock()
 *
 *  If we can add more clocks,
 *    Look through the clock array for an open spot.
 *    If we found a spot,
 *      Set the clock position:  if it was specified on the command line,
 *        use that, otherwise back it near the upper right.
 *      Add the clock to the new screen.
 *      If the add was successful, increment the clock count.
 *      End the search loop.
 */

static void NewClock(theScreen)
struct Screen *theScreen;
{
   short i;
   
   if (ClockCount < MAXSCREENS)
   {
      for (i=0; i<MAXSCREENS; i++)
      {
         if (Clock[i] == NULL)
         {
            if (ClockX || ClockY)
            {
               ClockIcon.x = ClockX;
               ClockIcon.y = ClockY;
            } else {
               ClockIcon.x = theScreen->Width - CLOCKWIDTH - 12;
               ClockIcon.y = 15;
            }
            Clock[i] = wAddIcon(theScreen,&ClockIcon);
            if (Clock[i]) ClockCount++;
            i = MAXSCREENS;
         }
      }
   }
}


/*
 *  CreateClocks()
 *
 *  Look through the Intuition screen list, and save the screen
 *  pointers (we do this because we don't want te list to change while
 *  we're looking at it, but we can't add icons as we go, since we
 *  will enter a Wait() state which may allow other tasks to change
 *  the screen list).
 *  For each pointer found, try to add a clock the that screen.
 *  Get a clock to receive NEWSCREEN messages.
 */

static void CreateClocks()
{
   struct Screen *Screen[MAXSCREENS];
   struct Screen *theScreen;
   short i = 0;
   int ILock;
   
   ILock = LockIBase(0L);
   theScreen = IntuitionBase->FirstScreen;
   while (theScreen)
   {
      Screen[i++] = theScreen;
      theScreen = theScreen->NextScreen;
   }
   UnlockIBase(ILock);
   while (i--) NewClock(Screen[i]);
   SetNewScreenClock();
}


/*
 *  WaitForAction()
 *
 *  Wait for the messages from the timer or the icon port.
 *  While there are still clock icons on screen,
 *    Get the datestamp, and recover the hours and minutes.
 *    If the time has changed since the last check,
 *      Remove the old hands and add the new ones.
 *      Update all the clocks to use the new faces.
 *      Save the time of the last update.
 *    Of no timer request is pending,
 *      Set up the timer request and send it.
 *      Mark that a requet is pending (in case we need to remove it later).
 *
 *    Wait for the timer or icon message.
 *    If the timer message was returned, mark it so we can re-issue it.
 *    For each icon message we receive, do the right thing:
 *      OPEN: change the display format when the user double-clicks the
 *            clock icon, and unselect the icon.  Only change if the
 *            icons is actually selected (to avoid changing during OPENALL
 *            commands).
 *      CLOSE:  remove the icon if the user closes it.
 *      AUTOREMOVE:  wIconify removed the icon when it closed the screen.
 *            In either casem we check to make sure it was not the NEWSCREEN
 *            icon, and if so, choose another.
 *      NEWSCREEN:  try to open a new clock icon on the new screen.
 *    Then reply to the message.
 */

static void WaitForAction()
{
   short Hours,Mins;
   short OldH = 0, OldM = 0;
   struct DateStamp theDateStamp;
   ULONG Signals;
   int NotDone = TRUE;
   struct wIconMessage *theMessage;
   extern struct wIconMessage *GetMsg();

   Signals = (1 << TimerPort->mp_SigBit)| (1 << ClockIcon.IconPort->mp_SigBit);

   while (NotDone)
   {
      DateStamp(&theDateStamp);
      Hours = theDateStamp.ds_Minute / 60;
      Mins  = theDateStamp.ds_Minute % 60;
      if (Hours != OldH || Mins != OldM)
      {
         UpdateFace(OldH,OldM,FALSE);
         UpdateFace(Hours,Mins,TRUE);
         UpdateTime(ClockName,Hours,Mins);
         UpdateClocks();
         OldH = Hours; OldM = Mins;
      }
      if (TimerSet == FALSE)
      {
         TimerRequest.tr_time.tv_secs  = DELAYSECS;
         TimerRequest.tr_time.tv_micro = DELAYMICS;
         SendIO(&(TimerRequest.tr_node));
         TimerSet = TRUE;
      }

      Wait(Signals);
      while (GetMsg(TimerPort)) TimerSet = FALSE;
      while (theMessage = GetMsg(ClockIcon.IconPort))
      {
         switch(theMessage->Action)
         {
            case WI_REPORTOPEN:
               if (wIconFlags(theMessage->Icon) & WI_SELECTED)
               {
                  TimeType = (MILITARY_TIME + STANDARD_TIME) - TimeType;
                  OldH = OldM = 0;
                  wUnSelectIcon(theMessage->Icon);
               }
               break;

            case WI_REPORTCLOSE:
               wRemoveIcon(theMessage->Icon);
            case WI_REPORTAUTOREMOVE:
               NotDone = FinishClock(theMessage->Icon);
               break;

            case WI_REPORTNEWSCREEN:
               NewClock(theMessage->Data.Screen);
               break;
         }
         ReplyMsg(theMessage);
      }
   }
}


/*
 *  _main()
 *
 *  Replaces the standard Lattice C _main routine.  It receives the
 *  complete command line as a single string as its only argument.
 *
 *  Parse the argument list.
 *  If wIconify is active,
 *    Open Intuition.
 *    Set up the ports and timer stuff.
 *    Create the clocks for the existing screens.
 *    If there were any clocks created, wait for them to be closed,
 *    Otherwise, say that none could be created.
 *    Exit with no error.
 *  Otherwise, say wIconify is not running.
 */

void _main(line)
char *line;
{
   ParseArgs(line);
   if (wIconifyActive())
   {
      CheckLibOpen(&IntuitionBase,"intuition.library",INTUITION_REV);
      SetupClock();
      CreateClocks();
      if (ClockCount) WaitForAction();
         else Print("Can't create Clock Icons on any Screen\n");
      DoExit(NULL);
   } else {
      Print("wIconify not running or incompatible version\n");
   }
}
