/* Dali Clock - a melting digital clock for PalmOS.
 * Copyright (c) 1991-2007 Jamie Zawinski <jwz@jwz.org>
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation.  No representations are made about the suitability of this
 * software for any purpose.  It is provided "as is" without express or
 * implied warranty.
 */


#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#ifdef HACK_FRAMEBUFFER
  /* needed to access WinHandle->displayAddrV20 for ROMs earlier than 3.5 */
# define ALLOW_ACCESS_TO_INTERNALS_OF_WINDOWS
#endif

#include "daliclock.h"
#include "numbers.h"
#include "hsv.h"


/* Make sure Err*Display* are not no-ops. */
#define ERROR_CHECK_LEVEL ERROR_CHECK_FULL

#include <PalmOS.h>
/*#include <Hardware.h>*/
#include <FeatureMgr.h>		/* for FtrGet() */

#ifndef HAVE_APPLAUNCHWITHCOMMAND
# define sysErrRomIncompatible -1
#endif


#define ROM_20 0x02003000
#define ROM_30 0x03003000
#define ROM_35 0x03503000
#define ROM_40 0x04003000

#define FG_PIXEL 253
#define BG_PIXEL (FG_PIXEL+1)

#undef countof
#define countof(x) (sizeof((x))/sizeof(*(x)))

#ifndef MAX_SEGS_PER_LINE
# define MAX_SEGS_PER_LINE 3
#endif

struct scanline {
  POS left[MAX_SEGS_PER_LINE], right[MAX_SEGS_PER_LINE];
};

typedef MemHandle FrameHandle;
struct frame {
  struct scanline scanlines [1]; /* scanlines are contiguous here */
};


/* The persistent preferences that aren't covered by system preferences.
 */
#define PREFS_VERSION 1
struct daliclock_prefs {
  Boolean inverted_p;
  Boolean seconds_only_p;
  int ticks;
};


struct dali_color {
  unsigned short r, g, b;
  int h;
  double s, v;
};

/* The runtime settings (some initialized from system prefs, but changable.)
 */
struct state {
  WinHandle window;
  Boolean inverted_p;
  Boolean twelve_hour_time_p;
  enum date_state { DTime, DDateIn, DDate, DDateOut, DDateOut2, DDash }
    display_date;
  char test_hack;
  int ticks;
  long interval;
  Boolean redraw_p;
  Boolean seconds_only_p;

  enum { MMDDYY, DDMMYY, YYMMDD, YYDDMM, MMYYDD, DDYYMM } date_format;
  Boolean pen_down_p;
  Boolean active_window_p;

  int tick;

  int char_width, char_height, colon_width;

  FrameHandle base_frames [12];
  FrameHandle orig_frames [6];
  FrameHandle current_frames [6];
  FrameHandle target_frames [6];
  FrameHandle clear_frame;

  signed char current_digits [6];
  signed char target_digits [6];

  UInt32 rom_version;
  Int16 win_width, win_height;
  Boolean color_p;
  struct dali_color fg, bg;
};



static FrameHandle
make_blank_frame (int width, int height)
{
  int size = sizeof (struct frame) + (sizeof (struct scanline) * (height - 1));
  FrameHandle fh = MemHandleNew (size);
  struct frame *frame;
  int x, y;

  if (!fh)
    {
      ErrDisplay ("Out of memory.");
      return 0;
    }

  frame = (struct frame *) MemHandleLock (fh);
  MemSet (frame, size, 0);
  for (y = 0; y < height; y++)
    for (x = 0; x < MAX_SEGS_PER_LINE; x++)
      frame->scanlines[y].left [x] = frame->scanlines[y].right [x] = width / 2;
  MemHandleUnlock (fh);
  return fh;
}


static FrameHandle
number_to_frame (const unsigned char *bits, int width, int height)
{
  int x, y;
  FrameHandle fh;
  struct frame *frame;
  POS *left, *right;

  fh = make_blank_frame (width, height);
  if (!fh) return 0;
  frame = (struct frame *) MemHandleLock (fh);

  for (y = 0; y < height; y++)
    {
      int seg, end;
      x = 0;
# define GETBIT(bits,x,y) \
         (!! ((bits) [((y) * ((width+7) >> 3)) + ((x) >> 3)] \
              & (1 << ((x) & 7))))

      left = frame->scanlines[y].left;
      right = frame->scanlines[y].right;

      for (seg = 0; seg < MAX_SEGS_PER_LINE; seg++)
        left [seg] = right [seg] = width / 2;

      for (seg = 0; seg < MAX_SEGS_PER_LINE; seg++)
        {
          for (; x < width; x++)
            if (GETBIT (bits, x, y)) break;
          if (x == width) break;
          left [seg] = x;
          for (; x < width; x++)
            if (! GETBIT (bits, x, y)) break;
          right [seg] = x;
        }

      for (; x < width; x++)
        if (GETBIT (bits, x, y))
          {
            /* This means the font is too curvy.  Increase MAX_SEGS_PER_LINE
               and recompile. */
            ErrDisplay ("Internal error: builtin font is bogus.");
          }

      /* If there were any segments on this line, then replicate the last
         one out to the end of the line.  If it's blank, leave it alone,
         meaning it will be a 0-pixel-wide line down the middle.
       */
      end = seg;
      if (end > 0)
        for (; seg < MAX_SEGS_PER_LINE; seg++)
          {
            left [seg] = left [end-1];
            right [seg] = right [end-1];
          }

# undef GETBIT
    }

  MemHandleUnlock (fh);
  return fh;
}


static void
copy_frame (struct state *state, FrameHandle from_h, FrameHandle to_h)
{
  struct frame *from = (struct frame *) MemHandleLock (from_h);
  struct frame *to   = (struct frame *) MemHandleLock (to_h);
  int y;

  for (y = 0; y < state->char_height; y++)
    to->scanlines[y] = from->scanlines[y];  /* copies the whole struct */

  MemHandleUnlock (to_h);
  MemHandleUnlock (from_h);
}


/* Returns the largest font that will fit on the screen.
 */
static const struct raw_number *
pick_font (struct state *state)
{
  int nn, cc;
  const struct raw_number *num;

  if (state->seconds_only_p)
    nn = 2, cc = 0;
  else
    nn = 6, cc = 2;

# define CHECK(WHICH) \
    num = get_raw_number_##WHICH (); \
    if (state->win_width  >= ((num[0].width * nn) + (num[10].width * cc)) && \
        state->win_height >= num[0].height) \
      return num
  CHECK(3);
  CHECK(2);
  CHECK(1);
  CHECK(0);
# undef CHECK
  ErrDisplay ("Display too small for all fonts!");
  return 0;
}


static Boolean
init_numbers (struct state *state)
{
  int i;
  const struct raw_number *raw = pick_font (state);
  if (!raw) return false;

  state->char_width  = raw[0].width;
  state->char_height = raw[0].height;
  state->colon_width = raw[10].width;

  for (i = 0; i < countof(state->base_frames); i++)
    state->base_frames [i] =
      number_to_frame (raw[i].bits, raw[i].width, raw[i].height);

  for (i = 0; i < countof(state->orig_frames); i++)
    {
      FrameHandle fh = make_blank_frame (state->char_width,
                                         state->char_height);
      if (!fh) return false;
      state->orig_frames [i] = fh;
    }

  for (i = 0; i < countof(state->current_frames); i++)
    {
      FrameHandle fh = make_blank_frame (state->char_width,
                                         state->char_height);
      if (!fh) return false;
      state->current_frames [i] = fh;
    }

  for (i = 0; i < countof(state->target_frames); i++)
    {
      FrameHandle fh = make_blank_frame (state->char_width,
                                         state->char_height);
      if (!fh) return false;
      state->target_frames [i] = fh;
    }

  state->clear_frame = make_blank_frame (state->char_width,
                                         state->char_height);
  if (!state->clear_frame) return false;

  for (i = 0; i < countof(state->target_digits); i++)
    state->target_digits[i] = state->current_digits[i] = -1;

  return true;
}


static void
free_numbers (struct state *state)
{
  int i;
# define FREEIF(x) do { if ((x)) { MemHandleFree((x)); (x) = 0; } } while (0)
# define FREELOOP(x) do { \
    for (i = 0; i < countof ((x)); i++) FREEIF ((x)[i]); } while (0)

  FREELOOP (state->base_frames);
  FREELOOP (state->orig_frames);
  FREELOOP (state->current_frames);
  FREELOOP (state->target_frames);
  FREEIF (state->clear_frame);

# undef FREELOOP
# undef FREEIF
}



#ifndef HAVE_SYSTICKSPERSECOND
 /* Documentation says 100 on real device, 60 on mac emulator.
    What about on other emulators?  Seems to be ~100 on Linux.
  */
# define SysTicksPerSecond() 100
#endif /* !HAVE_SYSTICKSPERSECOND */





static void
fill_target_digits (struct state *state)
{
  UInt32 seconds = TimGetSeconds ();  /* since 1/1/1904 */
  DateTimeType date;

  TimSecondsToDateTime (seconds, &date);

  if (state->test_hack)
    {
      state->target_digits [0] =
        state->target_digits [1] =
        state->target_digits [2] =
        state->target_digits [3] =
        state->target_digits [4] =
        state->target_digits [5] = (state->test_hack == '-' ? -1
                                    : state->test_hack - '0');
      state->test_hack = 0;
    }
  else if (state->display_date == DTime ||
           state->display_date == DDash)
    {
      Boolean twelve_hour_time_p = state->twelve_hour_time_p;

      if (state->display_date == DDash)
        state->display_date = DTime;

      if (twelve_hour_time_p && date.hour > 12) date.hour -= 12;
      if (twelve_hour_time_p && date.hour == 0) date.hour = 12;
      state->target_digits [0] = (date.hour - (date.hour % 10)) / 10;
      state->target_digits [1] = date.hour % 10;
      state->target_digits [2] = (date.minute - (date.minute % 10)) / 10;
      state->target_digits [3] = date.minute % 10;
      state->target_digits [4] = (date.second - (date.second % 10)) / 10;
      state->target_digits [5] = date.second % 10;

      if (twelve_hour_time_p && state->target_digits [0] == 0)
        state->target_digits [0] = -1;
    }
  else
    {
      int m0,m1,d0,d1,y0,y1;
      m0 = (date.month - (date.month % 10)) / 10;
      m1 = date.month % 10;
      d0 = (date.day - (date.day % 10)) / 10;
      d1 = date.day % 10;
      y0 = date.year % 100;
      y0 = (y0 - (y0 % 10)) / 10;
      y1 = date.year % 10;

      if (state->display_date == DDateIn)
        state->display_date = DDate;
      if (state->display_date == DDateOut)
        state->display_date = DDateOut2;
      else if (state->display_date == DDateOut2)
        state->display_date = DDash;

      if (state->seconds_only_p)
        {
          /* In seconds-only mode, always display dotm. */
          state->target_digits [4] = d0; state->target_digits [5] = d1;
        }
      else switch (state->date_format)
        {
        case MMDDYY:
          state->target_digits [0] = m0; state->target_digits [1] = m1;
          state->target_digits [2] = d0; state->target_digits [3] = d1;
          state->target_digits [4] = y0; state->target_digits [5] = y1;
          break;
        case DDMMYY:
          state->target_digits [0] = d0; state->target_digits [1] = d1;
          state->target_digits [2] = m0; state->target_digits [3] = m1;
          state->target_digits [4] = y0; state->target_digits [5] = y1;
          break;
        case YYMMDD:
          state->target_digits [0] = y0; state->target_digits [1] = y1;
          state->target_digits [2] = m0; state->target_digits [3] = m1;
          state->target_digits [4] = d0; state->target_digits [5] = d1;
          break;
        case YYDDMM:
          state->target_digits [0] = y0; state->target_digits [1] = y1;
          state->target_digits [2] = d0; state->target_digits [3] = d1;
          state->target_digits [4] = m0; state->target_digits [5] = m1;
          break;
          /* These are silly, but are included for completeness... */
        case MMYYDD:
          state->target_digits [0] = m0; state->target_digits [1] = m1;
          state->target_digits [2] = y0; state->target_digits [3] = y1;
          state->target_digits [4] = d0; state->target_digits [5] = d1;
          break;
        case DDYYMM:
          state->target_digits [0] = d0; state->target_digits [1] = d1;
          state->target_digits [2] = y0; state->target_digits [3] = y1;
          state->target_digits [4] = m0; state->target_digits [5] = m1;
          break;
        }
    }
}


#ifdef HACK_FRAMEBUFFER
static unsigned char *frame_buffer = 0;
static unsigned int frame_buffer_bytes_per_line = 0;
static unsigned int frame_buffer_depth = 0;

static void
cache_frame_buffer (struct state *state)
{
  frame_buffer_bytes_per_line = 0;
  frame_buffer = 0;

  if (state->rom_version < ROM_30)  /* ROM earlier than 3.0 -- assume. */
    {
#     ifndef hwrDisplayWidth
#       define hwrDisplayWidth 160
#     endif
      frame_buffer_bytes_per_line = hwrDisplayWidth/8;
    }
  else
    {
      UInt32 w = 0, h = 0, d = 0;
      Boolean color = false;
      WinScreenMode (winScreenModeGet, &w, &h, &d, &color);
      if (w > 0 && h > 0)
        {
          frame_buffer_depth = d;
          if (d == 1)
            frame_buffer_bytes_per_line = (w+7) / 8;
          else if (d == 8)
            frame_buffer_bytes_per_line = w;
          else if (d == 16)
            frame_buffer_bytes_per_line = w*2;
          else
            frame_buffer_bytes_per_line = w*4;
        }
    }

  if (frame_buffer_depth > 8)
    frame_buffer_bytes_per_line = 0; /* #### not yet supported */

  if (frame_buffer_bytes_per_line > 0)
    {
      WinHandle w = WinGetDisplayWindow();

      if (!w)
        ;
      else if (state->rom_version < ROM_35)
        frame_buffer = (unsigned char *) w->displayAddrV20;
      else
        { /* functions from "3.5 New Feature Set" */
          BitmapPtr b = WinGetBitmap (w);
          frame_buffer = (unsigned char *) (b ? BmpGetBits (b) : 0);

          /* If we're running on 4.0 or newer, we can ask the bitmap
             what its row-stride is instead of guessing, above.
          */
          if (b && state->rom_version >= ROM_40)
            {
              Coord bw, bh;
              UInt16 rowbytes = 0;
              BmpGetDimensions (b, &bw, &bh, &rowbytes);
              frame_buffer_bytes_per_line = rowbytes;
              state->win_width  = bw;
              state->win_height = bh;
            }
        }
    }

# if 0
  {
    char buf[100];
    int i = 0;
    buf[i++] = 'D';
    buf[i++] = '=';
    buf[i++] = '0' + (frame_buffer_depth / 10);
    buf[i++] = '0' + (frame_buffer_depth % 10);
    buf[i++] = ' ';
    buf[i++] = 'W';
    buf[i++] = '=';
    buf[i++] = '0' + (state->win_width / 100) % 10;
    buf[i++] = '0' + (state->win_width / 10) % 10;
    buf[i++] = '0' + (state->win_width % 10);
    buf[i++] = ' ';
    buf[i++] = 'H';
    buf[i++] = '=';
    buf[i++] = '0' + (state->win_height / 100) % 10;
    buf[i++] = '0' + (state->win_height / 10) % 10;
    buf[i++] = '0' + (state->win_height % 10);
    buf[i++] = 0;
    ErrDisplay (buf);
  }
# endif /* 0 */
}
#endif /* HACK_FRAMEBUFFER */


static void
draw_horizontal_line (int x1, int x2, int y, int screen_width, Boolean black_p)
{
  if (x1 > screen_width) x1 = screen_width;
  else if (x1 < 0) x1 = 0;

  if (x2 > screen_width) x2 = screen_width;
  else if (x2 < 0) x2 = 0;

  if (x1 == x2) return;

  if (x1 > x2)
    {
      Int16 swap = x1;
      x1 = x2;
      x2 = swap;
    }

/*  if (y > 205) y = 205;*/

#ifdef HACK_FRAMEBUFFER
  if (frame_buffer)
    {
      unsigned char *scanline =
        frame_buffer + (y * (long) frame_buffer_bytes_per_line);

      /* #### note: assuming depth is 1 or 8 */
      if (frame_buffer_depth == 8)
        {
          int pixel = (black_p ? FG_PIXEL : BG_PIXEL);
          for (; x1 < x2; x1++)
            scanline[x1] = pixel;
        }
      else if (black_p)
        for (; x1 < x2; x1++)
          scanline[x1>>3] |= 1 << (7 - (x1 & 7));
      else
        for (; x1 < x2; x1++)
          scanline[x1>>3] &= ~(1 << (7 - (x1 & 7)));
    }
  else
#endif /* HACK_FRAMEBUFFER */

  if (black_p)
    WinDrawLine (x1, y, x2, y);
  else
    WinEraseLine (x1, y, x2, y);
}



static void
draw_frame (struct state *state, FrameHandle fh, int x, int y)
{
  struct frame *frame = (struct frame *) MemHandleLock (fh);
  int px, py;

  for (py = 0; py < state->char_height; py++)
    {
      struct scanline *line = &frame->scanlines [py];
      int last_right = 0;

      for (px = 0; px < MAX_SEGS_PER_LINE; px++)
        {
          if (px > 0 &&
              (line->left[px] == line->right[px] ||
               (line->left [px] == line->left [px-1] &&
                line->right[px] == line->right[px-1])))
            continue;

          /* Erase the line between the last segment and this segment.
           */
          draw_horizontal_line (x + last_right,
                                x + line->left [px],
                                y + py,
                                state->win_width,
                                state->inverted_p);

          /* Draw the line of this segment.
           */
          draw_horizontal_line (x + line->left [px],
                                x + line->right[px],
                                y + py,
                                state->win_width,
                                !state->inverted_p);

          last_right = line->right[px];
        }

      /* Erase the line between the last segment and the right edge.
       */
      draw_horizontal_line (x + last_right,
                            x + state->char_width,
                            y + py,
                            state->win_width,
                            state->inverted_p);
    }

  MemHandleUnlock (fh);
}


static void
clear_borders (struct state *state, Boolean all_p)
{
  int ww = state->win_width;
  int wh = state->win_height;
  int dw = (state->seconds_only_p
            ? (state->char_width * 2)
            : ((state->char_width * 6) + (state->colon_width * 2)));
  int dh = state->char_height;
  int dx = (ww - dw) / 2;
  int dy = (wh - dh) / 2;
  RectangleType r;
  Boolean inverted_p = state->inverted_p;

#define RECT() \
   if (r.extent.x > 0 && r.extent.y > 0) \
     (inverted_p ? WinDrawRectangle(&r, 0) : WinEraseRectangle (&r, 0))

  if (all_p) {
    r.topLeft.x = 0; r.topLeft.y = 0; r.extent.x = ww; r.extent.y = wh;
    RECT();
    return;
  }

  /* Color in these four rectangles:
     ---------------
     |______A______|
     |_B_|_____|_C_|
     |______D______|
   */

  r.topLeft.x = 0; r.topLeft.y = 0;  r.extent.x = ww; r.extent.y = dy; /* A */
  RECT();

  r.topLeft.x = 0; r.topLeft.y = dy; r.extent.x = dx; r.extent.y = dh; /* B */
  RECT();

  r.topLeft.x = dx + dw; r.topLeft.y = dy;			       /* C */
  r.extent.x = ww - r.topLeft.x;
  r.extent.y = dh;
  RECT();

  r.topLeft.x = 0; r.topLeft.y = dy + dh;			       /* D */
  r.extent.x = ww;
  r.extent.y = wh - r.topLeft.y;
  RECT();
# undef RECT
}


static void draw_clock (struct state *state);
static void cycle_colors (struct state *, Boolean init_p);

static void
start_sequence (struct state *state)
{
  int i;

  if (state->display_date == DDate &&
      !state->pen_down_p)
    state->display_date = DDateOut;

  for (i = 0; i < countof (state->current_frames); i++)
    {
      /* Copy the (old) target_frames into the (new) current_frames,
         since that's what's on the screen now.
       */
      copy_frame (state, state->current_frames[i], state->orig_frames[i]);
    }

  draw_clock (state);
  fill_target_digits (state);

  for (i = 0; i < countof (state->current_frames); i++)
    {
      /* Now fill the (new) target_frames with the digits of the
         current time, since that's where we're going.
       */
      int j = state->target_digits[i];
      copy_frame (state,
                  (j < 0 ? state->clear_frame : state->base_frames [j]),
                  state->target_frames[i]);
    }

  cycle_colors (state, false);
}


static void
one_step (struct state *state,
          FrameHandle orig_h,
          FrameHandle current_h, 
          FrameHandle target_h,
          int tick)
{
  struct frame *orig_frame    = (struct frame *) MemHandleLock (orig_h);
  struct frame *current_frame = (struct frame *) MemHandleLock (current_h);
  struct frame *target_frame  = (struct frame *) MemHandleLock (target_h);

  struct scanline *orig   =    &orig_frame->scanlines [0];
  struct scanline *curr   = &current_frame->scanlines [0];
  struct scanline *target =  &target_frame->scanlines [0];
  int i = 0, x;
  int ticks = state->ticks;
  int tt = ticks+1-tick;

  for (i = 0; i < state->char_height; i++)
    {
# define STEP(field) \
         (curr->field = (orig->field \
                         + (((int) (target->field - orig->field)) \
                            * tt / ticks)))

      for (x = 0; x < MAX_SEGS_PER_LINE; x++)
        {
          STEP (left [x]);
          STEP (right[x]);
        }
      orig++;
      curr++;
      target++;
# undef STEP
    }
  MemHandleUnlock (orig_h);
  MemHandleUnlock (current_h);
  MemHandleUnlock (target_h);
}


static void
tick_sequence (struct state *state)
{
  int i;

  /* Advance one step in the current animation sequence. */
  for (i = 0; i < countof (state->current_frames); i++)
    if (state->target_digits[i] != state->current_digits[i])
      one_step (state,
                state->orig_frames[i],
                state->current_frames[i],
                state->target_frames[i],
                state->tick);
  state->tick--;

  if (state->tick <= 0)
    {
      /* End of the animation sequence; fill target_frames with the
         digits of the current time, and restart the counter. */
      start_sequence (state);
      state->tick = state->ticks;
    }
}


static void
draw_clock (struct state *state)
{
  int x, y, i;
  int width, height;

  width = state->win_width;
  height = state->win_height;
  x = (width - ((state->char_width * 6) + (state->colon_width * 2))) / 2;
  y = (height - state->char_height) / 2;

  if (state->seconds_only_p)
    x = (width - (state->char_width * 2)) / 2;

  if (x < 0) x = 0;

# define DIGIT(n) \
  if (state->target_digits[n] != state->current_digits[n]) \
    draw_frame (state, state->current_frames [n], x, y); \
  i++; \
  x += state->char_width

# define COLON() \
  draw_frame (state, state->base_frames \
                       [state->display_date == DTime ? 10 : 11], \
              x, y); \
  x += state->colon_width

  i = 0;

  if (state->seconds_only_p)
    {
      DIGIT(4);
      DIGIT(5);
    }
  else
    {
      DIGIT(0);
      DIGIT(1);
      COLON();
      DIGIT(2);
      DIGIT(3);
      COLON();
      DIGIT(4);
      DIGIT(5);
    }
# undef COLON
# undef DIGIT
}


static void
init_colors (struct state *state)
{
  if (!state->color_p) goto SKIP;

  if (state->rom_version < ROM_35)
    return;

  state->fg.r = 0x0000;
  state->fg.g = 0x4444;
  state->fg.b = 0x6666;

  state->bg.r = 0xFFFF;
  state->bg.g = 0x0000;
  state->bg.b = 0x0000;

  rgb_to_hsv ( state->fg.r,  state->fg.g,  state->fg.b,
              &state->fg.h, &state->fg.s, &state->fg.v);
  rgb_to_hsv ( state->bg.r,  state->bg.g,  state->bg.b,
              &state->bg.h, &state->bg.s, &state->bg.v);

 SKIP:
  cycle_colors (state, true);
}


static void
cycle_colors (struct state *state, Boolean init_p)
{
  RGBColorType table[2];
  int hue_tick = 2;

  if (!state->color_p) goto SKIP;

  if (state->rom_version < ROM_35)
    return;

  if (init_p) 
    {
      UInt32 seed = TimGetTicks();
      hue_tick = SysRandom(seed);
    }

  state->fg.h = (state->fg.h + hue_tick) % 360;
  state->bg.h = (state->bg.h + hue_tick + 1) % 360;  /* different rates */

  hsv_to_rgb ( state->fg.h,  state->fg.s,  state->fg.v,
              &state->fg.r, &state->fg.g, &state->fg.b);
  hsv_to_rgb ( state->bg.h,  state->bg.s,  state->bg.v,
              &state->bg.r, &state->bg.g, &state->bg.b);

  table[0].index = FG_PIXEL;
  table[0].r = state->fg.r >> 8;
  table[0].g = state->fg.g >> 8;
  table[0].b = state->fg.b >> 8;

  table[1].index = BG_PIXEL;
  table[1].r = state->bg.r >> 8;
  table[1].g = state->bg.g >> 8;
  table[1].b = state->bg.b >> 8;

  WinPalette (winPaletteSet, FG_PIXEL, 2, table);
  WinSetForeColor (FG_PIXEL);
  WinSetBackColor (BG_PIXEL);

 SKIP:
  clear_borders (state, init_p);
}


static Boolean
do_settings_form (struct state *state, FormPtr form)
{
  FieldPtr field;
  ControlPtr dark, light, c12, c24, secs, mdy, dmy, ymd;

  MemHandle otext, ntext;
  char buf[5];
  char *s;

  /* Write into the text field
   */

  field = FrmGetObjectPtr (form, FrmGetObjectIndex (form, SettingsFieldFPS));
  if (!field)
    {
      ErrDisplay ("FPS field missing");
      return false;
    }
 
  if (state->ticks >= 10)
    {
      buf[0] = ((state->ticks / 10) % 10) + '0';
      buf[1] = (state->ticks % 10) + '0' ;
      buf[2] = 0;
    }
  else
    {
      buf[0] = (state->ticks % 10) + '0';
      buf[1] = 0;
    }

  ntext = (MemHandle) MemHandleNew (StrLen(buf)+1);
  s = (char *) MemHandleLock ((MemHandle) ntext);
  StrCopy (s, buf);
  MemHandleUnlock ((MemHandle) ntext);

  otext = FldGetTextHandle (field);
  FldSetTextHandle (field, ntext);
  /* FldDrawField (field); */

  if (otext) 
    MemHandleFree ((MemHandle) otext);

  /* Write into the checkboxes.
   */

  dark  = FrmGetObjectPtr (form, FrmGetObjectIndex(form,SettingsButtonDark));
  light = FrmGetObjectPtr (form, FrmGetObjectIndex(form,SettingsButtonLight));
  c12   = FrmGetObjectPtr (form, FrmGetObjectIndex(form,SettingsButton12));
  c24   = FrmGetObjectPtr (form, FrmGetObjectIndex(form,SettingsButton24));
  secs  = FrmGetObjectPtr (form, FrmGetObjectIndex(form,SettingsSecondsOnly));
  mdy   = FrmGetObjectPtr (form, FrmGetObjectIndex(form,SettingsMMDDYY));
  dmy   = FrmGetObjectPtr (form, FrmGetObjectIndex(form,SettingsDDMMYY));
  ymd   = FrmGetObjectPtr (form, FrmGetObjectIndex(form,SettingsYYMMDD));

  CtlSetValue (dark,   state->inverted_p);
  CtlSetValue (light, !state->inverted_p);
  CtlSetValue (secs,   state->seconds_only_p);
  CtlSetValue (c12,    state->twelve_hour_time_p && !state->seconds_only_p);
  CtlSetValue (c24,   !state->twelve_hour_time_p && !state->seconds_only_p);
  CtlSetValue (mdy,    state->date_format == MMDDYY);
  CtlSetValue (dmy,    state->date_format == DDMMYY);
  CtlSetValue (ymd,    state->date_format == YYMMDD);


  /* Pop up the dialog box and process events waiting for "OK".
   */
  FrmDoDialog (form);


  /* Read from the checkboxes.
   */

  state->inverted_p = CtlGetValue (dark);

  {
    Boolean old = state->seconds_only_p;
    state->seconds_only_p = CtlGetValue (secs);
    if (state->seconds_only_p != old)
      {
        free_numbers (state);
        if (!init_numbers (state))
          {
            ErrDisplay ("Out of memory.");
            state->seconds_only_p = false;
            init_numbers (state);
          }
      }
  }

  if (!state->seconds_only_p)
    state->twelve_hour_time_p = CtlGetValue (c12);

  state->date_format = (CtlGetValue (dmy) ? DDMMYY :
                        CtlGetValue (ymd) ? YYMMDD :
                        MMDDYY);

  /* Parse the frames-per-second text.
   */
  s = (char *) MemHandleLock ((MemHandle) ntext);
  state->ticks = 0;
  while (*s)
    {
      state->ticks = (state->ticks * 10) + (*s - '0');
      if (state->ticks >= 100) break;
      s++;
    }
  MemHandleUnlock ((MemHandle) ntext);

  FrmDeleteForm (form);

  state->redraw_p = true;
  if (state->ticks <= 0) state->ticks = DEFAULT_FRAME_RATE;
  state->interval = SysTicksPerSecond() / state->ticks;
  state->tick = state->ticks;

  {
    struct daliclock_prefs prefs;
    prefs.inverted_p = state->inverted_p;
    prefs.seconds_only_p = state->seconds_only_p;
    prefs.ticks = state->ticks;

#ifdef HAVE_PREFSETAPPPREFERENCES_20
    PrefSetAppPreferences (CREATOR_ID, PREFS_VERSION, 1,
                           &prefs, sizeof(prefs), true);
#else  /* !HAVE_PREFSETAPPPREFERENCES_20 */
    PrefSetAppPreferences (CREATOR_ID, 1, &prefs, sizeof(prefs));
#endif /* !HAVE_PREFSETAPPPREFERENCES_20 */
  }

  init_colors (state);

  return true;
}


/* Blocks until the exact moment that the wall-clock's seconds advance,
   so that we are animating in lock-step with the reported time.
 */
static void
sync_to_seconds (void)
{
  UInt32 sec1, sec2;
  sec1 = sec2 = TimGetSeconds ();
  while (sec1 == sec2)
    sec2 = TimGetSeconds ();
}


static Boolean
app_handle_event (struct state *state, EventPtr event)
{
  FormPtr form;

  switch (event->eType)
    {
    case menuEvent:
      state->redraw_p = true;
      switch (event->data.menu.itemID)
        {
        case MainMenuAbout:
          form = FrmInitForm (AboutForm);
          FrmDoDialog (form);
          FrmDeleteForm (form);
          return true;

        case MainMenuSettings:
          form = FrmInitForm (SettingsForm);
          return do_settings_form (state, form);

        default:
          return false;
        }

    case keyDownEvent:
      if (event->data.keyDown.chr == ' ')
        {
          state->twelve_hour_time_p = !state->twelve_hour_time_p;
          return true;
        }
      else if (event->data.keyDown.chr == '-' ||
               (event->data.keyDown.chr >= '0' &&
                event->data.keyDown.chr <= '9'))
        {
          state->test_hack = event->data.keyDown.chr;
          return true;
        }
      else if (KeyCurrentState() & keyBitsAll)
        {
          /* Display date of any of the hardware keys (arrows, center-button)
             are pressed or held down. */
          state->display_date = DDateIn;
          return true;
        }
      else
        return false;
      break;

    case penDownEvent:		    /* Display date while pen down */
      state->pen_down_p = true;
      state->display_date = DDateIn;
      return true;
      break;

    case penUpEvent:		    /* leave date ~2 seconds after pen up */
      state->pen_down_p = false;
      return true;
      break;

    case winEnterEvent:
      if (event->data.winEnter.enterWindow == state->window)
        state->active_window_p = 1;
      return false;
      break;

    case winExitEvent:
      if (event->data.winEnter.exitWindow == state->window)
        state->active_window_p = 0;
      return false;
      break;

    case nilEvent:
      {
        Boolean redraw_p = state->redraw_p;

        if (! state->active_window_p)
          return true;

        if (state->rom_version >= ROM_35)
          WinScreenLock (winLockCopy);

        tick_sequence (state);

        if (redraw_p)
          {
            int i;
            state->redraw_p = false;
            for (i = 0; i < countof(state->target_digits); i++)
              state->target_digits[i] = state->current_digits[i] = -2;
            init_colors (state);
          }

        draw_clock (state);

        if (state->rom_version >= ROM_35)
          WinScreenUnlock ();

        if (redraw_p)
          sync_to_seconds ();

        return true;
      }

    default:
      return false;
    }
}

static Boolean
initialize (struct state *state)
{
  SystemPreferencesType sysPrefs;
  struct daliclock_prefs prefs;
  UInt16 prefs_size;
  Boolean got_prefs;

  MemSet (state, sizeof(*state), 0);

  FtrGet (sysFtrCreator, sysFtrNumROMVersion, &state->rom_version);

  PrefGetPreferences (&sysPrefs);

  state->twelve_hour_time_p = !Use24HourFormat (sysPrefs.timeFormat);

  switch (sysPrefs.dateFormat)
    {
    case dfMDYWithSlashes:		/* 12/31/95     */
    case dfMDYLongWithComma:		/* Dec 31, 1995 */
      state->date_format = MMDDYY;
      break;
    case dfDMYWithSlashes:		/* 31/12/95     */
    case dfDMYWithDots:			/* 31.12.95     */
    case dfDMYWithDashes:		/* 31-12-95     */
    case dfDMYLong:			/* 31 Dec 1995  */
    case dfDMYLongWithDot:		/* 31. Dec 1995 */
    case dfDMYLongNoDay:		/* Dec 1995     */
    case dfDMYLongWithComma:		/* 31 Dec, 1995 */
    case dfMYMed:			/* Dec '95      */
      state->date_format = DDMMYY;
      break;
    case dfYMDWithSlashes:		/* 95/12/31     */
    case dfYMDWithDots:			/* 95.12.31     */
    case dfYMDWithDashes:		/* 95-12-31     */
    case dfYMDLongWithDot:		/* 1995.12.31   */
    case dfYMDLongWithSpace:		/* 1995 Dec 31  */
      state->date_format = YYMMDD;
      break;
    default:
      ErrNonFatalDisplayIf (true, "unregognised date format");
      state->date_format = MMDDYY;
      break;
    }


  prefs_size = sizeof(prefs);

#ifdef HAVE_PREFSETAPPPREFERENCES_20
  got_prefs = (PREFS_VERSION ==
               PrefGetAppPreferences (CREATOR_ID, PREFS_VERSION,
                                      &prefs, &prefs_size,
                                      true));
#else  /* !HAVE_PREFSETAPPPREFERENCES_20 */
  got_prefs = (PrefGetAppPreferences (CREATOR_ID, 1, &prefs, prefs_size));
#endif /* !HAVE_PREFSETAPPPREFERENCES_20 */


  state->inverted_p = true;
  state->seconds_only_p = 0;
  state->ticks = DEFAULT_FRAME_RATE;

  if (got_prefs)
    {
      state->inverted_p = prefs.inverted_p;
      state->seconds_only_p = prefs.seconds_only_p;
      if (prefs.ticks > 0)
        state->ticks = prefs.ticks;
    }

  state->redraw_p = true;
  state->display_date = DTime;
  state->test_hack = 0;
  state->interval = SysTicksPerSecond() / state->ticks;
  state->tick = state->ticks;

  WinGetWindowExtent (&state->win_width, &state->win_height);

# ifdef HACK_FRAMEBUFFER
  cache_frame_buffer (state);
# endif /* HACK_FRAMEBUFFER */

  if (!init_numbers (state))
    return false;

  return true;
}


static Boolean
start_app (struct state *state)
{
  FormPtr form;

#if 0
  MemSetDebugMode (memDebugModeCheckOnChange |
                   memDebugModeCheckOnAll |
                   memDebugModeScrambleOnChange |
                   memDebugModeScrambleOnAll |
                   memDebugModeFillFree |
                   memDebugModeAllHeaps |
                   memDebugModeRecordMinDynHeapFree);
#endif

  form = FrmInitForm (MainForm);
  FrmSetActiveForm (form);
  FrmDrawForm (form);

  state->window = WinGetDrawWindow();

  if (state->rom_version < ROM_30)
    state->color_p = false;
  else
    WinScreenMode (winScreenModeGet, NULL, NULL, NULL, &state->color_p);

  init_colors (state);

  return false;
}


static void
stop_app (struct state *state)
{
  state->display_date = DTime;
  free_numbers (state);
  FrmCloseAllForms ();
}


static void
event_loop (struct state *state)
{
  UInt16 error;
  EventType event;

  long delay = 0;

  do {
    UInt32 start, stop;
    long elapsed;

    EvtGetEvent (&event, delay);

    start = TimGetTicks();		/* start timing event execution */

    if (!SysHandleEvent (&event))
      if (!MenuHandleEvent (NULL, &event, &error))
        if (!app_handle_event (state, &event))
          if (state->rom_version >= ROM_35)
            FrmDispatchEvent (&event);

    stop = TimGetTicks();		/* stop timing event execution */


    elapsed = stop - start;
    delay = state->interval - elapsed;
    if (delay < 0) delay = 0;

  } while (event.eType != appStopEvent);
}


static Err
check_rom_version (UInt32 required_version, UInt16 launch_flags)
{
  UInt32 rom_version = 0;
  FtrGet (sysFtrCreator, sysFtrNumROMVersion, &rom_version);
  if (rom_version < required_version)
    {
      if ((launch_flags &
           (sysAppLaunchFlagNewGlobals | sysAppLaunchFlagUIApp)) 
          == (sysAppLaunchFlagNewGlobals | sysAppLaunchFlagUIApp))
        {
          FrmAlert (RomIncompatibleAlert);
      
# ifdef HAVE_APPLAUNCHWITHCOMMAND
          /* PalmOS 1.0 will continuously relaunch this app unless we switch
             to another safe one.  The sysFileCDefaultApp is considered "safe"
          */
          if (rom_version < ROM_20)
            AppLaunchWithCommand (sysFileCDefaultApp,
                                  sysAppLaunchCmdNormalLaunch, 0);
# endif /* HAVE_APPLAUNCHWITHCOMMAND */
        }

      return sysErrRomIncompatible;
    }
  return 0;
}



UInt32 
PilotMain (UInt16 cmd, MemPtr cmdPBP, UInt16 launchFlags)
{
  Err error = check_rom_version (ROM_20, launchFlags);
  if (error)
    return error;

  if (cmd == sysAppLaunchCmdNormalLaunch)
    {
      struct state state;

      if (!initialize (&state))
        return true;

      if (start_app (&state))
        return true;

      event_loop (&state);
      stop_app (&state);
    }
  return 0;
}

/* pose -load_apps daliclock.prc -run_app 'Dali Clock'
   remember to set "ReportScreenAccess=0" in ~/.poserrc
 */
