/***********************************************************************
 *
 * NeWS HyperTIES formatter
 * Don Hopkins
 *
 ***********************************************************************/

#include <stdio.h>
#include <sys/param.h>
#include "fmt.h"
#include "entry.h"
#include "master-index.h"

/**********************************************************************/

#define MAX_STR_LEN 512

/**********************************************************************/

/*
 * Font metrics cache
 * 
 * This implements a font metrics cache in the client.
 * When a font of some point size not in the cache is needed, NeWS is 
 * queried for the height, ascent, and descent of the font, and the
 * widths of all 256 characters. 
 */

/*
 * The metrics are just in a singly linked list right now. This should
 * eventually be changed to use a tree so looking up fonts is faster. 
 */

struct font_metrics {
  char *name;
  int size;
  int ascent;
  int descent;
  int height;
  int *widths;
  struct font_metrics *next;
};

struct font_metrics *cached_metrics = NULL;

/*
 * Query the NeWS server for the metrics of a font of a given name and point. 
 * Return NULL if it was not found.
 * Allocate a font_metrics struct, fill it in and load the metrics into it,
 * add it to the cache, and return a pointer to it.
 */
struct font_metrics *
get_metrics(name, size)
     char *name;
     int size;
{
  int i, c;
  struct font_metrics *metrics;

  do_find_font(name, size);
  ps_flush_PostScript();

  while (1) {
    if (psio_error(PostScriptInput)) {
      fprintf(stderr, "Error on input!\n");
      bomb(1);
    } else if (found_font()) {
      metrics = (struct font_metrics *)calloc(1, sizeof(struct font_metrics));
      if (metrics == NULL ||
	  (metrics->widths = (int *)calloc(256, sizeof(int))) == NULL) {
	fprintf(stderr, "Malloc failed!\n");
	bomb(1);
      }
      metrics->name = (char *)malloc(strlen(name)+1);
      strcpy(metrics->name, name);
      metrics->size = size;
      do_get_metrics(&metrics->height, &metrics->ascent, &metrics->descent);
      /* Look out! Here come 256 integers! */
      for (i=0; i<256; i++) {
	pscanf(PostScriptInput, "d", &metrics->widths[i]);
      }
      metrics->next = cached_metrics;
      cached_metrics = metrics;
      return(metrics);
    } else if(no_font()) {
      fprintf(stderr, "Font %s %d not found!\n", name, size);
      return(NULL);
    } else if psio_eof(PostScript) {
      fprintf(stderr, "Lost server!\n");
      bomb(1);
    } else {
      c = psio_getc(PostScriptInput);
      printf("Bizarre input '%c' (0x%X)!\n", c, c);
      if (c < 0)
	bomb(1);
    }
  }
}

/*
 * Look in the cache for the metrics of a font of a given name and size,
 * and return that if found, otherwise, query the server for the font,
 * and return the result of that.
 */
struct font_metrics *
find_metrics(name, size)
     char *name;
     int size;
{
  register struct font_metrics *cache = cached_metrics;

  while (cache != NULL) {
    if (cache->size == size)
      if (strcmp(cache->name, name) == 0)
	return(cache);
    cache = cache->next;
    }
  return(get_metrics(name, size));
}

/*
 * Determine the length of a string, using a given set of font metrics.
 */
int
string_width(metrics, string)
     struct font_metrics *metrics;
     char *string;
{
  int width;

  for (width=0; *string != '\0'; string++)
    width += metrics->widths[*string];
  return(width);
}

/**********************************************************************/

/*
 * Entry instantiation stuff.
 */

MASTER_INDEX *Master;

/* 
 * Unique id for each stamp-pad object.
 * The handle we use to refer to an object in the NeWS server.
 */
int instance_id_count = 0;

char object_class[MAX_STR_LEN];

/*
 * Pump the body of an object into the NeWS server, which 
 * executes it, pushing the instantiation arguments onto the
 * stack. Return the name of the object's class, to which a
 * /new message will be sent.
 */

char *send_entry_body(ent)
     ENTRY *ent;
{
  int count, len;
  char buf[MAX_STR_LEN];
  FILE *f;

  if ((f = fopen(FileOfEntry(ent), "r")) == NULL) {
    perror("fopen entry file");
    fprintf(stderr, "Can't open file %s of entry %s!\n",
	    FileOfEntry(ent), KeyOfEntry(ent));
    bomb(1);
  }

  fseek(f, OffsetOfEntry(ent), 0);

  count = LengthOfEntry(ent);

  fgets(object_class, MAX_STR_LEN, f);
  len = strlen(object_class);
  object_class[len-1] = '\0';
  if (count != -1)
    count -= len;

  fgets(buf, MAX_STR_LEN, f);
  len = strlen(buf);
  if (count != -1)
    count -= len;

  while (!feof(f) && (count == -1 || count > 0)) {
    len = ((count > MAX_STR_LEN) || (count == -1) ? MAX_STR_LEN : count);
    len = fread(buf, 1, len, f);
    if (fwrite(buf, 1, len, PostScript) != len) {
      perror("writing entry body");
      fprintf(stderr, "Error writing entry body to PostScript file!\n");
    }
    if (count != -1)
      count -= len;
  }

  fclose(f);

  return(object_class);
}

/*
 * Make sure an stamp-pad picture object is instantiated in memory (as an
 * InstanceHeader) and in the server (as an Stamp in StampDict). If
 * it's not, tell the server where in the file system the object's
 * coming from, pump in the object's body, and send a new message to
 * the object's class, binding it to a unique instance id in
 * StampDict.  The instance ID is be used as a handle to the object in
 * the server. Query its size, and cache that away in the InstanceHeader. 
 * An picture is a stamp-pad that can put instructions into the display list.
 */
define_picture(ent)
     ENTRY *ent;
{
  INSTANCE_HEADER *new;
  char *class;

  if (InstanceOfEntry(ent) == NULL) {
    new = CreateInstanceHeader(ent, instance_id_count++, 0, 0);
    send_entry_pos(ent);
    class = send_entry_body(ent);
    do_def_picture(class, KeyOfEntry(ent), InstanceId(new));
    do_measure_stamp(InstanceId(new),
		     &InstanceWidth(new), &InstanceHeight(new));
  }
}

/*
 * Make sure a target stamp-pad object is instantiated in memory (as
 * an InstanceHeader) and in the server (as a TargetStamp in
 * StampDict).  Same as define_picture, except that a TargetStamp is a
 * stamp pad that can put a target item down on the current page,
 * enter it into the TargetDict, and put a referece to it in the
 * page's Targets dictionary, so the page's event manager can look out
 * for its start interests.  TargetStamps can also put instructions into
 * the display list. Each target that a TargetStamp stamps down gets a
 * unique ID, consisting of its name (that the author refered to it
 * by), a dot, and a serial number (the count of how many of targets
 * of this name have been stamped down). This ID is associated with
 * the target object in TargetDict. Each entry in PageDict has a
 * Targets field, that is a dictionary associating the target ID of
 * each target on that page with its reference.
 */
define_target(ent)
     ENTRY *ent;
{
  INSTANCE_HEADER *new;
  char *class;

  if (InstanceOfEntry(ent) == NULL) {
    new = CreateInstanceHeader(ent, instance_id_count++, 0, 0);
    send_entry_pos(ent);
    class = send_entry_body(ent);
    do_def_target(class, KeyOfEntry(ent), InstanceId(new));
    do_measure_stamp(InstanceId(new),
		     &InstanceWidth(new), &InstanceHeight(new));
  }
}

send_entry_pos(ent)
     ENTRY *ent;
{
  char *slash;
  char dir[MAX_STR_LEN], file[MAX_STR_LEN];
  char *rindex();

  strcpy(dir, FileOfEntry(ent));
  if ((slash = rindex(dir, '/')) == 0) {
    strcpy(file, dir);
    dir[0] = '\0';
  } else {
    strcpy(file, slash+1);
    slash[1] = '\0';
  }
  do_def_file_pos(dir, file, OffsetOfEntry(ent), LengthOfEntry(ent));
}

/*
 * Try to find a document of a given name, returning a counted string in buf,
 * containing the file name, or a string of zero length, if the name was not
 * known.
 */
ENTRY *lookup_document(name, buf)
     char *name;
     char *buf;
{
  ENTRY *ent = FindInIndex(Master->documents, name);

  if (ent == NULL) {
    fprintf(stderr, "Can't find document name \"%s\" in master index!\n",
	    name);
    fflush(stderr);
  }
  counted_entry_name(ent, buf);
  return(ent);
}

counted_entry_name(ent, buf)
     ENTRY *ent;
     char *buf;
{
  if (ent == NULL) {
    buf[0] = buf[1] = 0;
  } else {
    strcpy(buf+1, FileOfEntry(ent));
    buf[0] = strlen(FileOfEntry(ent));
  }
}


/**********************************************************************/

/*
 * Formatting stuff
 *
 * HyperTIES formatter library
 */

int win_x = 512;
int win_y = 0;
int win_width = 640;
int win_height = 900;
int top_margin = 10;
int bottom_margin = 10;
int left_margin = 10;
int right_margin = 10;
int cur_x, cur_y;
int image_x, image_y;
int image_width, image_height;
int in_line = 0;
int line_x, line_y;
char *source_name = "Untitled";
int source_pos, source_len;
int str_len = 0;
char str[MAX_STR_LEN];
int str_x, str_y;
int str_width;
int in_string = 0;
int page_width, page_height;
int top, bottom, left, right;
int on_page = 0;
char *font_name;
int font_size;
struct font_metrics *font = NULL;
char *default_font_name;
int default_font_size;
struct font_metrics *default_font = NULL;
char *button_font_name;
int button_font_size;
struct font_metrics *button_font = NULL;
char *definition_font_name;
int definition_font_size;
struct font_metrics *definition_font = NULL;
char *controls_font_name;
int controls_font_size;
struct font_metrics *controls_font = NULL;
int line_height = 0;
int default_line_height = 0;
int line_space = 4;
int para_indent = 4;
int space_width;
int pile_id_count = 0;
char *button_name = "default-button";
char *page_bg_name = NULL;
char *page_bg_ref = NULL;
char *definition_bg_name = NULL;
char *definition_bg_ref = NULL;
char *controls_bg_name = NULL;
char *controls_bg_ref = NULL;
char *full_entry_button_name = NULL;
char *full_entry_button_ref = NULL;
int recording = 0;

init_margins()
{
  left = left_margin;
  right = page_width - right_margin;
  bottom = bottom_margin;
  top = page_height - top_margin;
}

move_cur(x, y)
int x, y;
{
  end_line();
  cur_x = x;
  cur_y = y;
  start_line();
}

rmove_cur(x, y)
int x, y;
{
  move_cur(cur_x + x, cur_y + y);
}

overstrike()
{
  move_cur(image_x, image_y + image_height);
}

home()
{
  move_cur(left, top);
  /* first target will cover the whole window */
  place_image(0, 0, page_width, page_height);
  line_height = 0;
}

int new_pile(class, x, y, width, height)
     char *class;
     int x, y, width, height;
{
  do_make_pile(class, pile_id_count, x, y, width, height);
  return(pile_id_count++);
}

start_page()
{
  if (on_page) {
    end_page();
  }

  actually_start_page();
  record_start_page();
  do_get_page_size(&page_width, &page_height);
  init_margins();
  if (page_bg_name != NULL && page_bg_ref != NULL) {
    stamp_background_target(page_bg_name, page_bg_ref);
  }
  home();
  if (font)
    setup_font();
}

record_start_page()
{
  if (recording) {
    printf(".SP\n");
  }
}

actually_start_page()
{
  on_page = 1;
  do_start_page();
}

end_page()
{
  if (on_page) {
    end_line();
    actually_end_page();
    record_end_page();
  }
}

record_end_page()
{
  if (recording) {
    printf(".EP\n");
  } else {
    printf("p\n");
  }
}

actually_end_page()
{
  on_page = 0;
  do_end_page();
}

start_controls()
{
  if (on_page)
    end_page();
  on_page = 1;

  zap_pages();
  actually_start_page();
  record_start_page();
  do_get_page_size(&page_width, &page_height);
  init_margins();
  if (controls_bg_name != NULL && controls_bg_ref != NULL) {
    stamp_background_target(controls_bg_name, controls_bg_ref);
  }
  bottom = -999999;
  home();
  if (font)
    setup_font();
}

start_definition()
{
  if (on_page)
    end_page();
  on_page = 1;

  zap_pages();
  actually_start_page();
  record_start_page();
  do_get_page_size(&page_width, &page_height);
  init_margins();
  if (definition_bg_name != NULL && definition_bg_ref != NULL) {
    stamp_background_target(definition_bg_name, definition_bg_ref);
  }
  bottom = -999999;
  home();
  if (font)
    setup_font();
}

zap_pages()
{
  actually_zap_pages();
  record_zap_pages();
}

record_zap_pages()
{
  if (recording) {
    printf(".ZP\n");
  }
}

actually_zap_pages()
{
  do_zap_pages();
}

start_line()
{
  if (!in_line) {
    line_x = cur_x;
    line_y = cur_y;
    line_height = 0;
    source_pos = 0; /* Later */
    actually_start_line();
    record_start_line();
  }
}

record_start_line()
{
  if (recording) {
    printf(".SL\n");
  }
}

actually_start_line()
{
  in_line = 1;
  do_start_line();
}

end_line()
{
  flush_str();
  if (in_line) {
    source_len = 0; /* Later */
    source_name = "";
    actually_end_line(line_x, line_y, cur_x - line_x, -line_height,
		      source_name, source_pos, source_len);
    record_end_line(line_x, line_y, cur_x - line_x, -line_height,
		      source_name, source_pos, source_len);
  }
}

record_end_line(x, y, w, h, name, pos, len)
int x, y, w, h;
char *name;
int pos, len;
{
  if (recording) {
/*
    printf("%d %d\nc~ %s~\n%d %d %d %d\n.EL\n",
	   len, pos, name, h, w, y, x);
*/
    printf(".EL\n");
  } else {
    printf("l");
  }
}

actually_end_line(x, y, w, h, name, pos, len)
int x, y, w, h;
char *name;
int pos, len;
{
  in_line = 0;
  do_end_line(x, y, w, h, name, pos, len);
}

set_title(title)
     char *title;
{
  actually_set_pile_name(title);
  record_set_pile_name(title);
}

record_set_pile_name(name)
     char *name;
{
  if (recording) {
    printf("c~ %s~\n.SN\n", name);
  }
}

actually_set_pile_name(title)
     char *title;
{
  do_set_pile_name(title);
}

set_font(name, size)
     char *name;
     int size;
{
  if ((font = find_metrics(name, size)) == NULL) {
    fprintf(stderr, "Can't find font %s %d!\n", name, size);
    bomb(1);
  }

  setup_font();
}

setup_font()
{
  flush_str();
  if (on_page)
    use_font(font->name, font->size);
  space_width = string_width(font, " ");
  default_line_height = font->height;
}

use_font(name, size)
     char *name;
     int size;
{
  actually_use_font(name, size);
  record_use_font(name, size);
}

record_use_font(name, size)
     char *name;
     int size;
{
  if (recording) {
    printf("%d\nc~ %s~\n.UF\n", size, name);
  }
}

actually_use_font(name, size)
     char *name;
     int size;
{
  do_use_font(name, size);
}

new_line()
{
  end_line();
  if (line_height == 0)
    line_height = default_line_height;
  cur_y -= line_height + line_space;
  cur_x = left;
  if ((cur_y - default_line_height) <= bottom) {
    end_page();
    start_page();
  }
  start_line();
}

space()
{
  if ((str_y != cur_y - font->height) || (cur_x != str_x + str_width)) {
    flush_str();
  }

  if (str_len != 0) {
    str[str_len++] = ' ';
    str[str_len] = '\0';
    str_width += space_width;
  }
  cur_x += space_width;
}

place_image(x, y, width, height)
     int x, y, width, height;
{
  image_x = x;
  image_y = y;
  image_width = width;
  image_height = height;
  if (line_height < height)
    line_height = height;
}

flush_str()
{
  if (str_len) {
    str[str_len] = '\0';
    while (str_len > 0 && str[str_len-1] == ' ')
      str[--str_len] = '\0';
    actually_put_string(str, str_x, str_y + font->descent);
    record_put_string(str, str_x, str_y + font->descent);
    str_len = 0;
  }
}

record_put_string(str, x, y)
     char *str;
     int x, y;
{
  if (recording) {
    printf("%d %d\nc~ %s~\n.PS\n", y, x, str);
  }
}

actually_put_string(str, x, y)
     char *str;
     int x, y;
{
  do_put_string(str, x, y);
}

show_string(string)
     char *string;
{
  fit_image(string_width(font, string), font->height);

  stuff_string(string);
}

center_string(string)
     char *string;
{
  int width;

  width = string_width(font, string);
  cur_x = (left + right - width) >> 1;

  fit_image(width, font->height);

  stuff_string(string);
}

right_string(string)
     char *string;
{
  int width;

  width = string_width(font, string);
  cur_x = right - width;

  fit_image(width, font->height);

  stuff_string(string);
}

stuff_string(string)
     char *string;
{
  if ((image_y != str_y) || (image_x != str_x + str_width)) {
    flush_str();
  }

  if (str_len == 0) {
    str_width = 0;
    str_x = image_x;
    str_y = image_y;
  }

  str[str_len] = '\0';
  strcat(str, string);
  str_len += strlen(string);
  str_width += image_width;
}

paint_picture(name)
     char *name;
{
  ENTRY *ent = FindInIndex(Master->pictures, name);

  flush_str();

  if (ent == NULL) {
    fprintf(stderr, "Can't find image name \"%s\" in master index!\n", name);
    bomb(1);
  }

  define_picture(ent);
  fit_image(InstanceWidth(InstanceOfEntry(ent)),
	    InstanceHeight(InstanceOfEntry(ent)));
  put_picture(ent, image_x, image_y);
}

put_picture(ent, x, y)
     ENTRY *ent;
     int x, y;
{
  actually_put_picture(KeyOfEntry(ent), x, y);
  record_put_picture(KeyOfEntry(ent), x, y);
}

actually_put_picture(name, x, y)
     char *name;
     int x, y;
{
  ENTRY *ent = FindInIndex(Master->pictures, name);

  if (ent == NULL) {
    fprintf(stderr, "Can't find image name \"%s\" in master index!\n", name);
    bomb(1);
  }

  define_picture(ent);
  do_put_picture(InstanceId(InstanceOfEntry(ent)), x, y);
}

record_put_picture(name, x, y)
     char *name;
     int x, y;
{
  if (recording) {
    printf("%d %d\nc~ %s~\n.PP\n", y, x, name);
  }
}

fit_image(width, height)
     int width, height;
{
  if ((cur_x + width > right) && (cur_x != left))
    new_line();

  if (((cur_y - height) <= bottom) && (cur_y != top)) {
    end_page();
    start_page();
  }

  place_image(cur_x, cur_y - height, width, height);

  cur_x += width;
}

stamp_target(name, ref)
     char *name;
     char *ref;
{
  ENTRY *ent = FindInIndex(Master->targets, name);

  flush_str();

  if (ent == NULL) {
    fprintf(stderr, "Can't find target name \"%s\" in master index!\n", name);
    bomb(1);
  }

  define_target(ent);

  place_image(image_x, image_y,
	      InstanceWidth(InstanceOfEntry(ent)) ?
	        InstanceWidth(InstanceOfEntry(ent)) : image_width,
	      InstanceHeight(InstanceOfEntry(ent)) ?
	        InstanceHeight(InstanceOfEntry(ent)) : image_height);

  put_target(ent, ref, image_x, image_y, image_width, image_height);
}

put_target(ent, ref, x, y, w, h)
     ENTRY *ent;
     char *ref;
     int x, y, w, h;
{
  actually_put_target(KeyOfEntry(ent), ref, x, y, w, h);
  record_put_target(KeyOfEntry(ent), ref, x, y, w, h);
}

record_put_target(name, ref, x, y, w, h)
     char *name, *ref;
     int x, y, w, h;
{
  if (recording) {
    printf("%d %d %d %d\nc~ %s~\nc~ %s~\n.PT\n",
	   h, w, y, x, ref, name);
  }
}

actually_put_target(name, ref, x, y, w, h)
     char *name, *ref;
     int x, y, w, h;
{
  ENTRY *ent = FindInIndex(Master->targets, name);

  if (ent == NULL) {
    fprintf(stderr, "Can't find target name \"%s\" in master index!\n", name);
    bomb(1);
  }

  define_target(ent);

  do_put_target(InstanceId(InstanceOfEntry(ent)), ref,
		x, y, w, h);
}

stamp_background_target(name, ref)
     char *name;
     char *ref;
{
  ENTRY *ent = FindInIndex(Master->targets, name);

  flush_str();

  if (ent == NULL) {
    fprintf(stderr, "Can't find target name \"%s\" in master index!\n", name);
    bomb(1);
  }

  define_target(ent);

  place_image(0, 0, page_width, page_height);

  put_target(ent, ref, image_x, image_y, image_width, image_height);
}

free_stamps()
{
  FreeInstances();
  do_free_stamps();
}

setup_definition_pile()
{
  do_setup_definition_pile();
}

setup_controls_pile()
{
  do_setup_controls_pile();
}

setup_contents_pile()
{
  do_setup_contents_pile();
}

use_pile(pileid)
     int pileid;
{
  do_use_pile(pileid);
}

name_pile(name)
     char *name;
{
  do_name_pile(name);
}

int use_linked_pile(name)
     char *name;
{
  int id;

  record_use_linked_pile(name);
  return(actually_use_linked_pile(name));
}

int actually_use_linked_pile(name)
     char *name;
{
  int id;

  do_use_linked_pile(name, &id);
  return(id);
}

record_use_linked_pile(name)
     char *name;
{
  if (recording) {
    printf("c~ %s~\n.ULP\n", name);
  }
}

int use_parent_pile()
{
  int id;

  record_use_parent_pile();
  return(actually_use_parent_pile());
}

int actually_use_parent_pile()
{
  int id;

  do_use_parent_pile(&id);
  return(id);
}

record_use_parent_pile()
{
  if (recording) {
    printf(".UPP\n");
  }
}

def_local(name)
     char *name;
{
  do_def_local(name);
}

def_global(name)
     char *name;
{
  do_def_global(name);
}

write_ps_char(c)
     char c;
{
  psio_putc(c, PostScript);
}

write_ps_text(addr, len)
     char *addr;
     int len;
{
  while (--len >= 0) {
    psio_putc(*addr++, PostScript);
  }
}

send_ps_text(addr)
     char *addr;
{
  write_ps_text(addr, strlen(addr));
}

send_ps_string(str)
	char *str;
{
  do_send_ps_string(str);
}

send_ps_int(i)
    int i;
{
  do_send_ps_int(i);
}

/*
 * Initialization stuff
 */

FILE *MasterIndexFile;

init_fmt()
{
  char pathname[MAXPATHLEN]; /* From <sys/param.h> */

  cached_metrics = NULL;
  PostScriptInput = PostScript = NULL;

  if ((MasterIndexFile = fopen("master-index", "r")) == NULL) {
    fprintf(stderr, "Cannot open master-index file!\n");
    bomb(1);
  }

  Master = ReadMasterIndex(CreateMasterIndex(), MasterIndexFile);

  fclose(MasterIndexFile);

  if (ps_open_PostScript() == 0) {
    fprintf(stderr, "Cannot connect to NeWS server!\n");
    bomb(1);
  }

  do_initialize(getwd(pathname));
}

flush_PS()
{
  ps_flush_PostScript();
}

quit_PS()
{
  ps_close_PostScript();
}

no_forth()
{
  fprintf(stderr, "Bombing! (no forth!)\n");
  exit(1);
}

int (*forth_bomb)() = no_forth;
int (*forth_exec)() = no_forth;

bomb()
{
  fflush(stdout);
  fflush(stderr);
  forth_bomb();
}

