/* misc.c */

/*
#define FAKE_RISCOS_2
*/

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

#include "os.h"
#include "msgs.h"
#include "swis.h"
#include "sprite.h"
#include "bbc.h"
#include "wimp.h"
#include "template.h"
#include "colourtran.h"
#include "flex.h"
#include "coords.h"
#include "misc.h"
#include "wimpt.h"
#include "win.h"
#include "werr.h"

#include "tracker.h"

#define LGAP      4
#define BGAP     44
#define RGAP     44
#define TGAP     44

#define MARGIN   40

static int offset = 0;
static int wimpver;

int misc_xeig, misc_yeig, misc_pixx, misc_pixy;
wimp_palettestr misc_wimpcols;

os_error *misc_err(char *msg, ...)
{  static os_error eb;
   va_list ap;

   va_start(ap, msg);
   vsprintf(eb.errmess, msgs_lookup(msg), ap);
   va_end(ap);

   return &eb;
}

void misc_boxby(wimp_box *b, int dx, int dy)
{  b->x0 += dx;
   b->y0 += dy;
   b->x1 += dx;
   b->y1 += dy;
}

void misc_boxto(wimp_box *b, int x, int y)
{  misc_boxby(b, x - b->x0, y - b->y0);
}

void misc_clipbox(wimp_box *obox, wimp_box *clip)
{  if (obox->x0 < clip->x0)
      obox->x0 = clip->x0;
   else if (obox->x0 > clip->x1)
      obox->x0 = clip->x1;

   if (obox->y0 < clip->y0)
      obox->y0 = clip->y0;
   else if (obox->y0 > clip->y1)
      obox->y0 = clip->y1;

   if (obox->x1 < clip->x0)
      obox->x1 = clip->x0;
   else if (obox->x1 > clip->x1)
      obox->x1 = clip->x1;

   if (obox->y1 < clip->y0)
      obox->y1 = clip->y0;
   else if (obox->y1 > clip->y1)
      obox->y1 = clip->y1;
}

os_error *misc_template(char *name, wimp_wind **wdefp)
{  wimp_wind *wdef;

   if (wdef = template_syshandle(name), !wdef)
      return misc_err("misc1:Can't find template %s", name);

   *wdefp = wdef;

   return NULL;
}

BOOL misc_overlap(wimp_box *a, wimp_box *b)
{  return a->x0 < b->x1 && a->x1 > b->x0 &&
          a->y0 < b->y1 && a->y1 > b->y0;
}

BOOL misc_commonbox(wimp_box *obox, wimp_box *ibox)
{  if (!misc_overlap(obox, ibox))
      return FALSE;

   obox->x0 = misc_max(obox->x0, ibox->x0);
   obox->y0 = misc_max(obox->y0, ibox->y0);
   obox->x1 = misc_min(obox->x1, ibox->x1);
   obox->y1 = misc_min(obox->y1, ibox->y1);

   return TRUE;
}

void misc_boundbox(wimp_box *obox, wimp_box *ibox)
{  obox->x0 = misc_min(obox->x0, ibox->x0);
   obox->y0 = misc_min(obox->y0, ibox->y0);
   obox->x1 = misc_max(obox->x1, ibox->x1);
   obox->y1 = misc_max(obox->y1, ibox->y1);
}

void misc_makebox(wimp_box *obox, int x0, int y0, int x1, int y1)
{  obox->x0 = misc_min(x0, x1);
   obox->y0 = misc_min(y0, y1);
   obox->x1 = misc_max(x0, x1);
   obox->y1 = misc_max(y0, y1);
}

BOOL misc_inbox(wimp_box *box, int x, int y)
{  return x >= box->x0 && x < box->x1 && y >= box->y0 && y < box->y1;
}

/* Sprite plotting */

typedef struct
{  int mode;
   BOOL notrans;
   sprite_pixtrans trans[16];
   sprite_factors  factors;
} misc_sprinfo;

static struct
{  misc_sprinfo *info;
   int size, used;
} misc__sic;

void misc_newmode(void)
{  misc_xeig = bbc_vduvar(bbc_XEigFactor);
   misc_yeig = bbc_vduvar(bbc_YEigFactor);
   misc_pixx = 1 << misc_xeig;
   misc_pixy = 1 << misc_yeig;

   wimpt_noerr(wimp_readpalette(&misc_wimpcols));

   if (misc__sic.size > 0)
   {  misc__sic.size =
      misc__sic.used = 0;
      free(misc__sic.info);
      misc__sic.info = NULL;
   }
}

os_error *misc_sprput(sprite_area *area, sprite_id *id, int x, int y)
{  sprite_info info;
   os_error *err;
   int hi, lo, mid;

   if (err = sprite_readsize(area, id, &info), err)
      return err;

   /* Find mapping for this mode in cache, and if not present add it */

   for (lo = 0, hi = misc__sic.used; lo <= hi; )
   {  mid = (lo + hi) / 2;

      if (misc__sic.info[mid].mode < info.mode)
         lo = mid + 1;
      else if (misc__sic.info[mid].mode > info.mode)
         hi = mid - 1;
      else
         goto gotinfo;
   }

   /* Info wasn't found so make room for it */

   if (misc__sic.used >= misc__sic.size)
   {  if (misc__sic.size > 0)
      {  void *newp;

         if (newp = realloc(misc__sic.info, sizeof(misc_sprinfo) * (10 + misc__sic.size)), !newp)
            return misc_err("misc2");

         misc__sic.info = newp;
      }
      else
      {  if (misc__sic.info = malloc(sizeof(misc_sprinfo) * 10), !misc__sic.info)
            return misc_err("misc2");
      }
      misc__sic.size += 10;
   }

   for (mid = 0; mid < misc__sic.used && misc__sic.info[mid].mode < info.mode; mid++)
      ;

   if (mid < misc__sic.used)
      memmove(&misc__sic.info[mid + 1],
              &misc__sic.info[mid],
              sizeof(misc_sprinfo) * (misc__sic.used - mid));

   misc__sic.info[mid].mode = info.mode;
   
   if (err = wimp_readpixtrans(area, id, &misc__sic.info[mid].factors, misc__sic.info[mid].trans), err)
      return err;

   if (bbc_modevar(info.mode, bbc_NColour) <= 15)
   {  misc__sic.info[mid].notrans = TRUE;

      for (lo = 0; lo < 16; lo++)
      {  if (misc__sic.info[mid].trans[lo] != lo)
         {  misc__sic.info[mid].notrans = FALSE;
            break;
         }

         if (misc__sic.info[mid].factors.xmag != misc__sic.info[mid].factors.xdiv ||
             misc__sic.info[mid].factors.ymag != misc__sic.info[mid].factors.ydiv)
            misc__sic.info[mid].notrans = FALSE;
      }
   }
   else
      misc__sic.info[mid].notrans = FALSE;

   tracker_printf("Added info for mode %d sprites at position %d (%s)\n",
               info.mode, mid, misc__sic.info[mid].notrans ? "notrans" : "trans");

gotinfo:

   if (misc__sic.info[mid].notrans)
      return sprite_put_given(area, id, 8, x, y);
   else
      return sprite_put_scaled(area, id, 8, x, y, &misc__sic.info[mid].factors,
                                                  misc__sic.info[mid].trans);
}

#ifdef DEVELOPER
os_error *misc__fix(char *file, int line, os_error *err)
{  if (err)
      tracker_printf("File %s, line %d: %s\n", file, line, err->errmess);
   else
      tracker_printf("File %s, line %d: misc_fix() invoked with NULL argument!\n");
   return err;
}
#endif

#if 0
os_error *misc_mousespeed(int dx, int dy)
{  int x, y;
   os_error *err;
   char wb[3];

   x = 194;
   if (err = os_byte(161, &x, &y), err)
      return misc_fix(err);

   wb[0] = 2;
   wb[1] = (dx == 0 ? y : dx);
   wb[2] = (dy == 0 ? y : dy);

   return os_word(21, wb);
}
#endif

os_error *misc_settype(char *name, int ftype)
{  os_filestr fcb;

   fcb.action   = 18;
   fcb.name     = name;
   fcb.loadaddr = ftype;

   return os_file(&fcb);
}

os_error *misc_windowbox(wimp_box *box, BOOL shrink)
{  wimp_box scrn;
   int sx, sy;
   int bw, bh;

   scrn.x0 = LGAP;
   scrn.y0 = BGAP;
   scrn.x1 = ((bbc_vduvar(bbc_XWindLimit) - 1) << bbc_vduvar(bbc_XEigFactor)) - RGAP;
   scrn.y1 = ((bbc_vduvar(bbc_YWindLimit) - 1) << bbc_vduvar(bbc_YEigFactor)) - TGAP;

   sx = (scrn.x0 + scrn.x1) / 2;
   sy = (scrn.y0 + scrn.y1) / 2;

   if (shrink)
   {  bw = (scrn.x1 - scrn.x0) / 2;
      bh = (scrn.y1 - scrn.y0) / 2;
   }
   else
   {  bw = box->x1 - box->x0;
      bh = box->y1 - box->y0;
   }

   box->x0 = sx - bw / 2;
   box->y0 = sy - bh / 2;
   box->x1 = box->x0 + bw;
   box->y1 = box->y0 + bh;

   if (box->y0 + offset - MARGIN < scrn.y0)
      offset = scrn.y1 - box->y1 - MARGIN;

   box->y0 += offset;
   box->y1 += offset;

   offset -= 44;

   return NULL;
}

os_error *misc_opendir(char *dirname)
{  os_regset regs;
   char cbuf[300];

   sprintf(cbuf, "Filer_OpenDir %s", dirname);
   regs.r[0] = (int) cbuf;

   return os_swix(OS_CLI, &regs);
}

/* Safe memory management stuff */

os_error *misc_falloc(void **ptr, int size)
{

   if (!flex_alloc(ptr, size))
      return misc_err("misc2");

   return NULL;
}

os_error *misc_fextend(void **ptr, int by)
{  if (!flex_extend(ptr, by))
      return misc_err("misc2");

   return NULL;
}

os_error *misc_fmidextend(void **ptr, int at, int by)
{  if (!flex_midextend(ptr, at, by))
      return misc_err("misc2");

   return NULL;
}

int misc_fsize(void **ptr)
{  return flex_size(ptr);
}

void misc_ffree(void **ptr)
{  if (*ptr) flex_free(ptr);
}

#ifdef MEMTRACE

typedef struct
{  char *file;
   int line;
   int size;
} meminfo;

typedef struct remember
{  struct remember *link;
   int tag;  /* This is either the anchor for a flex block, or the returned
              * address for a malloc block.
              */

   meminfo original;
   meminfo latest;
} remember;

static remember *allmem;

static remember *findmem(int tag)
{  remember *r;

   for (r = allmem; r; r = r->link)
      if (r->tag == tag)
         return r;

   return NULL;
}

static remember *unlink(remember *list, remember *r)
{  if (r == list)
      return list->link;

   list->link = unlink(list->link, r);
   return list;
}

static void losemem(char *file, int line, int tag)
{  remember *r;

   if (r = findmem(tag), !r)
      werr(TRUE, "%s: %d, Attempt to free nonexistant block %08x", file, line, tag);

   allmem = unlink(allmem, r);
   (free)(r);
}

static void remem(char *file, int line, int size, int tag)
{  remember *r;
   meminfo  *i;

   if (r = findmem(tag), r)
      i = &(r->latest);
   else
   {  if (r = (malloc)(sizeof(remember)), !r)
         werr(TRUE, "Not enough memory to remember allocation");

      r->tag = tag;
      i = &(r->original);
      r->latest.file = NULL;
      r->link = allmem;
      allmem = r;
   }

   i->file = file;
   i->line = line;
   i->size = size;
}

void __memreport(void)
{  remember *r;

   tracker_printf("\fMemory usage\n\n");
   tracker_printf("%-10s%-34s%s\n\n", "Tag", "Original", "Latest");

   for (r = allmem; r; r = r->link)
   {
      tracker_printf("%08x, %16s, %5d, %8d ", r->tag,
                                              r->original.file,
                                              r->original.line,
                                              r->original.size);

      if (r->latest.file)
         tracker_printf("%16s, %5d, %8d\n", r->latest.file,
                                            r->latest.line,
                                            r->latest.size);
      else
         tracker_printf("unchanged\n");
   }
}

/* Replace memory allocation routines */

int __flex_alloc(char *name, int line, flex_ptr anchor, int n)
{  int ok = (flex_alloc)(anchor, n);

   if (ok) remem(name, line, n, (int) anchor);
   return ok;
}

int __flex_extend(char *name, int line, flex_ptr anchor, int n)
{  int ok = (flex_extend)(anchor, n);
   if (ok) remem(name, line, n, (int) anchor);
   return ok;
}

void __flex_free(char *name, int line, flex_ptr anchor)
{  name = name;
   line = line;
   losemem(name, line, (int) anchor);
   (flex_free)(anchor);
}

void *__malloc(char *name, int line, size_t size)
{  void *m = (malloc)(size);

   if (m)
      remem(name, line, size, (int) m);

   return m;
}

void *__realloc(char *name, int line, void *m, size_t size)
{  void *m2;

   m2 = (realloc)(m, size);

   if (m2)
   {  losemem(name, line, (int) m);
      if (size) remem(name, line, size, (int) m2);
   }

   return m2;
}

void __free(char *name, int line, void *m)
{  name = name;
   line = line;

   if (m)
      losemem(name, line, (int) m);
   (free)(m);
}

os_error *(misc_malloc)(char *name, int line, void **ptr, int size)
{  void *mem = NULL;

   if (size && (mem = __malloc(name, line, size), !mem))
      return misc_err("misc2");

   *ptr = mem;
   return NULL;
}

void (misc_free)(char *name, int line, void **ptr)
{  if (*ptr) __free(name, line, *ptr);
   *ptr = NULL;
}

#else
void __memreport(void) {}

os_error *misc_malloc(void **ptr, int size)
{  void *mem = NULL;

   if (size && (mem = malloc(size), !mem))
      return misc_err("misc2");

   *ptr = mem;
   return NULL;
}

void misc_free(void **ptr)
{  if (*ptr) free(*ptr);
   *ptr = NULL;
}

#endif

os_error *misc_force(wimp_w w)
{  wimp_wstate state;
   wimp_redrawstr r;
   os_error *err;

   r.w = w;

   if (err = wimp_get_wind_state(r.w, &state), err)
      return misc_fix(err);

   r.box = state.o.box;
   coords_box_toworkarea(&r.box, (coords_cvtstr *) &state.o.box);

   return wimp_force_redraw(&r);
}

os_error *misc_chtitle(wimp_w w)
{  wimp_wstate state;
   wimp_redrawstr r;
   os_error *err;

   r.w = w;

   if (err = wimp_get_wind_state(r.w, &state), err)
      return misc_fix(err);

   if (!(state.flags & wimp_WOPEN))
      return NULL;

   if (err = wimp_getwindowoutline(&r), err)
      return misc_fix(err);

   r.box.y0 = state.o.box.y1;
   r.w = -1;

   return wimp_force_redraw(&r);
}

void misc_growbox(wimp_box *box, int dx, int dy)
{  box->x0 -= dx;
   box->y0 -= dy;
   box->x1 += dx;
   box->y1 += dy;
}

static BOOL ukproc(wimp_eventstr *e, void *handle)
{  handle = handle;

   switch (e->e)
   {  case wimp_ESEND:
      case wimp_ESENDWANTACK:
         switch (e->data.msg.hdr.action)
         {  case wimp_PALETTECHANGE:
               misc_newmode();
               break;
         }
   }
   return FALSE;
}

int misc_wimpver(void)
{  return wimpver;
}

void misc_report(char *tag, ...)
{  va_list ap;
   static char msg[256];

   va_start(ap, tag);
   vsprintf(msg, msgs_lookup(tag), ap);
   va_end(ap);

   werr(FALSE, msg);
}

int misc_min(int a, int b)
{  return a < b ? a : b;
}

int misc_max(int a, int b)
{  return a > b ? a : b;
}

char *misc_leaf(char *fname)
{  char *leafp;

   for (leafp = fname; *fname; ++fname)
   {  if (*fname == '.' || *fname == ':')
         leafp = fname+1;
   }

   return leafp;
}

os_error *misc_strdup(char **dest, const char *source)
{  os_error *err;

   if (err = misc_malloc((void **) dest, strlen(source) + 1), err)
      return misc_fix(err);

   strcpy(*dest, source);

   return NULL;
}

int misc_strcicmp(const char *s1, const char *s2)
{  while (*s1 && toupper(*s1) == toupper(*s2))
      s1++, s2++;

   return toupper(*s1) - toupper(*s2);
}

os_error *misc_counticons(wimp_w w, int *countp)
{  wimp_i i;
   wimp_icon icn;
   os_error *err;

   for (i = 0; ; i++)
   {  if (err = wimp_get_icon_info(w, i, &icn), err)
         return misc_fix(err);

      if (icn.flags == wimp_IDELETED)
      {  *countp = i;
         return NULL;
      }
   }

   return NULL;
}

os_error *misc_icontextlen(wimp_w w, wimp_i i, int *lenp)
{  wimp_icon icn;
   os_error *err;
   char *s;
   int l;

   if (err = wimp_get_icon_info(w, i, &icn), err) return misc_fix(err);

   if (icn.flags & wimp_ITEXT)
   {  if (icn.flags & wimp_INDIRECT)
         s = icn.data.indirecttext.buffer;
      else
         s = icn.data.text;

      for (l = 0; *s >= ' '; s++, l++)
         ;

      *lenp = l;
   }
   else
      *lenp = 0;

   return NULL;
}

/* Advance the caret to the end of the next writeable icon if there is one,
 * and return FALSE through wrap or if there are no more writeables put the
 * caret into limbo (still in the window, but no particular icon) and return
 * TRUE through wrap. If misc_nextwriteable() is called when the caret is in
 * 'limbo' the first writeable will be selected. Circular caret motion can
 * be created by calling this function, checking wrap, and if it's set call
 * this function again.
 */
os_error *misc_nextwriteable(wimp_w w, BOOL *wrap)
{  os_error *err;
   int icons;
   wimp_caretstr c;
   wimp_i i;
   wimp_icon icn;

   if (err = misc_counticons(w, &icons), err) return misc_fix(err);

   if (err = wimp_get_caret_pos(&c), err) return misc_fix(err);

   /* c.i + 1 handles case where i == -1 correctly */

   for (i = (c.i < 0 || c.i >= icons) ? 0 : c.i + 1; i < icons; i++)
   {  if (err = wimp_get_icon_info(w, i, &icn), err) return misc_fix(err);

      if (((icn.flags >> 12) & 0x0E) == 0x0E)
         break;
   }

   if (i < icons)
   {  c.w = w;
      c.i = i;
      c.x = 0;
      c.y = 0;
      c.height = -1;

      if (err = misc_icontextlen(w, i, &c.index), err)
         return misc_fix(err);

      *wrap = FALSE;

      if (err = wimp_set_caret_pos(&c), err)
         return misc_fix(err);

   }
   else
      *wrap = TRUE;

   return NULL;
}

os_error *misc_prevwriteable(wimp_w w, BOOL *wrap)
{  os_error *err;
   int icons;
   wimp_caretstr c;
   wimp_i i;
   wimp_icon icn;

   if (err = misc_counticons(w, &icons), err) return misc_fix(err);
   if (err = wimp_get_caret_pos(&c), err) return misc_fix(err);

   for (i = (c.i < 0 || c.i >= icons) ? icons-1 : c.i-1; i >= 0; i--)
   {  if (err = wimp_get_icon_info(w, i, &icn), err) return misc_fix(err);

      if (((icn.flags >> 12) & 0x0E) == 0x0E)
         break;
   }

   if (i >= 0)
   {  c.w = w;
      c.i = i;
      c.x = 0;
      c.y = 0;
      c.height = -1;

      if (err = misc_icontextlen(w, i, &c.index), err)
         return misc_fix(err);

      *wrap = FALSE;

      if (err = wimp_set_caret_pos(&c), err)
         return misc_fix(err);

   }
   else
      *wrap = TRUE;

   return NULL;
}

os_error *misc_fillrect(wimp_box *box)
{  if (box->x1 > box->x0 && box->y1 > box->y0)
   {  bbc_move(box->x0, box->y0);
      bbc_plot(bbc_RectangleFill | bbc_DrawAbsFore,
               box->x1 - 1, box->y1 - 1);
   }
   return NULL;
}

os_error *misc_setfontrgb(int fh, int bcol, int fcol)
{  os_regset regs;

   regs.r[0] = (int) fh;
   regs.r[1] = bcol;
   regs.r[2] = fcol;
   regs.r[3] = 14;

   return os_swix(ColourTrans_SetFontColours, &regs);
}

os_error *misc_setfontcolours(int fh, int bcol, int fcol)
{  os_regset regs;

   regs.r[0] = (int) fh;
   regs.r[1] = misc_wimpcols.c[bcol].word;
   regs.r[2] = misc_wimpcols.c[fcol].word;
   regs.r[3] = 14;

   return os_swix(ColourTrans_SetFontColours, &regs);
}

os_error *misc_settitle(wimp_w w, char *title)
{  os_error *err;
   int nicons;
   wimp_winfo *info;

   if (err = misc_counticons(w, &nicons), err) return err;

   if (err = misc_malloc((void **) &info, sizeof(wimp_winfo) + sizeof(wimp_icon) * nicons), err)
      return err;

   info->w = w;

   if (err = wimp_get_wind_info(info), err) goto fail;

   strncpy(info->info.title.indirecttext.buffer, title, info->info.title.indirecttext.bufflen-1);
   info->info.title.indirecttext.buffer[info->info.title.indirecttext.bufflen-1] = '\0';

   misc_free((void **) &info);

   return misc_chtitle(w);

fail:
   misc_free((void **) &info);
   return err;
}

int misc_init(char *taskname)
{  misc__sic.size =
   misc__sic.used = 0;
   misc__sic.info = NULL;
   misc_newmode();
   win_add_unknown_event_processor(ukproc, NULL);
#ifdef FAKE_RISCOS_2
   wimpt_init(taskname);
   wimpver = 200;
   return 200;
#else
   return wimpver = wimpt_init(taskname), wimpver;
#endif
}
