/****************************************************************************
  PROGRAM INFO
****************************************************************************/

#define VERSION "1.07 (15 Mar 93)"

/*  File:    c.timer
 *  Purpose: Desktop 24 hour countdown/countup timer
 *  Author:   Tony Shew, 1992
 *
 *  Index
 **inc       #includes
 **glo       global data/macros
 **sdf       Setup dbox functions
 **sdh       Setup dbox handler
 **pid       progInfo dbox
 **meh       Menu handler funtions
**tdf       Timer dbox functions
 **tdh       Timer dbox handler
 **ich       Icon click handler
 **ini       Initialisation
 **main      main()
 *
 *  History: Compiled with Acorn ANSI C Release 4
 *           v1.00 - 14 Dec 92 - First working version.
 *           v1.01 - 17 Dec 92 - Title bar shows current state of timer
 *           v1.02 - 20 Dec 92 - Setup alarm beep via dbox.
 *           v1.03 - 06 Jan 93 - Changed Template
 *           v1.04 - 19 Jan 93 - Click with adjust on arrows reverses effect
 *           v1.05 - 24 Feb 93 - Check stored time on load
 *           v1.06 - 03 Mar 93 - Arrows work even when beeping
 *           v1.07 - 15 Mar 93 - Add line to desktop !Boot file
 *
 *    v1.01  wimp_ECLOSE will also stop alarm if sounding
 *           changed Purpose: from '1 hour countdown timer'
 *           Cannot store 00:00:00
 *           default stored time = 12:34:56
 *           Title bar shows current state of timer
 *           timer template - removed toggle size icon / title indirected
 *           added Alarm sprite
 *           event mask
 *    v1.02  Setup alarm beep via dbox.
 *           save the stored time
 *           Can now use other sound voices
 *           Click on icon bar will stop alarm beep
 *           sound now via bbc_sound()
 *           channelvoice now via argc/argv in main()
 *           Flash icon bar colours once a beep
 *    v1.03  Changed timer template to look better in mode 12
 *           Cannot pause unless timer is running
 *    v1.04  Adjust click on arrows reverses effect
 *           Changed setup template to look better in mode 12
 *    v1.05  Added files Sprites22 & Sprites23
 *           check stored time for valid data on file load.
 *           extended event mask
 *    v1.06  When beeping, arrows now work as well as cancelling beeps
 *    v1.07  Add line to desktop !Boot file to auto run !Timer on startup
 */

/****************************************************************************
  **inc - #INCLUDE's
****************************************************************************/

#include "alarm.h"
#include "baricon.h"
#include "bbc.h"
#include "dbox.h"
#include "event.h"
#include "menu.h"
#include "msgs.h"
#include "os.h"
#include "res.h"
#include "resspr.h"
#include "template.h"
#include "werr.h"
#include "wimp.h"
#include "wimpt.h"
#include "win.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/****************************************************************************
  **glo - GLOBAL DATA / MACROs
****************************************************************************/

#define Sound_Configure 0x40140    /* swi number */
#define FRONT -1
static wimp_w dbhandle;
enum window_icons { START, PAUSE, CLEAR, STORE, RESET, HRSUP, HRSDN,
                    MINUP, MINDN, SECUP, SECDN, TIME };
static int hrs, min, sec, shrs, smin, ssec;
static char time[9];
static dbox tdbx;
static BOOL trunning, tdown, tpaused, tbeep;
static int tstart, tend, ttogo, telapsed;
static char *thandle = "Countdown timer";
enum sdbx_icons { SCH = 7, SVO, SPI, SDU, SIN,
                  SCHDN, SVODN, SPIDN, SDUDN, SINDN,
                  SCHUP, SVOUP, SPIUP, SDUUP, SINUP };
static int sch, svo, spi, sdu, sin, maxch;
static char *beepicon[2] = { "beep0", "beep1" };
static BOOL bicon = FALSE;

static char *app_title     = "Timer";
static char *app_resourses = "Timer";
static int   app_eventmask = wimp_EMNULL
                           | wimp_EMPTRLEAVE
                           | wimp_EMPTRENTER
                           | wimp_EMUSERDRAG
                           | wimp_EMKEY
                           | wimp_EMSCROLL
                           | wimp_EMLOSECARET
                           | wimp_EMGAINCARET
                           ;

/****************************************************************************
  ICON BAR MENU STRUCTURE
****************************************************************************/

static menu icon_menu;
#define icon_menu_title "Timer"
enum icon_menu_list { icon_menu_info = 1,
                      icon_menu_setup,
                      icon_menu_save,
                      icon_menu_quit
                    };
#define icon_menu_items msgs_lookup("imenu")

/****************************************************************************
  **sdf - SETUP DBOX
****************************************************************************/

static void write_setup_dbox(dbox d)
{
  dbox_setnumeric(d, SCH, sch);
  dbox_setnumeric(d, SVO, svo);
  dbox_setnumeric(d, SPI, spi);
  dbox_setnumeric(d, SDU, sdu);
  dbox_setnumeric(d, SIN, sin);
  if (!tbeep) wimpt_complain(bbc_sound(sch, svo, spi, sdu, 0));
}

static void change_sch(dbox d, register int adjust)
{
  sch += adjust;
  if (sch < 1) sch = maxch;
  if (sch > maxch) sch = 1;
}

static void change_svo(dbox d, register int adjust)
{
  svo += adjust;
  if (svo < 256) svo = 383;
  if (svo > 383) svo = 256;
}

static void change_spi(dbox d, register int adjust)
{
  spi += adjust;
  if (spi < 0) spi = 255;
  if (spi > 255) spi = 0;
}

static void change_sdu(dbox d, register int adjust)
{
  sdu += adjust;
  if (sdu < 1) sdu = sin / 5;
  if (sdu > sin / 5) sdu = 1;
}

static void change_sin(dbox d, register int adjust)
{
  sin += adjust;
  if (sin < 10) sin = 500;
  if (sin > 500) sin = 10;
  if (sdu > (sin / 5)) sdu = sin / 5;
}

/**sdh */
static void setup_dbox(void)
{
  register int response;
  dbox d;
  wimp_eventstr *e;

  if ((d = dbox_new("setup")) != NULL) {
    dbox_show(d);
    write_setup_dbox(d);

    while ((response = dbox_fillin(d)) > dbox_CLOSE) {
      e = wimpt_last_event();
      switch (response)
      {
        case SCHDN:
          if (e->data.but.m.bbits != wimp_BRIGHT) change_sch(d, -1);
          else change_sch(d, 1);
          break;
        case SCHUP:
          if (e->data.but.m.bbits != wimp_BRIGHT) change_sch(d, 1);
          else change_sch(d, -1);
          break;

        case SVODN:
          if (e->data.but.m.bbits != wimp_BRIGHT) change_svo(d, -1);
          else change_svo(d, 1);
          break;
        case SVOUP:
          if (e->data.but.m.bbits != wimp_BRIGHT) change_svo(d, 1);
          else change_svo(d, -1);
          break;

        case SPIDN:
          if (e->data.but.m.bbits != wimp_BRIGHT) change_spi(d, -1);
          else change_spi(d, 1);
          break;
        case SPIUP:
          if (e->data.but.m.bbits != wimp_BRIGHT) change_spi(d, 1);
          else change_spi(d, -1);
          break;

        case SDUDN:
          if (e->data.but.m.bbits != wimp_BRIGHT) change_sdu(d, -1);
          else change_sdu(d, 1);
          break;
        case SDUUP:
          if (e->data.but.m.bbits != wimp_BRIGHT) change_sdu(d, 1);
          else change_sdu(d, -1);
          break;

        case SINDN:
          if (e->data.but.m.bbits != wimp_BRIGHT) change_sin(d, -1);
          else change_sin(d, 1);
          break;
        case SINUP:
          if (e->data.but.m.bbits != wimp_BRIGHT) change_sin(d, 1);
          else change_sin(d, -1);
          break;
      }
      write_setup_dbox(d);
    }
    dbox_dispose(&d);
  }
}

/****************************************************************************
  **pid - DISPLAY progInfo DBOX
****************************************************************************/

static void info_dbox(void)
{
  dbox d;

  if ((d = dbox_new("progInfo")) != NULL) {
    dbox_setfield(d, 4, VERSION);
    dbox_show(d);
    dbox_fillin(d);
    dbox_dispose(&d);
  }
}

/****************************************************************************
  **meh - MENU EVENT HANDLER
****************************************************************************/

static void load_data(void)
{
  FILE *in;
  char stime[16];

  if ((in = res_openfile("Data", "rb")) != NULL) {
    fscanf(in, "%s %d %d %d %d %d", stime, &sch, &svo, &spi, &sdu, &sin);
    fclose(in);
  }

  sscanf(stime, "%d:%d:%d", &shrs, &smin, &ssec);
  if (shrs || smin || ssec) {
    if (shrs > 23 || shrs < 0) shrs = 0;
    if (smin > 23 || smin < 0) smin = 0;
    if (ssec > 23 || ssec < 0) ssec = 0;
  }
  if (!shrs && !smin && !ssec) {
    shrs = 12;
    smin = 34;
    ssec = 56;
  }

  if (sch < 1 || sch > maxch) sch = 1;
  if (svo < 256 || svo > 383) svo = 348;
  if (spi < 0 || spi > 255) spi = 165;
  if (sin < 10 || sin > 500) sin = 50;
  if (sdu < 1 || sdu > sin / 5) sdu = sin / 5;
}

static void save_data(void)
{
  FILE *out;
  char stime[9];
  char *ps = "File: !Timer.Data";

  sprintf(stime, "%2d:%2d:%2d", shrs, smin, ssec);
  if (stime[0] == ' ') stime[0] = '0';
  if (stime[3] == ' ') stime[3] = '0';
  if (stime[6] == ' ') stime[6] = '0';

  if ((out = res_openfile("Data", "wb")) != NULL) {
    fprintf(out, "%s\n%d %d %d %d %d\n\n%s", stime,sch,svo,spi,sdu,sin,ps);
    fclose(out);
  }
  else {
    fclose(out);
    werr(FALSE, msgs_lookup("fileerr"));
  }
}

static void icon_menu_fn(void *handle, register char *hit)
{
  switch (hit[0])
  {
    case icon_menu_info:
      if (wimpt_last_event() -> e != wimp_EMENU)
        info_dbox();
      break;

    case icon_menu_setup:
      if (wimpt_last_event() -> e != wimp_EMENU)
        setup_dbox();
      break;

    case icon_menu_save:
      save_data();
      break;

    case icon_menu_quit:
      exit(0);

    default:
      break;
  }
}

/****************************************************************************
  **tdf - TIMER DBOX EVENT HANDLER
****************************************************************************/

static void bring_to_front(wimp_w winhandle)
{
  wimp_wstate state;

  wimpt_noerr(wimp_get_wind_state(winhandle, &state));
  state.o.behind = FRONT;
  wimpt_noerr(wimp_open_wind(&state.o));
}

static void timer_adjusthour(register int adjust)
{
  hrs += adjust;
  if (hrs > 23) hrs = 0;
  if (hrs < 0) hrs =23;
}

static void timer_adjustminute(register int adjust)
{
  min += adjust;
  if (min > 59) min = 0;
  if (min < 0) min = 59;
}

static void timer_adjustsecond(register int adjust)
{
  sec += adjust;
  if (sec > 59) sec = 0;
  if (sec < 0) sec = 59;
}

static char *write_time(void)
{
  sprintf(time, "%2d:%2d:%2d", hrs, min, sec);
  if (time[0] == ' ') time[0] = '0';
  if (time[3] == ' ') time[3] = '0';
  if (time[6] == ' ') time[6] = '0';
  return time;
}

static void timer_beep(register int called_at, void *handle)
{
  register int next = alarm_timenow() + sin;

  bring_to_front(dbhandle);
  wimpt_complain(bbc_sound(sch, svo, spi, sdu, 0));
  baricon_newsprite(beepicon[(bicon = !bicon)]);
  alarm_set(next, timer_beep, handle);
}
  
static void timer_alarmfn(register int called_at, void *handle)
{
  register int i, now;

  now = alarm_timenow();

  if (tdown) i = (tend - now) / 100;
  else i = (now - tstart) / 100;

  hrs = (i / 3600) % 24;
  min = (i / 60) % 60;
  sec = (i % 3600) % 60;

  if (hrs < 0 || min < 0 || sec < 0) hrs = min = sec = 0;

  dbox_setfield(tdbx, TIME, write_time());
  if (!tdown || ((hrs || min || sec) && trunning && tdown))
    alarm_set((now + 100 - ((now - tstart) % 100)), timer_alarmfn, handle);
  else {
    trunning = FALSE;
    tbeep = TRUE;
    alarm_set(alarm_timenow(), timer_beep, handle);
    win_settitle(dbhandle, msgs_lookup("titleal"));
  }
}

static void timer_start(dbox d)
{
  register int first, now;

  now = alarm_timenow();
  trunning = TRUE;
  baricon_newsprite("running");
  win_settitle(dbhandle, msgs_lookup("titlest"));
  dbox_setfield(tdbx, START, msgs_lookup("start"));

  if (!tpaused) {                 /* start new timer */
    if (hrs || min || sec) {      /* count down timer */
      tdown = TRUE;
      first = 100;
      ttogo = (((hrs * 3600) + (min * 60) + sec) * 100);
      tend = now + ttogo;
    }
    else {                        /* count up timer */
      tdown = FALSE;
      first = 100;
    }
    tstart = now;
    alarm_set(tstart + first, timer_alarmfn, (void *)thandle);
  }
  else {                          /* restart paused timer */
    if (tdown) tend = now + ttogo - telapsed;
    tstart = now - telapsed;
    tpaused = FALSE;
    first = 100 - (telapsed % 100);
    alarm_set(now + first, timer_alarmfn, (void *)thandle);
  }
}

static void timer_pause(dbox d)
{
  telapsed = alarm_timenow() - tstart;
  alarm_removeall((void *)thandle);
  tpaused = TRUE;
  baricon_newsprite("paused");
  win_settitle(dbhandle, msgs_lookup("titlepa"));
  dbox_setfield(tdbx, START, msgs_lookup("restart"));
}

static void timer_clear(dbox d)
{
  trunning = tpaused = tbeep = FALSE;
  alarm_removeall((void *)thandle);
  hrs = min = sec = 0;
  dbox_setfield(d, TIME, write_time());
  baricon_newsprite("ready");
  win_settitle(dbhandle, msgs_lookup("titlere"));
  dbox_setfield(tdbx, START, msgs_lookup("start"));
}

static void timer_store(void)
{
  if (hrs || min || sec) {
    shrs = hrs;
    smin = min;
    ssec = sec;
  }
}

static void timer_reset(dbox d)
{
  timer_clear(d);
  hrs = shrs;
  min = smin;
  sec = ssec;
  dbox_setfield(d, TIME, write_time());
}

static void write_to_boot_file(int handle)
{
  int OS_GBPB = 0xc;
  os_regset r;
  char line[256], title[32];

  sprintf(title, "%s$Dir", app_title);
  sprintf(line, "Run %s\n", getenv(title));
  r.r[0] = 2;
  r.r[1] = handle;
  r.r[2] = (int)line;
  r.r[3] = strlen(line);
  wimpt_complain(os_swix(OS_GBPB, &r));
}

/**tdh */
static BOOL timer_dbox_eventhandler(dbox d, void *event, void *handle)
{
  wimp_eventstr *e = (wimp_eventstr *)event;

  switch (e->e)
  {
    case wimp_ECLOSE:
      if (tbeep) timer_clear(d);
      wimpt_complain(wimp_close_wind(e->data.o.w));
      return TRUE;

    case wimp_ESEND:
    case wimp_ESENDWANTACK:
      switch (e->data.msg.hdr.action)
      {
        case 10:    /* include line in desktop !Boot file */
          write_to_boot_file(e->data.msg.data.words[0]);
          return TRUE;
      }
      break;

    case wimp_EBUT:
      switch (e->data.but.m.i)
      {
        case START:
          if (tbeep)
            timer_clear(d);
          else if (!trunning || tpaused)
            timer_start(d);
          return TRUE;

        case PAUSE:
          if (tbeep)
            timer_clear(d);
          else if (!tpaused && trunning)
            timer_pause(d);
          return TRUE;

        case CLEAR:
          timer_clear(d);
          return TRUE;

        case STORE:
          if (tbeep)
            timer_clear(d);
          else 
            timer_store();
          return TRUE;

        case RESET:
          timer_reset(d);
          return TRUE;

        case HRSUP:
          if (tbeep)
            timer_clear(d);
          if (!trunning) {
            if (e->data.but.m.bbits != wimp_BRIGHT) timer_adjusthour(1);
            else timer_adjusthour(-1);
            dbox_setfield(d, TIME, write_time());
          }
          return TRUE;

        case HRSDN:
          if (tbeep)
            timer_clear(d);
          if (!trunning) {
            if (e->data.but.m.bbits != wimp_BRIGHT) timer_adjusthour(-1);
            else timer_adjusthour(1);
            dbox_setfield(d, TIME, write_time());
          }
          return TRUE;

        case MINUP:
          if (tbeep)
            timer_clear(d);
          if (!trunning) {
            if (e->data.but.m.bbits != wimp_BRIGHT) timer_adjustminute(1);
            else timer_adjustminute(-1);
            dbox_setfield(d, TIME, write_time());
          }
          return TRUE;

        case MINDN:
          if (tbeep)
            timer_clear(d);
          if (!trunning) {
            if (e->data.but.m.bbits != wimp_BRIGHT) timer_adjustminute(-1);
            else timer_adjustminute(1);
            dbox_setfield(d, TIME, write_time());
          }
          return TRUE;

        case SECUP:
          if (tbeep)
            timer_clear(d);
          if (!trunning) {
            if (e->data.but.m.bbits != wimp_BRIGHT) timer_adjustsecond(1);
            else timer_adjustsecond(-1);
            dbox_setfield(d, TIME, write_time());
          }
          return TRUE;

        case SECDN:
          if (tbeep)
            timer_clear(d);
          if (!trunning) {
            if (e->data.but.m.bbits != wimp_BRIGHT) timer_adjustsecond(-1);
            else timer_adjustsecond(1);
            dbox_setfield(d, TIME, write_time());
          }
          return TRUE;
        
        default:
          if (tbeep) {
            timer_clear(d);
            return TRUE;
          }
      }
      break;
  }
  return FALSE;
}

/****************************************************************************
  **ich - ICON CLICK EVENT HANDLER
****************************************************************************/

static void iconbar_click(wimp_i icon)
{
  if (tbeep)
    timer_clear(tdbx);
  else
    bring_to_front(dbhandle);
}

/****************************************************************************
  **ini - INITIALISATION
****************************************************************************/

static BOOL initialise(void)
{
  wimpt_init(app_title);
  res_init(app_resourses);
  resspr_init();
  template_init();
  dbox_init();
  msgs_init();
  alarm_init();

  /* usual startup bit and pieces */
  if ((icon_menu = menu_new(icon_menu_title, icon_menu_items)) == NULL)
    return FALSE;
  baricon("ready", (int)resspr_area(), iconbar_click);
  if (!event_attachmenu(win_ICONBAR, icon_menu, icon_menu_fn, 0))
    return FALSE;
  event_setmask(app_eventmask);
    
  /* setup timer window */
  if ((tdbx = dbox_new("timer")) == NULL)
    return FALSE;
  dbhandle = dbox_syshandle(tdbx);
  event_attachmenu((event_w)dbhandle, icon_menu, icon_menu_fn, 0);
  dbox_raw_eventhandler(tdbx, timer_dbox_eventhandler, 0);
  win_claim_unknown_events(dbhandle);

  /* setup start data */
  win_settitle(dbhandle, msgs_lookup("titlere"));
  dbox_setfield(tdbx, START, msgs_lookup("start"));
  dbox_setfield(tdbx, TIME, "00:00:00");
  hrs = min = sec = 0;
  tbeep = trunning = tpaused = FALSE;

  return TRUE;
}

/****************************************************************************
  **main - MAIN PROGRAM
****************************************************************************/

int main(int argc, char *argv[])
{
  if (initialise()) {
    if (argc > 1) {   /* we have some sound modules to deal with */
      register int i, j;
      char s[60];
      os_regset r;

      /* assign voices to channels */
      for (j = 1, i = 2; j < argc && j < 8; j++) {
        sprintf(s, "ChannelVoice %d %s", i, argv[j]);
        if (!wimpt_complain(os_cli(s))) i++;
      }
      maxch = i - 1;
      if (maxch > 1) {  /* check no of channels in use, get more if needed */
        r.r[0] = r.r[1] = r.r[2] = r.r[3] = r.r[4] = 0;
        wimpt_complain(os_swix(Sound_Configure, &r));
        if (maxch > r.r[0]) wimpt_complain(bbc_voices(maxch));
      }
    }
    else maxch = 1;

    load_data();   /* This call must come after maxch is assigned */
    
    while (TRUE)
      event_process();
  }

  return 0;
}

/****************************************************************************
  END OF FILE
****************************************************************************/
