/*----------------------------------------------------*
 | File: VLTIMER.c - Small clock/timer for the Amiga. |
 | Version: 2.00 (no version string to save space :-) |
 | Last modified: MLO 30 Nov 1993 20:21:09            |
 +----------------------------------------------------+-----------*
 | This code is free software, that you can distribute and modify |
 | under the terms of the GNU General Public License (included in |
 | the file LICENSE.TXT of the distribution) -- see also the file |
 | DISCLAIMER.TXT, about the (lack of) warranties and the author. |
 *----------------------------------------------------------------*/

/**
 | VLTimer opens a small window with a digital clock/timer in the
 | VLT custom screen (or in the default Public Screen).  The timer
 | can be reset selecting the window and hitting the '0' key; the
 | 'TAB' key switches between clock and timer.
**/

/**
 | #include's
**/

#include <stdio.h>                      /* Standard library */
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <exec/types.h>                 /* Amiga stuff */
#include <exec/memory.h>
#include <workbench/startup.h>
#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/icon.h>
#include <proto/intuition.h>

/**
 | Global variables to detach from CLI
**/

long  __stack        = 4000;
char *__procname     = "VLTimer";
long  __priority     = 0;
long  __BackGroundIO = 0;

/**
 | Global variables to be addressed by Cleanup()
**/

struct IntuitionBase *IntuitionBase = 0;
struct Screen        *pScreen       = 0;
struct Window        *Window        = 0;
struct IntuiMessage  *pIM           = 0;
struct Library       *IconBase      = 0;
struct RDArgs        *pRDArgs       = 0;

/**
 | #define's:
 | - miscellaneous parameters;
 | - flags and IDCMP's for the VLTimer window;
**/

#define REVISION        37              /* Library revision (AmigaDOS 2.04) */
#define W_WIDTH         58              /* Window width (text excluded)     */
#define NO_TIME         ((ULONG) ~0)    /* Flag 'no time on screen'         */
#define DELTA           10              /* Sleep time (DELTA/50 seconds)    */
#define ZERO            '0'             /* ASCII zero                       */
#define TAB             '\t'            /* ASCII TAB                        */

#define W_IDCMP IDCMP_CLOSEWINDOW | IDCMP_VANILLAKEY
#define W_FLAG1 WFLG_CLOSEGADGET | WFLG_DRAGBAR | WFLG_DEPTHGADGET
#define W_FLAG2 WFLG_SIMPLE_REFRESH
#define W_FLAGS (W_FLAG1 | W_FLAG2)

/**
 | CLI command line template, and constants for ReadArgs
**/

#define TEMPLATE "PUBSCREEN/K,CLOCK/S"

enum eOptions {
  OPTION_SCREEN = 0,
  OPTION_CLOCK,
  N_OPTIONS                              /* This must be last */
};

/**
 | ANSI procedures prototypes
**/

void Cleanup(int ReturnCode);
void NtoA(char *buf, int h, int m, int s);

/**
 | The main program.
 | If something goes wrong, VLTimer exits silently setting the
 | return code for the CLI to 20 (EXIT_FAILURE).
**/

int main(
  int argc,
  char *argv[]
){
  unsigned int Start[2];                /* When the timer was started */
  int dispClock;                        /* Clock/Timer flag           */

  ULONG Last             = NO_TIME;     /* Last printed time          */
  char slate[]           = "hh:mm:ss";  /* Print buffer               */
  char *pScreenName      = 0;           /* Public Screen name         */

  struct NewWindow NWind = {            /* VLTimer window parameters  */
    0, 0, W_WIDTH, 0,
    0, 1,
    W_IDCMP,
    W_FLAGS,
    0, 0,
    (UBYTE *) "",
    0, 0,
    0, 0, 0, 0,
    PUBLICSCREEN
  };

/**
 | Opens the intuition library
**/

  if ((IntuitionBase = (struct IntuitionBase *)
        OpenLibrary("intuition.library", REVISION)) == 0) {
    exit(EXIT_FAILURE);
  }

/**
 | Allocates an IntuiMessage buffer
**/

  if ((pIM = AllocMem(sizeof(struct IntuiMessage), MEMF_ANY)) == 0) {
    Cleanup(EXIT_FAILURE);
  }

/**
 | If called from the CLI, parses the command line; if started from
 | the workbench, gets the tool types.  In both cases, looks for:
 | - 'PUBSCREEN=<name>', to define the screen where the VLTimer
 |   window will be opened, if different from the default public
 |   screen);
 | - 'CLOCK', to start in the clock mode (the default is the timer
 |   mode).
 | If an error is detected parsing the tool types or the command
 | line, the default values are used for the public screen and the
 | starting mode.
**/

  if (argc) {
    long options[(int) N_OPTIONS];

    memset(options, 0, sizeof(options));

    if ((pRDArgs = ReadArgs(TEMPLATE, options, 0)) != 0) {
      pScreenName = (char *) options[(int) OPTION_SCREEN];
      dispClock   = options[(int) OPTION_CLOCK];
    }
  } else {
    struct WBArg      *pWBA;
    struct DiskObject *pDO;

    if ((IconBase = OpenLibrary("icon.library", REVISION)) == 0) {
      Cleanup(EXIT_FAILURE);
    }

    pWBA=((struct WBStartup *) argv)->sm_ArgList;

    if (pWBA->wa_Name != 0  && (pDO = GetDiskObject(pWBA->wa_Name)) != 0) {
      pScreenName = (char *)
          FindToolType((char **) pDO->do_ToolTypes, "PUBSCREEN");
      dispClock =
          (FindToolType((char **) pDO->do_ToolTypes, "CLOCK") != 0);
      FreeDiskObject(pDO);
    }
  }

/**
 | Locks the target screen, and opens the window (after having
 | adjusted the NewWindow parameters, considering the screen font,
 | to open the window in its lower right corner).  Yes, I know, YOU
 | don't like that position---but I do...
**/

  if ((pScreen = LockPubScreen(pScreenName)) == 0) {
    Cleanup(EXIT_FAILURE);
  }

  NWind.Screen = pScreen;
  NWind.LeftEdge = pScreen->Width -
    (NWind.Width += pScreen->RastPort.TxWidth * 8);
  NWind.TopEdge = pScreen->Height -
    (NWind.Height = pScreen->WBorTop + pScreen->RastPort.TxHeight + 1);

  if ((Window = OpenWindow(&NWind)) == 0) {
    Cleanup(EXIT_FAILURE);
  }

/**
 | Starts the timer; then begins an infinite loop (exit closing the
 | window).  Checks for Intuition event (window closed, or key hit),
 | and performs the appropriate action: resets the timer if the '0'
 | key has been hit, or switches between timer and clock if the 'TAB'
 | key has been hit).  Then asks for the current time and, if the
 | display should has changed, updates it; then suspends itself for
 | a short time.
**/

  for ((void) timer(Start); ; Delay(DELTA)) {
    struct IntuiMessage *IMsg;          /* Intuition message pointer   */
    int hour, mins, secs;               /* Elapsed time                */
    int oldSec;                         /* Last time clock was updated */

    while ((IMsg = (struct IntuiMessage *) GetMsg(Window->UserPort)) != 0) {
      memcpy(pIM, IMsg, sizeof(struct IntuiMessage));
      ReplyMsg((struct Message *) IMsg);

      switch (pIM->Class) {
        case IDCMP_CLOSEWINDOW:
          Cleanup(EXIT_SUCCESS);
          break;

        case IDCMP_VANILLAKEY:
          switch (pIM->Code) {
            case TAB:
              if ((dispClock = !dispClock) != 0) {
                oldSec = -1;
              }
              break;

            case ZERO:
              (void) timer(Start);
              break;

          }
          break;

      }
    }

    if (dispClock) {
      struct DateStamp ds;

      (void) DateStamp(&ds);
      secs = ds.ds_Tick / TICKS_PER_SECOND;
      if (secs != oldSec) {
        hour   = ds.ds_Minute / 60;
        mins   = ds.ds_Minute % 60;
        oldSec = secs;
        NtoA(slate, hour, mins, secs);
        SetWindowTitles(Window, slate, (UBYTE *) -1);
      }
    } else {
      unsigned int SysSecs[2];

      (void) timer(SysSecs);
      *SysSecs -= *Start;
      if (SysSecs[1] < Start[1]) (*SysSecs)--;
      if (*SysSecs != Last) {
        secs = (Last = *SysSecs) % 60;
        mins = (Last / 60) % 60;
        hour = (Last / 3600) % 24;
        NtoA(slate, hour, mins, secs);
        SetWindowTitles(Window, slate, (UBYTE *) -1);
      }
    }
  }
}

void Cleanup(
  int ReturnCode                        /* Exit code */
){

/**
 | Releases all system resources, then exits.
**/

  if (Window != 0) {
    void *IMsg;

    while ((IMsg = GetMsg(Window->UserPort)) != 0) {
      ReplyMsg(IMsg);
    }
    CloseWindow(Window);
  }

  if (pScreen       != 0) UnlockPubScreen(0, pScreen);
  if (pRDArgs       != 0) FreeArgs(pRDArgs);
  if (pIM           != 0) FreeMem(pIM, sizeof(struct IntuiMessage));
  if (IconBase      != 0) CloseLibrary(IconBase);
  if (IntuitionBase != 0) CloseLibrary((struct Library *) IntuitionBase);

  exit(ReturnCode);
}

void NtoA(
  char *buf,                            /* Output buffer */
  int h,                                /* Hours */
  int m,                                /* Minutes */
  int s                                 /* Seconds */
){
/**
 | Instead of sprintf(... "%02d:%02d:%02d" ...); to save memory.
**/

  *buf++ = ZERO + ((h / 10) % 10);
  *buf++ = ZERO + (h % 10);
   buf++;
  *buf++ = ZERO + (m / 10);
  *buf++ = ZERO + (m % 10);
   buf++;
  *buf++ = ZERO + (s / 10);
  *buf   = ZERO + (s % 10);
}
