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

#include "drawfdiag.h"
#include "drawfobj.h"
#include "wimp.h"
#include "wimpt.h"
#include "win.h"
#include "event.h"
#include "baricon.h"
#include "res.h"
#include "menu.h"
#include "template.h"
#include "dbox.h"
#include "werr.h"
#include "xfersend.h"
#include "saveas.h"
#include "flex.h"
#include "heap.h"
#include "font.h"

#define UNUSED(x) ((x) = (x))

#define APP_NAME "DrawFont"
#define FONTNAMES 100

#define icon_menu_INFO 1
#define icon_menu_FONT 2
#define icon_menu_SIZE 3
#define icon_menu_TEXT 4
#define icon_menu_SAVE 5
#define icon_menu_QUIT 6

/******************************* TYPEDEFS *******************************/

typedef struct {
  char ident[4];
  char bpp;
  char version;
  short dsize;

  short x0;
  short y0;
  short xd;
  short yd;

  int offset[9];
} font_hdr;

typedef struct {
  char name[40];
  int  rsvd1;
  int  rsvd2;
  char chars;
  char rsvd[3];

  char mapping[256];
} metrics_hdr;

typedef struct {
  int x;
  int y;
} point;

/******************************* GLOBAL DATA *******************************/

wimp_i baricon_handle;

char buff[256];                            /* general purpose buffer */

menu icon_menu;                            /* menus */
menu font_menu;
menu size_menu;
menu text_menu;

char *fontnames[FONTNAMES];                /* font list */

char textstring[256];                      /* user input data */
int cfont = 0;
char sizestring[5] = "120";
int fontsize;

int xy_bits;                               /* co-ordinate system */
float xy_scale;

char *outlines = NULL;                     /* file blocks */
char *metrics = NULL;

draw_diag diag;                            /* Draw file structures */
draw_objectType obj;

point pos;                                 /* current point */


/******************************** FUNCTIONS ********************************/

void null_proc(wimp_i icon)
{
  UNUSED(icon);
}

int sgn8(int n)
{
  return (n & 0x0000080) ? (n | 0xffffff00) : n;
}

int sgn12(int n)
{
  return (n & 0x00000800) ? (n | 0xfffff000) : n;
}

void getxy(char **p, int *x, int *y, int xy_bits, point pos)
{
int d;

  switch (xy_bits)
  {
  case 8:
    *x = (int) (sgn8(*((*p)++)) * xy_scale) + pos.x;
    *y = (int) (sgn8(*((*p)++)) * xy_scale) + pos.y;
    break;

  case 12:
    d = *((*p)++) + *((*p)++) * 0x100 + *((*p)++) * 0x10000;
    *x = (int) (sgn12(d & 0x00000fff) * xy_scale) + pos.x;
    *y = (int) (sgn12(d >> 12) * xy_scale) + pos.y;
    break;
  }
}

int char_offset(int i, char *p)
{
font_hdr *h = (font_hdr *) p;
int chunk_off = h->offset[i/32];
int char_off;
int off;

  if (chunk_off == 0)
    return 0;

  char_off = ((int *) (p + chunk_off))[i % 32];
  if (char_off == 0)
    return 0;

  off = chunk_off + char_off;
  if (p[off] & 0x08 == 0)
    return 0;

  return off;
}

char *load_file(char *filename, BOOL outlines)
{
os_filestr f;
char *p;

  f.action = 15;                           /* read size of file */
  f.name   = filename;
  f.start  = (int) "Font$Path";
  if (wimpt_complain(os_file(&f)) || f.action != 1)
    return NULL;

  if (outlines && (f.start < 256))         /* borrowed outlines */
  {
    werr(FALSE, "Not a real Outlines file");
    return NULL;
  }

  p = (char *) heap_alloc(f.start);        /* allocate memory for file */
  if (p == NULL)
  {
    werr(FALSE, "Unable to claim memory");
    return NULL;
  }

  f.action   = 14;                         /* read file into memory */
/*f.name     = filename;  ** not needed ** */
  f.loadaddr = (int) p;
  f.execaddr = 0;
  f.start    = (int) "Font$Path";
  if (wimpt_complain(os_file(&f)))
  {
    heap_free((void *) p);
    return NULL;
  }

  return p;
}

void tidy_up(char *msg)
{
  werr(FALSE, msg);

  if (outlines)
    heap_free((void *) outlines);

  if (metrics)
    heap_free((void *) metrics);

  if (obj.path)
    flex_free((void **) &obj.path);

  if (diag.data)
    flex_free((void **) &diag.data);
}

BOOL start_up(void)
{
char *fontname = fontnames[cfont];
char *ptr;
font_hdr *h;
draw_box box;

int i;

  pos.x = 0;                               /* initialise current point */
  pos.y = 0;

  fontsize = (atoi(sizestring)) * 640;     /* read font size from menu */

  sprintf(buff, "%s.Outlines", fontname);  /* load outlines file */
  if ((outlines = load_file(buff, TRUE)) == 0)
    return FALSE;

  ptr = outlines + sizeof(font_hdr);       /* read font name from outlines file*/
  fontname = ptr + *((short *) ptr);

  h = (font_hdr *) outlines;               /* point h at outlines file header */

  if (h->bpp != 0)
  {
    tidy_up("Not an Outlines file");
    return FALSE;
  }

  for (i = 0; i < 8; i++)                  /* mark empty chunks with offset = 0 */
    if (h->offset[i] == h->offset[i+1])
      h->offset[i] = 0;

  xy_scale = fontsize / h->dsize;          /* scale outlines to required size */


  sprintf(buff, "%s.IntMetrics", fontname);/* load metrics file */
  if ((metrics = load_file(buff, TRUE)) == 0)
  {
    tidy_up("Couldn't load IntMetrics file");
    return FALSE;
  }

  if (flex_alloc((void **) &diag.data, 40) == 0) /* write file header */
  {
    tidy_up("Unable to claim memory");
    return FALSE;
  }

  box.x0 = 0;
  box.y0 = 0;
  box.x1 = 0;
  box.y1 = 0;
  draw_create_diag(&diag, "DrawFont", box);

  if (flex_alloc((void **) &obj.path, sizeof(draw_pathstrhdr)) == 0)
  {
    tidy_up("Unable to claim memory");
    return FALSE;
  }

  obj.path->tag = draw_OBJPATH;

  obj.path->fillcolour = 0x00000000;  /* = black */
  obj.path->pathcolour = 0xffffffff;  /* = none */
  obj.path->pathwidth  = 0;           /* = thin */
  obj.path->pathstyle.joincapwind = 0x40; /* = mitred joins, butt caps, even-odd winding, no dash */
  obj.path->pathstyle.reserved8   = 0;
  obj.path->pathstyle.tricapwid   = 0;
  obj.path->pathstyle.tricaphei   = 0;

  return TRUE;
}

BOOL add_char(int c, point p, BOOL recursive)
{
Path_eleptr e;
char data;
int xy_bits;
int o = char_offset(c, outlines);
char *ptr = outlines + o + 1;                   /* point ptr at start of char definition */

  if (o == 0)
    return TRUE;

  xy_bits = (outlines[o] & 0x01) ? 12 : 8;      /* select 8 or 12 bit definitions */
  ptr += (xy_bits * 4)/8;                       /* skip bounding box */

  do
  {
    obj.path->size = flex_size((void **) &obj.path);

    switch (data = *(ptr++) & 0x0f, data & 0x03)
    {
    case 0:
      switch (data & 0x0c)
      {
      case 0x04:                         /* skip over skeleton lines */
        while (data = *(ptr++), data);
        break;

      case 0x08:                         /* composite character inclusions */
        while (data = *(ptr++), data)
        {
        point offset;

          getxy(&ptr, &offset.x, &offset.y, xy_bits, p);

          if (!recursive)
            add_char(data, offset, TRUE);
        }
        break;
      }
      break;

    case 0x01:                           /* move */
      if (flex_extend((void **) &obj.path, obj.path->size + sizeof(Path_movestr)) == 0)
      {
        tidy_up("Unable to claim memory");
        return FALSE;
      }

      e.move = (Path_movestr *) ((char *) obj.path + obj.path->size);
      e.move->tag = draw_PathMOVE;
      getxy(&ptr, &e.move->x, &e.move->y, xy_bits, p);
      break;

    case 0x02:                           /* line */
      if (flex_extend((void **) &obj.path, obj.path->size + sizeof(Path_linestr)) == 0)
      {
        tidy_up("Unable to claim memory");
        return FALSE;
      }

      e.line = (Path_linestr *) ((char *) obj.path + obj.path->size);
      e.line->tag = draw_PathLINE;
      getxy(&ptr, &e.line->x, &e.line->y, xy_bits, p);
      break;

    case 0x03:                           /* curve */
      if (flex_extend((void **) &obj.path, obj.path->size + sizeof(Path_curvestr)) == 0)
      {
        tidy_up("Unable to claim memory");
        return FALSE;
      }

      e.curve = (Path_curvestr *) ((char *) obj.path + obj.path->size);
      e.curve->tag = draw_PathCURVE;
      getxy(&ptr, &e.curve->x1, &e.curve->y1, xy_bits, p);
      getxy(&ptr, &e.curve->x2, &e.curve->y2, xy_bits, p);
      getxy(&ptr, &e.curve->x3, &e.curve->y3, xy_bits, p);
      break;
    }
  } while (data);

  return TRUE;
}

BOOL finish_off(char *save_path)
{
os_filestr f;

  heap_free((void *) metrics);             /* free file memory */
  heap_free((void *) outlines);
  flex_free((void **) &obj.path);          /* free path buffer */

  f.action   = 10;                         /* write Draw file */
  f.name     = save_path;
  f.loadaddr = 0xaff;
  f.start    = (int) diag.data;
  f.end      = (int) diag.data + diag.length;

  if (wimpt_complain(os_file(&f)))
    return FALSE;

  flex_free((void **) &diag.data);         /* free diagram memory */

  return TRUE;
}

BOOL save_it(char *output, void *handle)
{
metrics_hdr *m;
short *x0;
short *y0;
short *x1;
short *y1;
short *widths;

int i;

  UNUSED(handle);

  if (!start_up())
    return FALSE;

  m = (metrics_hdr *) metrics;             /* point m at metrics file header */
  x0 = (short *) (metrics + sizeof(metrics_hdr)); /* setup array base pointers */
  y0 = x0 + m->chars;
  x1 = y0 + m->chars;
  y1 = x1 + m->chars;
  widths = y1 + m->chars;

  for (i = 0; i < strlen(textstring); i++) /* write character definitions */
  {
  draw_object han;
  draw_error  err;
  int c = textstring[i];

    if (flex_extend((void **) &obj.path, sizeof(draw_pathstrhdr)) == 0)
    {
      tidy_up("Unable to claim memory");
      return FALSE;
    }
    obj.path->size = sizeof(draw_pathstrhdr);

    if (!add_char(c, pos, FALSE))
    {
      tidy_up("Unable to claim memory");
      return FALSE;
    }

    if (obj.path->size > sizeof(draw_pathstrhdr))
    {
    Path_eleptr e;

      if (flex_extend((void **) &obj.path, obj.path->size + sizeof(Path_termstr)) == 0)
      {
        tidy_up("Unable to claim memory");
        return FALSE;
      }
      e.term = (Path_termstr *) ((char *) obj.path + obj.path->size);
      e.term->tag = draw_PathTERM;
      obj.path->size += sizeof(Path_termstr);

      obj.path->bbox.x0 = x0[m->mapping[c]] * fontsize / 1000 + pos.x;
      obj.path->bbox.y0 = y0[m->mapping[c]] * fontsize / 1000;
      obj.path->bbox.x1 = x1[m->mapping[c]] * fontsize / 1000 + pos.x;
      obj.path->bbox.y1 = y1[m->mapping[c]] * fontsize / 1000;

      if (flex_extend((void **) &diag.data, flex_size((void **) &diag.data) + obj.path->size) == 0)
      {
        tidy_up("Unable to claim memory");
        return FALSE;
      }

      if (!draw_createObject(&diag, obj, draw_LastObject, TRUE, &han, &err))
      {
        tidy_up("Failed to create path segment");
        return FALSE;
      }
    }

    pos.x += (widths[m->mapping[c]] * fontsize / 1000);
  }

  return finish_off(output);
}

void infobox(void)
{
dbox  d;

  if ((d = dbox_new("ProgInfo")) != NULL)
  {
    dbox_show(d);
    dbox_fillin(d);
    dbox_dispose(&d);
  }
}

void icon_menuproc(void *handle, char *hit)
{
  UNUSED(handle);

  switch (hit[0])
  {
  case icon_menu_INFO:
    infobox();
    break;

  case icon_menu_FONT:
    if (hit[1] == 0)
      break;
    menu_setflags(font_menu, cfont + 1, 0, 0);
    cfont = hit[1] - 1;
    menu_setflags(font_menu, cfont + 1, 1, 0);
    break;

  case icon_menu_SAVE:
    saveas(0xaff, "DrawFile", 0, save_it, 0, 0, 0);
    break;

  case icon_menu_QUIT:
    exit(0);
  }
}

void make_menus(void)
{
int count = 0;
int i = 0;
char *fontbuff;
                                           /* Main menu */
  if ((icon_menu = menu_new(APP_NAME, ">Info| Font, Size, Text| >Save, Quit")) == NULL)
    exit(1);

                                           /* Font menu */
  if ((font_menu = menu_new("Fonts", "")) == NULL)
    exit(1);

  if (flex_alloc((void **) &fontbuff, 0) == 0)
    werr(TRUE, "Unable to claim memory");

  buff[0] = ',';

  while (TRUE)
  {
  int size;

    font_list(&buff[1], &count);           /* read next font name */

    if (count == -1)
      break;

    size = flex_size((void **) &fontbuff); /* claim memory for font name */
    if (flex_extend((void **) &fontbuff, size + strlen(&buff[1]) + 1) == 0)
      werr(TRUE, "Unable to claim memory");

    strcpy(fontbuff + size, &buff[1]);     /* add name to font list */
    fontnames[i++] = fontbuff + size;
    if (i == FONTNAMES)
      werr(TRUE, "Too many fonts");
    
    menu_extend(font_menu, buff);          /* add name to font menu */
  }

  if (i == 0)
    werr(TRUE, "No fonts available");

  menu_setflags(font_menu, 1, 1, 0);
  menu_submenu(icon_menu, icon_menu_FONT, font_menu);

                                           /* Font size submenu */
  if ((size_menu = menu_new("Font size", "9999")) == NULL)
    exit(1);
  menu_make_writeable(size_menu, 1, sizestring, 4, "a0-9");
  menu_submenu(icon_menu, icon_menu_SIZE, size_menu);

                                           /* Text string submenu */
  if ((text_menu = menu_new("Text string", "01234567890123456789")) == NULL)
    exit(1);
  menu_make_writeable(text_menu, 1, textstring, 256, "");
  menu_submenu(icon_menu, icon_menu_TEXT, text_menu);
}

void initialise(void)
{
  wimpt_init(APP_NAME);
  res_init(APP_NAME);

  template_init();
  dbox_init();

  flex_init();
  heap_init(TRUE);

  make_menus();

  baricon_handle = baricon("!drawfont", 1, null_proc);
  if (!event_attachmenu(win_ICONBAR, icon_menu, icon_menuproc, 0))
    exit(1);
}

/****************************** MAIN PROGRAM *******************************/

int main()
{
  initialise();

  while (TRUE)
    event_process();

  return 0;
}
