/* build.c --- Build the data structure as presented from the dvi file by
   dviread.  */

#include "d2rd.h"
#include "bbc.h"
#include "font.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include "kernel.h"
#include "visdelay.h"
#include "template.h"
#include "colourtran.h"
#include "res.h"
#include "flex.h"

/* The allocation units while reading a dvi file.  Maybe these could just be
   very large numbers, since the appriopriate arrays are re-alloc'd to the
   right size afterwards anyway, or maybe not...  */

#define RULE_BLOCKSIZE     10
#define TEXT_BLOCKSIZE     100
#define CHAR_BLOCKSIZE     200


/* Set this variable to 1 to be blown away by gunge while loading a dvi file.  */

extern int debug;


/* CURRENT_DVI_FILE points to the struct dvi_file of the file currently being read.  */

struct dvi_file *current_dvi_file;


/* CURRENT_PAGE idem dito of the current page.  PAGE_PTR points to the
   pointer which should point to this page, which it indeed will as soon as
   this page is completely loaded.  */

static struct page **page_ptr, *current_page;


/* Possible \magstep values (magstep0, magstephalf, magstep1, ..., magstep5)
   in the order of the magnification submenu.  */

double tex_magstep[] = {1.000, 1.095, 1.200, 1.440, 1.728, 2.074, 2.488};


/* The fonthandle while reading the dvi file, as set by the last call to
   setfont.  */

int current_font_handle;


static int current_page_number;

int draw_page = 1;

int page_area_size;

int loading_a_file;

int x_os_scale, y_os_scale;

int rule_index, char_index, text_index;

int max_rule_index, max_char_index, max_text_index;



struct
{
  char val[1024];
  _kernel_swi_regs r;
} old_font;


/* This sets the Font$Path to my own fonts.  It saves the old Font$Prefix
   properly, which can't be done by getenv () and setenv () in case the
   variable is something like a macro.  Hence this _kernel_swi () stuff.  */

void
set_font_path (void)
{
  char *new_font_path;

  old_font.r.r[0] = (int) "Font$Path";
  old_font.r.r[1] = (int) &old_font.val[0];
  old_font.r.r[2] = 1024;
  old_font.r.r[3] = old_font.r.r[4] = 0;
  _kernel_swi (0x23, &old_font.r, &old_font.r);

  new_font_path = getenv ("PreviewFonts$Path");
  if (new_font_path == NULL)
    tfatal ("PreviewFonts$Path is not defined!");

  _kernel_setenv ("Font$Path", new_font_path);
}


void
reset_font_path (void)
{
  old_font.r.r[3] = 0;
  _kernel_swi (0x24, &old_font.r, &old_font.r);
}


/* Set the default values of a window.  */

void
default_display_values (struct display *w)
{
  w->hor_size = (int)(A4_WIDTH  * MILLIPOINTS);
  w->ver_size = (int)(A4_HEIGHT * MILLIPOINTS);
  w->hor_offset = 1 * MILLIPOINTS; /* one inch, in millipoints */
  w->ver_offset = 1 * MILLIPOINTS; /* one inch, in millipoints */
  w->magstep = 0; w->mag_tweak = 1.0;
  w->zoom = 0;
}


void
factors (struct display *w, double *x, double *y)
{
  *x = *y = tex_magstep[w->magstep] * w->mag_tweak;
}


void
fill_sprite (struct display *w)
{
  struct dvi_file *f = w->file;
  struct font_use *fu;
  struct page *p = w->page;
  struct text *t;
  struct rule *l;
  wimp_palettestr current_palette;
  wimp_paletteword pal;
  _kernel_swi_regs r;
  int h, i, k, x, y, dvi_to_handle[256];
  int things = 0, bpp = bbc_vduvar (bbc_Log2BPP);
  double xs, ys;

  visdelay_begin ();
  set_font_path ();

  for (i = 0; i < 256; i++)
    dvi_to_handle[i] = 0;

  wimp_readpalette (&current_palette);

  r.r[0] = 60 + 256;
  r.r[1] = (int) w->area;
  r.r[2] = (int) w->sid.s.name;
  r.r[3] = 0;

  err = (os_error *) _kernel_swi (0x2e, &r, &r);
  if (err)
    tfatal ("Can't redirect output to sprite: %s\n", err->errmess);

  /* Output is now to the sprite.  */

  wimp_setpalette (&current_palette);

  pal.word = -1; pal.bytes.gcol = 0;
  colourtran_setGCOL (pal, 128, 0, &i);
  bbc_clg ();

  factors (w, &xs, &ys);

  pal.word = -1;

  for (t = p->texts, i = 0; i < p->text_max; t++, i++)
    {
      /* If the dvi to handle translation results in 0, the font has not been
         found yet.  Thus, find it at the appropriate size.  */

      if (t->font > 255)
        tfatal ("fontno = %d\n", t->font);

      if (dvi_to_handle[t->font] == 0)
        {
          fu = f->fonts[t->font];
          err = font_find (fu->name, (int) (fu->size * xs), (int) (fu->size * ys), 0, 0, &dvi_to_handle[t->font]);
          if (err)
            {
              dvi_to_handle[t->font] = 0;
              continue;
            }
        }

      font_setcolour (dvi_to_handle[t->font], 0, 1, bpp == 0 ? 0 : bpp == 1 ? 2 : bpp == 2 ? 6 : 14);
      font_paint (&p->chars[t->text], 0, (int)(t->x * xs),
         (int)((w->ver_size - w->ver_offset - t->y) * ys));
      visdelay_percent ((100 * things++) / p->things);
    }

  for (i = 0; i < 256; i++)
    if (dvi_to_handle[i])
      font_lose (dvi_to_handle[i]);

  pal.word = 0;
  colourtran_setGCOL (pal, 0, 0, &i);

  for (l = p->rules, i = 0; i < p->rule_max; l++, i++)
    {
      visdelay_percent ((100 * things++) / p->things);
      font_converttoos ((int)(l->x * xs),
         (int)((w->ver_size - w->ver_offset - l->y) * ys), &x, &y);
      font_converttoos ((int) (l->w * xs), (int) (l->h * ys), &k, &h);
      bbc_rectanglefill (x, y, k, h);
    }

  /* Re-redirect output to the screen again.  */

  _kernel_swi (0x2e, &r, &r);

  reset_font_path ();

  visdelay_percent (-1);
  visdelay_end ();
} /* fill_sprite */



/********
  */

int
define_sprite (struct display *w)
{
  char *sprite_name = xmalloc (12);
  double xfac, yfac;
  int xpix, ypix;
  int size;

  factors (w, &xfac, &yfac);

  font_converttoos( (int)((w->hor_size - w->hor_offset) * xfac),
                    (int)((w->ver_size - w->ver_offset) * yfac),
                    &xpix, &ypix);

  xpix /= x_os_scale; xpix = (xpix+31) & ~31;
  ypix /= y_os_scale;

  size = (int) (1024 +
     (xpix * ypix * (1 << bbc_vduvar (bbc_Log2BPP))) / 8.0);

#ifdef USE_FLEX
  if (flex_alloc ((flex_ptr) &w->area, size) == 0)
    tfatal ("failed to allocate space for aprite area");
#else
  w->area = xmalloc (size);
#endif

  sprite_area_initialise (w->area, size);

  sprintf (sprite_name, "page");
  w->sid.tag =0;
  w->sid.s.name = sprite_name;

  err = sprite_create (w->area, sprite_name, TRUE,
                       xpix, ypix, vdu_mode);
  if (err)
    tfatal ("Can't create sprite: %s\n", err->errmess);

  return (0);
} /* define_sprite */



void
adjust_window_size (struct display *w)
{
  wimp_redrawstr r;
  wimp_wstate s;
  double xfac, yfac;

  factors (w, &xfac, &yfac);

  r.w = w->w_handle;
  r.box.x0 = 0;
  r.box.y0 = 0;
  font_converttoos((int)(w->hor_size*xfac), (int)(w->ver_size*yfac), &r.box.x1, &r.box.y1);
  wimpt_noerr (wimp_set_extent (&r));

  r.w = w->w_handle;
  r.box.x0 = 0;
  r.box.y0 = 0;
  r.box.x1 = 1 << 30;
  r.box.y1 = 1 << 30;
  wimpt_noerr (wimp_force_redraw (&r));

  wimp_get_wind_state (w->w_handle, &s);
  wimp_open_wind (&s.o);
} /* adjust_window_size */



void
set_window_title (struct display *w)
{
  wimp_wstate ws;

  sprintf (w->w_title, "%s %d%s", w->file->f_name, w->page->dvi_page, w->file->complete ? "" : " *");
  wimpt_noerr (wimp_get_wind_state (w->w_handle, &ws));
  ws.o.box.x1 -= 4;
  wimpt_noerr (wimp_open_wind (&ws.o));
  ws.o.box.x1 += 4;
  wimpt_noerr (wimp_open_wind (&ws.o));
}



void
build_a_window (struct dvi_file *df, struct page *p)
{
  struct display *w;
  wimp_wind *wind;
  wimp_openstr wop;
  double xfac, yfac;

  w = xcalloc (1, sizeof (struct display));
  w->file = df;
  w->page = p;

  w->w_title = xmalloc (300);
  sprintf (w->w_title, "%s", df->f_name);

  default_display_values (w);
  define_sprite (w);  
  fill_sprite (w);

  wind = template_syshandle ("yesyes");
  factors (w, &xfac, &yfac);

  wind->ex.x0 = 0;
  wind->ex.y0 = 0;
  font_converttoos((int)(w->hor_size*xfac), (int)(w->ver_size*yfac),
      &wind->ex.x1, &wind->ex.y1);
  wind->titleflags |= wimp_ITEXT | wimp_INDIRECT ;
  wind->titleflags &= ~(wimp_ISPRITE);
  wind->title.indirecttext.buffer = w->w_title;
  wind->title.indirecttext.validstring = NULL;
  wind->title.indirecttext.bufflen = 300;

  wimpt_noerr (wimp_create_wind (wind, &w->w_handle));

  /*
  ** Having created the window, we now open it.
  ** Initially, the part of the work area we want visible
  ** is the middle of the topmost part.  We can skip part
  ** of the top margin, too, as it won't have anything in it.
  */
  wop.w = w->w_handle;
  wop.box = wind->box;
  font_converttoos( 0, (int)((w->ver_size-(w->ver_offset/2)) * yfac),
                    &wop.x, &wop.y );
  wop.x = (wind->ex.x1-(wop.box.x1-wop.box.x0))/2;
  if (wop.x<0) wop.x = 0; /* in case of smaller work area than window */
  wop.behind = -1;
  wimp_open_wind (&wop);

  set_window_title (w);

  w->next = windows;
  windows = w;
} /* build_a_window */



/**********
   Read a dvifile and build a dvi_file structure.  Return 0 if no error
   occured, +1 if an error occured and the user has already been notified, -1
   otherwise.  */

int
read_a_file (char *name)
{
  char *dviread_argv[2], *xname, *real_name;
  struct dvi_file *df;
  struct display *w;
  FILE *f;

  if (loading_a_file)
    {
      fatal ("Already loading a file.  Please wait.");
      return (1);
    }

  /* If NAME is a directory, try looking for a file called dvi in there.  If
     this exists, correct NAME. */

  xname = malloc (5 + strlen (name));
  sprintf (xname, "%s.dvi", name);
  f = fopen (xname, "rb");
  fclose (f);
  real_name = (f == NULL ? name : xname);

  /* If this file is already loaded bring his window to the front so the user
     will see this as well (and spot the `mistake').  */

  for (w = windows; w; w = w->next)
    if (strcmp (w->file->f_name, real_name) == 0)
      {
        wimp_wstate r;

        wimpt_complain (wimp_get_wind_state (w->w_handle, &r));
        r.o.behind = -1;
        wimpt_complain (wimp_open_wind (&r.o));
        free (xname);
        return (0);
      }

  /* Alles klar Herr Commissar.  */

  df = calloc (1, sizeof (struct dvi_file));
  if (df == NULL)
    {
      fatal ("Out of memory, sorry.\n");
      return (1);
    }

  df->f_name = malloc (1 + strlen (real_name));
  if (df->f_name == NULL)
    {
      fatal ("Out of memory, sorry.\n");
      return (1);
    }

  strcpy (df->f_name, real_name);

  dviread_argv[0] = "6 * 9 == 42";
  dviread_argv[1] = real_name;
  page_ptr = &df->pages;
  current_page_number = 0;
  current_dvi_file = df;
  loading_a_file = TRUE;
  df->complete = FALSE;

  /* Switch on hourglass and start reading the dvi file. The accompanying
     visdelay_end () is in DRAW_endpage, where the hourglass is switched off
     after having loaded the first page.  */

  visdelay_begin ();
  dviread_main (2, dviread_argv);

  df->complete = TRUE;
  df->next = dvi_files;
  dvi_files = df;

  /* Windows _must_ point to the newly loaded file's window.  */

  for (w = windows; w; w = w->next)
    if (w->file == df)
      set_window_title (w);

  loading_a_file = FALSE;
  free (xname);
  return (0);
} /* read_a_file */




/* Delete the display_window with window handle H from the list of window
   display's.  If no such entry in the list can be found, just return
   without further action.  */

void
delete_display (wimp_w h)
{
  struct display **w, *t, *t2;
  struct dvi_file *f, **pf;
  int i;
  struct page *p, *q;

  for (w = &windows; *w; w = &(*w)->next)
    if ((*w)->w_handle == h)
      break;

  if (*w)
    {
      t = *w;
      *w = t->next;
      for (t2 = windows; t2; t2 = t2->next)
        if (t2->file == t->file)
          break;

      /* If T2 is NULL, this is the last display onto that dvi file, so the
         dvi file structure can be deleted as well.  */

      if (t2 == NULL)
        for (pf = &dvi_files; *pf; pf = &(*pf)->next)
          if (*pf == t->file)
            {
              f = *pf;
              *pf = f->next;
              for (i = 0; i < 256; i++)
                if (f->fonts[i])
                  free (f->fonts[i]);
              for (p = f->pages; p; p = q)
                {
#ifdef USE_FLEX
                  flex_free ((flex_ptr) &p->texts);
                  flex_free ((flex_ptr) &p->rules);
                  flex_free ((flex_ptr) &p->chars);
#else
                  free (p->texts);
                  free (p->rules);
                  free (p->chars);
#endif
                  q = p->next;
                  free (p);
                }
              break;
            }

#ifdef USE_FLEX
      flex_free ((flex_ptr) &t->area);
#else
      free (t->area);
#endif
      free (t);
    }
}

/******************************************

             DRAW_  routines

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



void
DRAW_startpage(void)
{
  struct page *p = xcalloc (1, sizeof (struct page));

  p->dvi_page = ++current_page_number;
  p->complete = FALSE;

  text_index = 0;
  max_text_index = TEXT_BLOCKSIZE;

#ifdef USE_FLEX
  if (flex_alloc ((flex_ptr) &p->texts, TEXT_BLOCKSIZE * sizeof (struct text)) == 0)
    tfatal ("failed to allocate room for text");
#else
  p->texts = xmalloc (TEXT_BLOCKSIZE * sizeof (struct text));
#endif

  rule_index = 0;
  max_rule_index = RULE_BLOCKSIZE;

#ifdef USE_FLEX
  if (flex_alloc ((flex_ptr) &p->rules, RULE_BLOCKSIZE * sizeof (struct rule)) == 0)
    tfatal ("failed to allocate room for chars");
#else
  p->rules = xmalloc (RULE_BLOCKSIZE * sizeof (struct rule));
#endif

  char_index = 0;
  max_char_index = CHAR_BLOCKSIZE;

#ifdef USE_FLEX
  if (flex_alloc ((flex_ptr) &p->chars, CHAR_BLOCKSIZE) == 0)
    tfatal ("failed to allocated room for rules");
#else
  p->chars = xmalloc (CHAR_BLOCKSIZE);
#endif

  *page_ptr = p;
  page_ptr = &p->next;
  p->things = 0;
  current_page = p;
}



void
DRAW_text(int xl, int yb, int xr, int yt, char *text)
{
  struct text *t;
  int i, len = 1 + strlen (text);

  yt = yt, xr = xr;   /* Indeed, to make cc shut up.  */

  /* If LEN == 1, a `zero string' was passed.  This could only come from
     DRAW_char, so we got passed a one-character string with the character 0
     in it.  */

  if (len == 1)
    len = 2;

  if (char_index + len >= max_char_index)
    {
      max_char_index += CHAR_BLOCKSIZE;
#ifdef USE_FLEX
      if (flex_extend ((flex_ptr) &current_page->chars, max_char_index) == 0)
        tfatal ("OUT OF MEMORY");
#else
      current_page->chars = xrealloc (current_page->chars, max_char_index);
#endif
    }

  for (i = 1; i < len; i++)
    text[i - 1] |= 128;
  strcpy (&current_page->chars[char_index], text);

  if (text_index == max_text_index)
    {
      max_text_index += TEXT_BLOCKSIZE;
#ifdef USE_FLEX
      if (flex_extend ((flex_ptr) &current_page->texts, max_text_index * sizeof (struct text)) == 0)
        tfatal ("OUT OF MEMORY");
#else
      current_page->texts = xrealloc (current_page->texts, max_text_index * sizeof (struct text));
#endif
    }

  t = &current_page->texts[text_index];
  t->font = current_font_handle;
  t->text = char_index;
  t->x = xl;
  t->y = yb;
  t->rx = xr;
  t->ty = yt;

  current_page->things++;
  char_index += len;
  text_index++;

  if (current_dvi_file->pages->complete && current_page->things % 66 == 0)
    do_a_poll ();
}


/**********
   Write one character.  This might be a special character (unprintable ASCII
   code) hence this routine, which tweaks the character and just calls
   DRAW_text to print it.  */

void
DRAW_char(int xl, int yb, char c)
{
  char a[2];

  a[0] = c; a[1] = 0;
  DRAW_text (xl, yb, xl + 100, yb, a);

  if (debug)
    fprintf(stderr, "Draw_char(xl = %d, yb = %d, char = %d)\n", xl, yb, c);
}


/**********
   Draw a rule at the specified position (X, Y) and of the specified width DX
   and height DY.  */

void
DRAW_rule(int x, int y, int dy, int dx)
{
  struct rule *r;

  if (rule_index == max_rule_index)
    {
      max_rule_index += RULE_BLOCKSIZE;
#ifdef USE_FLEX
      if (flex_extend ((flex_ptr) &current_page->rules, max_rule_index * sizeof (struct rule)) == 0)
        tfatal ("barf r1");
#else
     current_page->rules = xrealloc (current_page->rules, max_rule_index * sizeof (struct rule));
#endif
    }

  r = &current_page->rules[rule_index];

  r->x = x;
  r->y = y;
  r->w = dx;
  r->h = dy;

  current_page->things++;
  rule_index++;

#if 0
printf ("%d %d %d %p %p %p\n", char_index, text_index, rule_index, current_page->chars, current_page->texts, current_page->rules);
#endif

  if (debug)
    fprintf(stderr, "Draw_rule(xl = %d, yb = %d, dx = %d, dy = %d)\n", x, y, dx, dy);
}


/**********
   Select a new current font.  */

void
DRAW_selfont(int fno)
{
  current_font_handle = fno;
  if (debug) fprintf(stderr, "Draw_selfont (fno = %d)\n", fno);
}


/**********
   Define a font, named FNAME, of size SIZE, and to be identified as FNO.
   SIZE is in 16th of a point.  Also check if the font actually exists, so an
   error can be issued, because if the font appears not to be present while
   redrawing the sprite we can't issue an error message.  */

void
DRAW_deffont(int fno, char *fname, int size, int mag)
{
  char *s;
  struct font_use *f;
  int fh;

  s = xmalloc (1 + strlen (fname));
  sprintf (s, "%s", fname);

  set_font_path ();
  err = font_find (s, size * mag / 1000, size * mag / 1000, 0, 0, &fh);
  if (err)
    {
      fatal ("Cannot find font `%s': %s\n", fname, err->errmess);
      reset_font_path ();
      do_a_poll ();
    }
  else
    {
      font_lose (fh);
      reset_font_path ();
    }

  f = xcalloc (1, sizeof (struct font_use));
  f->name = s;
  f->size = size * mag / 1000;
  current_dvi_file->fonts[fno] = f;

  reset_font_path ();

  if (debug)
    fprintf(stderr, "Draw_deffont(no = %d, name = \"%s\", size = %d/16)\n", fno, fname, size);
}


/********
   Mark the end of a page.  MAXH and MAXV are the observed limits for this
   page; COUNT points to an array of TeX page counters.  */

void
DRAW_endpage(int maxh, int maxv, int *count)
{
  memcpy (current_page->tex_pages, count, 10 * sizeof (int));
  current_page->complete = TRUE;
  current_dvi_file->height = maxv;
  current_dvi_file->width = maxh;

#ifdef USE_FLEX
  if (flex_extend ((flex_ptr) &current_page->texts, text_index * sizeof (struct text)) == 0)
    tfatal ("barf e1");
  if (flex_extend ((flex_ptr) &current_page->chars, char_index) == 0)
    tfatal ("barf e2");
  if (flex_extend ((flex_ptr) &current_page->rules, rule_index * sizeof (struct rule)) == 0)
    tfatal ("barf e1");
#else
  current_page->texts = xrealloc (current_page->texts, text_index * sizeof (struct text));
  current_page->chars = xrealloc (current_page->chars, char_index);
  current_page->rules = xrealloc (current_page->rules, rule_index * sizeof (struct rule));
#endif

  current_page->text_max = text_index;
  current_page->rule_max = rule_index;
  current_page->char_max = char_index;

  if (debug)
    fprintf(stderr, "Draw_endpage(%d, %d, %d)\n", maxh, maxv, count[0]);

  if (current_page_number == 1)
    {
      build_a_window (current_dvi_file, current_dvi_file->pages);
      visdelay_end ();
    }
}

/* EOF */
