/* LedClock (C)1987 Ali T. Ozer. A clock program for interlaced screens. */

#include <exec/types.h>
#include <functions.h>
#include <intuition/intuition.h>
#include <exec/devices.h>
#include <devices/timer.h>
#include <graphics/gfxmacros.h>

/* LedClock is freely distributable and NOT for sale. 
**
** Author: Ali T. Ozer, ARPA: ali@score.stanford.edu, 
**                      REAL: Box 7703, Stanford, CA 94309
**
** A clock program for interlaced screens. This one uses
** numbers that resemble the numbers used in 7-segment LED and LCD
** clock displays. Various options are provided as compile time "#define"s:
**  
** TWENTYFOURHOUR  0 or 1, determines if the clock is 24-hour or not
** WINDOWXLOC      starting X location for the window in the WB screen
** WINDOWYLOC      starting Y location for the window in the WB screen
** FANCYPANIC      0 or 1, determines if a error message box is shown in
**                 case the clock can't run...
**
** LedClock works best with interlaced screens --- It will look ugly on a 
** "normal" WorkBench screen. (It will probably look fine on a lo-res, non
** interlaced screen, except it will be too big, way too big.)
**
** LedClock can be compiled by Manx 3.40a. Use the "+c" and "+d" switches
** while linking to assure that the static images go into CHIP ram:
**
**   cc ledclock.c
**   ln +cd ledclock.o -lc
**   run ledclock
**
** During linking you'll get warnings about _cli_parse and _wb_parse being
** redefined; you can safely ignore these messages.
**
** Various corners have been cut from this program to make the executable small.
** With Manx 3.40a, the executable is somewhere around 3.1 Kbytes. One can
** probably achieve half that size in assembly.
**
** Programmers might wish to fool around with the code to customize the
** clock. It's amazing how many parameters one can think of if one tries
** to make a general program... So, rather than trying to provide zillions
** of command line arguments, I took the easy way out and just threw out
** all generality... If you wish to create your customized clocks from
** this code, feel free to do so --- but please keep my original copyright
** notice (in the screen title bar) intact, along with any additional
** copyright notices you might add.
*/

/* The next few define's can be changed for different runtime parameters... */

#define TWENTYFOURHOUR 1
#define FANCYPANIC     0
#define WINDOWXLOC     20
#define WINDOWYLOC     14

#define COPYRIGHT "LedClock 1.0 (C)1987 Ali T. Ozer"

#if FANCYPANIC
#define Panic(arg)  WarnUser(arg)
#else 
#define Panic(arg)  CloseThings(1)
#endif

#define DIGITMAXX 18
#define DIGITMAXY 32

#define WINDOWX 0
#define WINDOWY 9
#define DIGIT1X (WINDOWX + 3)
#define DIGIT2X (DIGIT1X + DIGITMAXX + 3)
#define DIGIT3X (DIGIT2X + DIGITMAXX + 10)
#define DIGIT4X (DIGIT3X + DIGITMAXX + 3)
#define DIGITY  (WINDOWY + 3)
#define WINDOWWIDTH  (DIGIT4X + DIGITMAXX + 3)
#define WINDOWHEIGHT (DIGITY + DIGITMAXY + 3)
#define COLONX  (DIGIT3X - 6)
#define COLONY  (DIGITY  + 11)

/* The "leds" (the segments making up the digits) are named in the standard
** (clockwise) fashion, with a = top led, b = right top, ... f = left top, and
** g = middle.
**
** The following describes the vertical leds ("b", "c", "e", and "f") 
*/
USHORT vled [] = {
 0xdfff, 0x8fff, 0x8fff, 0x8fff, 0x8fff, 0x8fff, 0x8fff,
 0x1fff, 0x1fff, 0x1fff, 0x1fff, 0x1fff, 0x1fff, 0xbfff 
};

/* The following is the bitmap description of leds a and d.
*/
USHORT hled [] = {0x807f, 0x003f, 0x807f};

/* The following is the bitmap description of the middle ("g") led.
*/
USHORT mled [] = {0x80ff, 0x007f, 0x80ff};

/* The following describes the bitmap for the colon and the image.
*/
USHORT colondata [] = {
0x8fff, 0x8fff, 0x8fff, 0xffff, 0xffff, 0xffff,
0xffff, 0xffff, 0x1fff, 0x1fff, 0x1fff
};

struct Image colon = { 0,  0,  4, 11, 1, colondata, 1, 0, NULL};

/* The following describes the Image structures for all 7 leds, "a".."g"
*/
struct Image leds [] = {
{ 6,  0, 10,  3, 1, &hled[0], 1, 0, NULL},
{15,  2,  4, 14, 1, &vled[0], 1, 0, NULL},
{13, 17,  4, 14, 1, &vled[0], 1, 0, NULL},
{ 3, 30, 10,  3, 1, &hled[0], 1, 0, NULL},
{ 0, 17,  4, 14, 1, &vled[0], 1, 0, NULL},
{ 2,  2,  4, 14, 1, &vled[0], 1, 0, NULL},
{ 5, 15,  9,  3, 1, &mled[0], 1, 0, NULL}
};

/* In the following each byte determines what leds are lit for the 
** corresponding digit. The bits are ordered "xgfedcba" and a 1 means the 
** led is lit.
*/
UBYTE lled [] = {0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F};

struct Window      *MyWin;
struct RastPort    *MyRP;
struct Library     *IntuitionBase;
struct Library     *GfxBase;
struct timerequest  MyTR;
unsigned long      WindowSig, TimerSig;
int                curminutes;

/* Draw digit puts a 7-segment digit at the specified location. If digit is
** negative, then the area is erased but nothing is drawn. 
*/
DrawDigit (rp, digit, xloc, yloc)
struct RastPort *rp;
int digit, xloc, yloc;
{
  register int ledmask, ledcnt = 0;
  
  /* First erase it */
  RectFill (rp, (long)xloc, (long)yloc, 
                (long)(xloc + DIGITMAXX), (long)(yloc + DIGITMAXY));

  if (digit >= 0) {
    ledmask = lled[digit];
    while (ledcnt != 7) {
      if (ledmask & 1) DrawImage (rp, &leds[ledcnt], (long)xloc, (long)yloc);
      ledcnt++; ledmask >>= 1;
    }
  }
}

/* OpenTimer will open the timer device and also do all the initialization
** necessary to send timer requests... 
*/
OpenTimer (tr)
struct timerequest *tr;
{
  struct MsgPort *timerport;

  while ((timerport = CreatePort (NULL, 0L)) == NULL) Panic ("No port");

  OpenDevice (TIMERNAME, UNIT_VBLANK, tr, 0L);
  tr->tr_node.io_Message.mn_ReplyPort = timerport;
  tr->tr_node.io_Command = TR_ADDREQUEST;

}

/* OpenThings opens the Amiga libraries, the window. and the timer.
** If something goes wrong, a requester will pop up to warn the user.
** (If FANCYPANIC is defined as zero, then no requester pops up --- the
** program just quits.)
*/
OpenThings ()
{
  static struct NewWindow MyWinInfo =
  { WINDOWXLOC, WINDOWYLOC, WINDOWWIDTH, WINDOWHEIGHT, /* Lft,Top,Wd,Hgt */
    -1,-1,                   /* Detail pen, Block pen (-1 = use screens) */
    CLOSEWINDOW,                                           /* IDCMPflags */
    SMART_REFRESH | WINDOWDEPTH | WINDOWDRAG | WINDOWCLOSE | NOCAREREFRESH,
    NULL, NULL, NULL,              /* FirstGadget, Menu Checkmark, Title */
    NULL, NULL,                                        /* Screen, Bitmap */
    0, 0, 0, 0,                     /* Min Width/Height Max Width/Height */
    WBENCHSCREEN                                                 /* Type */
  };

  if (((IntuitionBase = OpenLibrary("intuition.library",0L)) == NULL) ||
      ((GfxBase = OpenLibrary("graphics.library",0L)) == NULL)) CloseThings (1);

  while (!(MyWin = OpenWindow(&MyWinInfo))) Panic ("No window");
  SetWindowTitles (MyWin, NULL, COPYRIGHT);

  MyRP = MyWin->RPort;
  WindowSig = 1L << MyWin->UserPort->mp_SigBit;

  SetOPen (MyRP, 1L); 
  SetAPen (MyRP, 1L);

  OpenTimer(&MyTR);
  TimerSig = 1L << MyTR.tr_node.io_Message.mn_ReplyPort->mp_SigBit;
}  

/* SendTimerRequest sends a timer request for the indicated amount of seconds.
** Assumes the timer request block pointed to by tr has already been properly
** setup --- including the io_Command field. 
*/
SendTimerRequest (tr, seconds)
struct timerequest *tr;
unsigned long seconds;
{
  tr->tr_time.tv_micro = 0L;
  tr->tr_time.tv_secs  = seconds;
  SendIO (tr);
}
  
#if FANCYPANIC

/* WarnUser is called when something goes wrong during initialization.
** WarnUser assumes Intuition is opened! (Now if that's not the case,
** then you're in trouble...)
*/

WarnUser (reason)
UBYTE *reason;
{
  static struct IntuiText postxt  = {3,1,COMPLEMENT,4,4,NULL,(UBYTE *)"Retry",NULL};
  static struct IntuiText negtxt  = {3,1,COMPLEMENT,4,4,NULL,(UBYTE *)"Sigh",NULL};
  static struct IntuiText bodytxt = {3,1,COMPLEMENT,4,6,NULL,NULL,NULL};

  bodytxt.IText = reason;
  if (AutoRequest (NULL,&bodytxt,&postxt,&negtxt,0L,0L,300L,60L) == FALSE)
     CloseThings (1);
}

#endif

/* CloseTimer closes the timer pointed to by tr. If no reply port is present,
** then CloseTimer assumes the timer was never opened.
*/
CloseTimer (tr)
struct timerequest *tr;
{
  struct MsgPort *msgp;
  if (msgp = tr->tr_node.io_Message.mn_ReplyPort) {
    DeletePort (msgp);  
    CloseDevice (tr);
  }
}

/* CloseThings releases all the resources obtained by the program.
*/
CloseThings(error_code)
int error_code;
{ 
  CloseTimer(&MyTR);
  if (MyWin)         CloseWindow(MyWin);
  if (GfxBase)       CloseLibrary(GfxBase);
  if (IntuitionBase) CloseLibrary(IntuitionBase);
  exit (error_code);
}

static int ht = -1, ho = -1, mt = -1, mo = -1;

/* WriteTime writes out the new time. 
*/
WriteTime (minutes)
int minutes;
{
  int hours, htens, hones, mtens, mones;
  
#if TWENTYFOURHOUR
  hours = minutes / 60;
#else
  hours = (minutes / 60 + 11) % 12 + 1;
#endif
    
  /* AM if minutes < 720, but we don't worry about this... */

  if ((htens = hours / 10) != ht)
    DrawDigit (MyRP, ((ht = htens) ? ht : -1), DIGIT1X, DIGITY);
  if ((hones = hours % 10) != ho)
    DrawDigit (MyRP, ho = hones, DIGIT2X, DIGITY);
  if ((mtens = (minutes % 60) / 10) != mt)
    DrawDigit (MyRP, mt = mtens, DIGIT3X, DIGITY);
  if ((mones = (minutes % 10)) != mo)
    DrawDigit (MyRP, mo = mones, DIGIT4X, DIGITY);
}    

/* ShowTheTime reads the time, updates the display, and sends a new timer
** request.
*/
ShowTheTime ()
{
  long timevec[3];

  DateStamp (&timevec[0]);
  curminutes = (int)(timevec[1]);
  WriteTime (curminutes);
  /* Timer request for the next top of minute */
  SendTimerRequest (&MyTR, 60L - (long)(timevec[2] / 50L));
}

main ()
{
  struct Task *me;

  OpenThings  ();

  /* Bump the priority up to 5. Not too serious, as under normal conditions
  ** this program will wake up only every 60 seconds.
  */
  if (me = FindTask (NULL)) SetTaskPri (me, 5L); 

  /* Clear the window and draw in the ":". 
  */  
  RectFill  (MyRP, (long)WINDOWX, (long)WINDOWY, 
                    WINDOWWIDTH-1L, WINDOWHEIGHT-1L); 
  DrawImage (MyRP, &colon, (long)COLONX, (long)COLONY);

  /* Now sit down, relax, and wait for either an IDCMP or a timer event...
  */
  while (1) {
    ShowTheTime ();
    if (Wait (WindowSig | TimerSig) & TimerSig)
       (void) GetMsg (MyTR.tr_node.io_Message.mn_ReplyPort);
    else {  
       AbortIO (&MyTR);  /* Don't even get the message, assume it's CLOSEWINDOW */
       CloseThings (0);
    }
  }
}



/* Including the following two lines prevents the linker from bringing in
** the two functions _wb_parse and _cli_parse called by the initialization
** code in _main. Reduces code size by about 900 bytes for Manx 3.40a.
*/
void _wb_parse () {}
void _cli_parse () {}
