/* This is the main file for hacking dvips to do HyperPostScript
 * Written by Mark D. Doyle 11/94. It is (C) Copyright 1994 by Mark D. Doyle
 * and the University of California. You may modify and use this program to
 * your heart's content.
 */
#include "dvips.h"

/*
 *   The external declarations:
 */
#include "protos.h"

#ifdef HPS
#include <stdlib.h>
#ifdef KPATHSEA
#include <kpathsea/c-ctype.h>
#else
#define TOLOWER Tolower
#endif
#ifndef FALSE
#define FALSE 0
#endif
#ifndef TRUE
#define TRUE 1
#endif
#ifndef Tolower
#define Tolower tolower
#endif
#define NAME 0
#define HREF 1
#define skip_space(s) for(; *s == ' '; s++);

Boolean inHTMLregion = 0;
#if defined(MIKTEX) && defined(NO_ERROR)
#undef NO_ERROR
#endif
Boolean NO_ERROR = 1;
Boolean HPS_ERROR = 0;
integer HREF_COUNT;
Boolean ISHREF = 0;
Boolean POPPED = 0;
Boolean PUSHED = 0;

char *current_name;
int current_type;
int current_pushcount;


#define GoTo 0
#define GoToR 1
#define Launch 2

typedef struct rectangle {
  double llx; /* lower left x coor */
  double lly; /* lower left y coor */
  double urx; /* upper right x coor */
  double ury; /* upper right y coor */
  } dvipsRectangle;

typedef struct hps_link {
  int action; /* GoTo, GoToR, or Launch */
  char *file;   /* PDF-style system-independent filename - unused now */
  dvipsRectangle rect;  /* rectangle for location of link */
  int border[5]; /* Border info --- with dashes*/
  int srcpg; /* Page of location */
  int page;  /* Page of destination --- left out is same as srcpg */
  int vert_dest; /* Vertical position of destination on page */
  double color[3]; /* color: length of array gives colorspace (3 <=> RGB)*/
  char *title; /* Title of link */
  } Hps_link;

struct nlist { /* hashtable entry */
  struct nlist *next; /* next entry in chain */
  char *name; /* defined name */
  Hps_link *defn; /* Link associated with name */
  };

typedef struct rect_list { /* linked list of rectangles */
  struct rect_list *next; /* next rectangle in the chain */
  dvipsRectangle rect;
  } Rect_list;

Rect_list *current_rect_list;

#define HASHSIZE 1223
static struct nlist *link_targets[HASHSIZE]; /* names .... */
static struct nlist *link_sources[HASHSIZE]; /* hrefs .... */

#ifdef KPATHSEA
#define dup_str xstrdup
#else
char *dup_str(); /* duplicate a string */
#endif

Boolean noprocset; /* Leave out BeginProc and EndProc comments */
/* This was hardcoded to letter size.  The denominator converts scaled
   points to big points (65536*72.27/72). */
#undef PAGESIZE /* HP-UX 10 defines for itself.  */
#define PAGESIZE ((int) (vpapersize/65781.76))
#define MARGIN 72
#define VERTICAL 0
#define HORIZONTAL 1
#define FUDGE 2.0

/* For later use
* static char *dest_key[9] = {"Fit", "FitB", "FitW", "FitH", "FitBH"
               "FitR", "FitV", "FitBV", "XYZ"};
*/

char *hs = NULL; /* html string to be handled */
char *url_name = NULL; /* url between double quotes */

static int href_or_name(void);
static int parseref(void);
static int get_string(void);
static  int do_link(char *s, int type);
static struct nlist *lookup_link(char *s, int type);
static struct nlist *install_link(char *name, struct hps_link *defn, int type);
static Hps_link *link_dup(struct hps_link *s);
static double dvi_to_hps_conv(int i, int dir);
static int href_name_match(char *h, char *n);
static void stamp_hps(struct hps_link *pl);
static void stamp_external(char *s, struct hps_link *pl);
static void print_rect_list(void);

/* parse anchor into link */
void
do_html(char *s)
{
  Hps_link *nl;
  url_name = (char *)malloc(strlen(s)+1);
  hs = s;
  HREF_COUNT = 0;
  skip_space(hs); /* skip spaces */
  if ( TOLOWER(*hs) == 'a') { /* valid start */
    POPPED = FALSE;
    if (inHTMLregion) {
      error("previous html not closed with /A");
      return;
    } else {
      inHTMLregion = TRUE;
      current_rect_list = NULL;
    }
    hs++;
    skip_space(hs);
    while(*hs != '\0') {
      /* parse for names and href */
      if (!href_or_name()) {
	error("Bad HMTL:");
	error(s);
	error("!");
	break;
      }
      skip_space(hs);
    }
  } else if ( (*hs == '/') && (TOLOWER(hs[1]) == 'a') ) {
    if (!inHTMLregion) {
      error("In html, /A found without previous A");
      return;
    } else {
      inHTMLregion = FALSE;
    }
       if (current_type == HREF && current_name[0] != '#') {
	 if ((nl = lookup_link(current_name, current_type)->defn)) {
	   nl->rect.urx = dvi_to_hps_conv(hh, HORIZONTAL);
	   nl->rect.ury = dvi_to_hps_conv(vv, VERTICAL)-FUDGE+12.0;
	   stamp_external(current_name,nl); /* Broken lines ? */
	 } else {
	   error("!Null lookup");
	 }
       } else {
	 if ((nl = lookup_link(current_name, current_type)->defn)) {
	   nl->rect.urx = dvi_to_hps_conv(hh, HORIZONTAL);
	   nl->rect.ury = dvi_to_hps_conv(vv, VERTICAL)-FUDGE+12.0;
	   if (current_type) {
	     stamp_hps(nl); /* Put link info right where special is */
	     print_rect_list(); /* print out rectangles */
	   }
	 } else {
                    error("!Null lookup");
	 }
       }
  }
  else {
    error( "No A in html special");
    error(s);
    /*error("!");*/
  }

  return;
}

static int
href_or_name(void) {
  if ((strncmp(hs, "href", 4) == 0) || (strncmp(hs, "HREF", 4) == 0)) {
    ISHREF = TRUE;
  } else if ((strncmp(hs, "name", 4) == 0)
	     || (strncmp(hs, "NAME", 4) == 0)) {
    ISHREF = FALSE;
  } else {
    error("Not valid href or name html reference");
    return(HPS_ERROR);
          }
  if(!parseref()) return(HPS_ERROR);
  if (url_name) {
    if( ISHREF ) {
      do_link(url_name, HREF);
    } else do_link(url_name, NAME);
  } else {
    error("No string in qoutes ");
    return(HPS_ERROR);
  }
  return(NO_ERROR);
}

static int
parseref(void) {
  int i = 0;
  for(i=0; i++ < 4; hs++); /* skip href or name in html string */
  skip_space(hs);
  if (*hs == '=' ) {
    hs++;
  } else {
    error("No equal sign");
    return(HPS_ERROR);
  }
  if(!get_string()) return(HPS_ERROR);
  return(NO_ERROR); /* extract stuff between double quotes */
}

static int
get_string(void) {
  char *v = url_name;

  skip_space(hs);
  /* hash_name(); */
  if (*hs == '"') {
    for(hs++; *hs != '"' && *hs != '\0'; *v++ = *hs++);  /* need to esc " */
    if(*hs == '"') {
      hs++;
      *v++ = '\0';
      return(NO_ERROR);
    } else {
      error("Improper href. Missing closing double quote");
      return(HPS_ERROR);
    }
  } else {
    error("Improper href. Missing opening double quote");
    return(HPS_ERROR);
  }
}

static int
do_link(char *s, int type)
{

  Hps_link *p;

  if (HREF_COUNT++ > 0) {
    error("!HTML string contains more than one href");
    return(HPS_ERROR);
  }
  p = (Hps_link *)malloc(sizeof(*p));
  p->action = GoTo;
  p->title = (char *)malloc(strlen(s)+1);
  p->title = s;
  p->srcpg = pagecounter;
  p->rect.llx = dvi_to_hps_conv(hh, HORIZONTAL);
  p->rect.lly = dvi_to_hps_conv(vv, VERTICAL)-FUDGE;
  p->rect.urx = -1.0;
  p->rect.ury = -1.0;
  p->vert_dest = -1;
  p->page = -1;
  p->color[0] = 0;
  p->color[1] = 0; /* Blue links */
  p->color[2] = 1;
  p->border[0] = 1; /* horizontal corner radius */
  p->border[1] = 1; /* vertical corner radius */
  p->border[2] = 1; /* box line width */
  p->border[3] = 3; /* dash on size */
  p->border[4] = 3;  /* dash off size */

  current_name = (char *)malloc(strlen(s)+1);
  current_name = s;
  current_type = type;
  current_pushcount = pushcount;
  install_link(s, p, type);
  return(NO_ERROR);
}

static unsigned int
hash_string(char *s)
{
  unsigned hashval;
  for (hashval = 0; *s != '\0'; s++)
    hashval = *s + 31 * hashval;
  return hashval % HASHSIZE;
}

/* lookup a hashed name */

static struct nlist *
lookup_link(char *s, int type)
{
  struct nlist *np;

  for(np = type ? link_sources[hash_string(s)] : link_targets[hash_string(s)];
        np != NULL; np = np -> next)
      if (strcmp(s, np->name) == 0)
      return np; /* found */
  return NULL; /* not found */
}

static struct nlist *
install_link(char *name, Hps_link *defn, int type)
{
  struct nlist *np;
  unsigned hashval;
    np = (struct nlist *)malloc(sizeof(*np));
    if (np == NULL || (np->name = dup_str(name)) == NULL)
      return NULL;
    hashval = hash_string(name);
    np->next = type ? link_sources[hashval] : link_targets[hashval];
    (type ? link_sources : link_targets)[hashval] = np;
    if ((np->defn = link_dup(defn)) == NULL)
      return NULL;
  return np;
}

#ifndef KPATHSEA
char *
dup_str(char *w) /* make a duplicate of s */
{
  char *p;
  p = (char *)malloc(strlen(w)+1);
  if (p != NULL)
    strcpy(p,w);
  return p;
}
#endif

static Hps_link *
link_dup(Hps_link *s) /* make a duplicate link */
{
  Hps_link *p;

  p = (Hps_link *) malloc(sizeof(*p));
  if (p != NULL)
    p = s;
  return p;
}

static double
dvi_to_hps_conv(int i, int dir)
{
  double hps_coor;
  /* Convert dvi integers into proper hps coordinates
     Take into account magnification and resolution that dvi file was
     produced at */
  hps_coor = dir ? (((i * 72.0) / vactualdpi) +MARGIN) : (PAGESIZE - ((i * 72.0) / (vactualdpi)) - MARGIN  );
  return(hps_coor);
}

static int
vert_loc(int i)
{
  int return_value;
  return_value = (int) (i + (PAGESIZE / 4) + FUDGE);
  if ( return_value > PAGESIZE) {
    return((int)PAGESIZE);
    } else if (return_value <  (PAGESIZE / 4.0)) {
        return((PAGESIZE / 4));
        } else return(return_value);
}

static Hps_link *
dest_link(char *s)
{
  /* Assume for now that only one entry with same NAME, i.e.
     Should be true for all legitimate papers.
     Also, assume prepending of # for names. */

  struct nlist *np;

  s++; /* get rid of hashmark */
  for(np = link_targets[hash_string(s)]; np != NULL; np = np -> next) {
     if ( href_name_match(s, np->name)) {
      return (np->defn); /* found */
      }
      }
  error("Oh no, link not found in target hashtable");
  error(s);
  error("!");
  return NULL; /* not found */
}

static int
count_targets(void) {
  int count=0;
  int i;
  struct nlist *np;

  for (i = 0; i < HASHSIZE; i++)
    for(np = link_targets[i]; np != NULL; np = np -> next)
      count++;
  return count;
}

static void
do_targets(void) {

  struct nlist *np;
  int i;
  Hps_link *dest;

  for (i = 0; i < HASHSIZE; i++)
    for(np = link_sources[i]; np != NULL; np = np -> next) {
      if (np->name[0] == '#') {
	dest = dest_link(np->name);
	np->defn->page = dest->srcpg;
	np->defn->vert_dest = vert_loc((int) dest->rect.lly);
      }
    }
}

static void
do_target_dict(void)
{
  struct nlist *np;
  int i;
  (void) fprintf(bitfile, "HPSdict begin\n");
  fprintf(bitfile, "/TargetAnchors\n");
  fprintf(bitfile, "%i dict dup begin\n",count_targets());

  for (i = 0; i < HASHSIZE; i++)
    for(np = link_targets[i]; np != NULL; np = np -> next)
      fprintf(bitfile, "(%s) [%i [%.0f %.0f %.0f %.0f] %i] def\n",
		    np->defn->title, np->defn->srcpg,
		    np->defn->rect.llx, np->defn->rect.lly,
		    np->defn->rect.urx, np->defn->rect.ury,
		    vert_loc((int) np->defn->rect.lly));
  fprintf(bitfile,"end targetdump-hook def end\n");
}

static int
href_name_match(char *h, char *n)
{
  int count = 0;
  int name_length = strlen(n);
  while((*h == *n) & (*h != '\0') )  {
    count++;
    h++;
    n++;
    }
  if (name_length == count) {
    return 1;
    } else {
     /* printf(" count: %i \n", count); */
    return 0;
    }
}

static void
stamp_hps(Hps_link *pl)
{
  char *tmpbuf;
  unsigned tmpsize;
  
  if (pl == NULL) {
    error("stamp_hps: null pl pointer, oh no!");
    return;
  }
  if (pl->title == NULL) {
    error("stamp_hps: null pl->title pointer, oh no!");
    return;
  }

  tmpsize = strlen(pl->title) + 500;
#if defined(MIKTEX)
  tmpbuf = (char*)xmalloc(tmpsize);
#else
  tmpbuf = xmalloc(tmpsize);
#endif

  /* print out the proper pdfm with local page info only
   *  target info will be in the target dictionary */
  snprintf(tmpbuf, tmpsize,
     " (%s) [[%.0f %.0f %.0f %.0f] [%i %i %i [%i %i]] [%.0f %.0f %.0f]] pdfm ",
     pl->title, pl->rect.llx, pl->rect.lly, pl->rect.urx, pl->rect.ury,
     pl->border[0], pl->border[1], pl->border[2], pl->border[3],pl->border[4],
     pl->color[0], pl->color[1], pl->color[2]);
  cmdout(tmpbuf);
  free(tmpbuf);


}

/* For external URL's, we just pass them through as a string. The hyperps
 * interpreter can then do what is wants with them.
 */
static void
stamp_external(char *s, Hps_link *pl)
{
  char *tmpbuf;
  unsigned tmpsize;
  if (pl == NULL) {
    error("stamp_external: null pl pointer, oh no!");
    return;
  }
  if (s == NULL) {
    error("stamp_external: null s pointer, oh no!");
    return;
  }

  tmpsize = strlen(s) + 500;
#if defined(MIKTEX)
  tmpbuf = (char*)xmalloc(tmpsize);
#else
  tmpbuf = xmalloc(tmpsize);
#endif

  /* print out the proper pdfm with local page info only
   *  target info will be in the target dictionary */
  snprintf(tmpbuf, tmpsize,
     " [[%.0f %.0f %.0f %.0f] [%i %i %i [%i %i]] [%.0f %.0f %.0f]] (%s) pdfm ",
     pl->rect.llx, pl->rect.lly, pl->rect.urx, pl->rect.ury,
     pl->border[0], pl->border[1], pl->border[2], pl->border[3],pl->border[4],
     pl->color[0], pl->color[1], pl->color[2], s);
  cmdout(tmpbuf);
  free(tmpbuf);
}

void
finish_hps(void) {
  fclose(bitfile);
  set_bitfile("head.tmp",1);
  do_targets();
  do_target_dict();
  fprintf(bitfile, "TeXDict begin\n");
  fprintf(bitfile, "%%%%EndSetup\n");
  fclose(bitfile);
  open_output();
  noprocset = 1;
  removecomments = 0;
  copyfile("head.tmp");
  copyfile("body.tmp");
  if (dvips_debug_flag == 0 && debug_flag == 0) {
     unlink("head.tmp");
     unlink("body.tmp");
  }
}

void
set_bitfile(const char *s, int mode)
{
if ((bitfile=fopen(s, mode ? FOPEN_ABIN_MODE : FOPEN_WBIN_MODE))==NULL) {
   error(s);
   error("!couldn't open file");
  }
  linepos = 0;
}

void
vertical_in_hps(void) {
  Rect_list *rl;
  /*printf("in vertical_in_hps"); */
  if (current_type == NAME) return; /* Handle this case later */
  if (!current_rect_list) {
    current_rect_list = (Rect_list *)malloc(sizeof(Rect_list));
    current_rect_list->next = NULL;
  }
  else {
    rl = (Rect_list *)malloc(sizeof(Rect_list));
    rl->next = current_rect_list;
    current_rect_list = rl;
  }
  current_rect_list->rect.llx = dvi_to_hps_conv(hh, HORIZONTAL);
  current_rect_list->rect.lly = dvi_to_hps_conv(vv, VERTICAL)-FUDGE;
  current_rect_list->rect.urx = dvi_to_hps_conv(hhmem, HORIZONTAL);
  current_rect_list->rect.ury = dvi_to_hps_conv(vvmem, VERTICAL)-FUDGE;

  if (POPPED) start_new_box();
}

static void
print_rect_list(void) {
  Rect_list *rl, *rln;

  for(rl = current_rect_list; rl != NULL; rl = rln) {
    /* printf("Rectangle is %.0f, %.0f, %.0f, %.0f\n", rl->rect.llx, rl->rect.lly,
            rl->rect.urx, rl->rect.ury); */
    rln = rl -> next;
    free(rl);
  }
}

void
end_current_box(void) {
  Hps_link *nl;

  POPPED = TRUE;
  HREF_COUNT--;
  if (current_type == HREF && current_name[0] != '#') {
    if ((nl = lookup_link(current_name, current_type)->defn)) {
      nl->rect.urx = dvi_to_hps_conv(hhmem, HORIZONTAL);
      nl->rect.ury = dvi_to_hps_conv(vvmem, VERTICAL)-FUDGE+12.0;
      stamp_external(current_name,nl); /* Broken lines ? */
    } else {
      error("!Null lookup");
    }
  } else {
    if ((nl = lookup_link(current_name, current_type)->defn)) {
      nl->rect.urx = dvi_to_hps_conv(hhmem, HORIZONTAL);
      nl->rect.ury = dvi_to_hps_conv(vvmem, VERTICAL)-FUDGE+12.0;
      if (current_type) {
	stamp_hps(nl); /* Put link info right where special is */
      }
    } else {
      error("!Null lookup");
    }
    /* printf("Ended box %.0f, %.0f, %.0f, %.0f\n", nl->rect.llx, \
       nl->rect.lly,nl->rect.urx, nl->rect.ury); */
  }
}

void
start_new_box(void) {
  POPPED = FALSE;
  do_link(current_name, current_type);
}
#else
int no_hypertex; /* prevent an empty file */
#endif
