#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "swis.h"

#include "os.h"
#include "flex.h"
#include "wimp.h"
#include "wimpt.h"
#include "win.h"
#include "res.h"
#include "template.h"
#include "baricon.h"
#include "event.h"
#include "menu.h"
#include "dbox.h"
#include "bbc.h"
#include "coords.h"
#include "werr.h"
#include "xferrecv.h"

#define space    ' '
#define linefeed '\n'
#define c_return '\x0d'

#define menu_INFO 1
#define menu_QUIT 2

#define X_MARGIN 8
#define Y_MARGIN 8

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

typedef struct
{
  char pathname[212];
  int size;
  void *txt;
  wimp_w han;
  void *buff;
  int width;
  int top, bottom;
} viewer;

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

int cycle = 0;

void close_viewer(viewer *v, int level);

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

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

int screen_width(void)
{
  return ( (1  + bbc_vduvar(bbc_XWindLimit))
         * (1 << bbc_vduvar(bbc_XEigFactor)) );
}

int lines(viewer *v)
{
wimp_wstate s;

  wimpt_noerr(wimp_get_wind_state(v->han, &s));

  return (s.o.box.y1 - s.o.box.y0 - 2 * Y_MARGIN) / 32;
}

int word_length(char *p)
{
int i = 0;
int c;

  while (c = p[i], c != space
                && c != linefeed
                && c != c_return)
    i++;

  return i;
}

char *format_line(char *txt, char *end, int width, char *buff)
{
char *p = txt;
int i, c;
BOOL ended = FALSE;

  while (*p == space)
    p++;

  for(i = 0; i < width; i++)
  {
    if (p >= end)
      ended = TRUE;

    if (!ended)
      switch (c = *p++)
      {
      case space:
        if (i + word_length(p) >= width)
          ended = TRUE;
        else
          buff[i] = space;
        break;

      case linefeed:
      case c_return:
        ended = TRUE;
        break;

      default:
        buff[i] = (c < space) ? space : c;
        break;
      }

    if (ended)
      buff[i] = space;
  }

  return p;
}

void open_viewer(viewer *v)
{
wimp_wstate s;
wimp_openstr o;
int width, top, bottom;
int i, k = 1;
char *t, *end, *dummy;
BOOL mode_change = wimpt_checkmode();

  wimpt_noerr(wimp_get_wind_state(v->han, &s));

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

  width  = ( o.box.x1 - o.box.x0 - 2 * X_MARGIN) / 16;
  top    = (-o.box.y1 - Y_MARGIN)                / 32;
  bottom = (-o.box.y0 + Y_MARGIN - 1)            / 32;

  if (width    == v->width         /* ensure viewer contents have changed */
     && top    == v->top
     && bottom == v->bottom
     && !mode_change)
    return;

  while (k > 0)
  {
    if (flex_extend(&v->buff, width * (bottom - top + 1)) == 0) /* buffer */
    {
      werr(FALSE, "flex_extend failed");
      close_viewer(v, 0);
      return;
    }

    t     = (char *) v->txt;                       /* initialise pointers */
    end   = t + v->size;
    dummy = (char *) malloc(width * sizeof(char));
    for (i = 0 ; t < end; i++)                             /* format text */
      t = format_line(t, end, width, (i >= top && i <= bottom)
                                     ? ((char *) v->buff) + width * (i - top)
                                     : dummy);
    free(dummy);

    k = bottom - i + 1;
    if (k > 0)
    {
      top    -= k;
      if (top < 0)
        top = 0;

      bottom -= k;
    }
  }

  if ((v->width != width) || mode_change)
  {
    o.w      = v->han;
    o.box.x0 = 0;
    o.box.x1 = screen_width() + 2 * X_MARGIN;
    o.box.y0 = -(i * 32 + 2 * Y_MARGIN);
    o.box.y1 = 0;
    wimpt_noerr(wimp_set_extent((wimp_redrawstr *) &o));

    wimpt_noerr(wimp_open_wind(&s.o));

    if (v->width != width)
    {
      wimpt_noerr(wimp_get_wind_state(v->han, &s));
      s.o.w = (wimp_w) -1;
      wimpt_noerr(wimp_force_redraw((wimp_redrawstr *) &s.o));
    }
  }

  v->top    = top;
  v->bottom = bottom;
  v->width  = width;
}

void redraw_viewer(viewer *v)
{
wimp_redrawstr r;
BOOL more;

  r.w = v->han;
  wimpt_noerr(wimp_redraw_wind(&r, &more));

  while (more)
  {
  int x, line;
  wimp_box b;
  coords_pointstr p;

    coords_box_toworkarea(&r.g, (coords_cvtstr *) &r.box);
    coords_offsetbox(&r.g, -X_MARGIN, Y_MARGIN, &r.g);
    b.x0 =   r.g.x0      / 16;
    b.x1 =   r.g.x1      / 16;
    b.y0 = (-r.g.y0 - 1) / 32;
    if (b.y0 > v->bottom) b.y0 = v->bottom;
    b.y1 =  -r.g.y1      / 32;

    for (line = b.y1; line <= b.y0; line++)
    {
      p.x =  b.x0 * 16 + X_MARGIN;
      p.y = -line * 32 - Y_MARGIN - 1;
      coords_point_toscreen(&p, (coords_cvtstr *) &r.box);
      bbc_move(p.x, p.y);

      for (x = b.x0; x <= b.x1; x++)
        bbc_vdu((x < v->width)
                ? ((char *) v->buff)[(line - v->top) * v->width + x]
                : space);
    }

    wimpt_noerr(wimp_get_rectangle(&r, &more));
  }
}

BOOL unknown_handler(wimp_eventstr *e, void *handle)
{
  switch(e->e)
  {
  case wimp_ESEND:
  case wimp_ESENDWANTACK:
    switch (e->data.msg.hdr.action)
    {
    case wimp_MMODECHANGE:
      open_viewer((viewer *) handle);
      break;
    }
    break;
  }
  return FALSE;
}

void close_viewer(viewer *v, int level)
{
  switch (level)
  {
  case 0:
    win_remove_unknown_event_processor(unknown_handler, (void *) v);
    win_register_event_handler(v->han, 0, (void *) v);

  case 1:
    if (wimpt_complain(wimp_delete_wind(v->han)))
      return;

  case 2:
    flex_free(&v->buff);

  case 3:
    flex_free(&v->txt);

  case 4:
    free((void *) v);
  }
}

void viewer_handler(wimp_eventstr *e, void *handle)
{
viewer *v = (viewer *) handle;

  switch(e->e)
  {
    case wimp_EREDRAW:
      redraw_viewer(v);
      break;

    case wimp_EOPEN:
      e->data.o.y = ((int) (e->data.o.y / 32)) * 32;
      (void) wimpt_complain(wimp_open_wind(&e->data.o));
      open_viewer(v);
      break;

    case wimp_ECLOSE:
      close_viewer(v, 0);
      break;

    case wimp_ESCROLL:
    {
    int dy = 0;

      switch(e->data.scroll.y)
      {
      case  1:
        dy =  1;
        break;

      case -1:
        dy = -1;
        break;

      case  2:
        dy =  lines(v);
        break;

      case -2:
        dy = -lines(v);
        break;
      }
      e->data.scroll.o.y += dy * 32;
    }
    (void) wimpt_complain(wimp_open_wind(&e->data.scroll.o));
    open_viewer(v);
    break;
  }
}

BOOL new_viewer(char *pathname)
{
viewer *v;
os_filestr f;
wimp_wind *w;
wimp_wstate s;

  f.action = 17;                                     /* find size of file */
  f.name = pathname;
  if (wimpt_complain(os_file(&f)))
    return FALSE;
  if (f.action != 1)
    return FALSE;

  if ((v = (viewer *) malloc(sizeof(viewer))) == 0)  /* create new struct */
    return FALSE;
  v->size = f.start;                                /* fill in size field */
  v->width = 0;                                 /* initialise width field */
  strncpy(v->pathname, pathname, 212);                   /* copy pathname */

  if (flex_alloc(&v->txt, v->size) == 0)      /* allocate memory for file */
  {
    werr(FALSE, "flex_alloc failed");
    close_viewer(v, 4);
    return FALSE;
  }

  f.action = 255;                                            /* load file */
  f.name = pathname;
  f.loadaddr = (int) v->txt;
  f.execaddr = 0;
  if (wimpt_complain(os_file(&f)))
  {
    close_viewer(v, 3);
    return FALSE;
  }

  if (flex_alloc(&v->buff, 0) == 0)  /* allocate memory for redraw buffer */
  {
    werr(FALSE, "flex_alloc failed");
    close_viewer(v, 3);
    return FALSE;
  }

  w = template_syshandle("Main");             /* create new viewer window */
  w->box.y1 = 800 - cycle * 48;
  w->box.y0 = w->box.y1 - 480;
  w->title.indirecttext.buffer = v->pathname;
  if (wimpt_complain(wimp_create_wind(w, &v->han)))
  {
    close_viewer(v, 2);
    return FALSE;
  }

  if (wimpt_complain(wimp_get_wind_state(v->han, &s)))     /* open window */
  {
    close_viewer(v, 1);
    return FALSE;
  }
  s.o.behind = (wimp_w) -1;
  if (wimpt_complain(wimp_open_wind(&s.o)))
  {
    close_viewer(v, 1);
    return FALSE;
  }

  win_register_event_handler(v->han,                  /* install handlers */
                             viewer_handler,
                             (void *) v);
  win_add_unknown_event_processor(unknown_handler,
                                  (void *) v);

  open_viewer(v);                                        /* update buffer */

  if (++cycle > 3)                         /* update viewer positon cycle */
    cycle = 0;

  return TRUE;
}

void iconbar_handler(wimp_eventstr *e, void *handle)
{
int filetype;
char *pathname;

  e = e;            /* prevent warnings */
  handle = handle;

  if ((filetype = xferrecv_checkinsert(&pathname)) != -1)
    if (new_viewer(pathname))
      xferrecv_insertfileok();
}

void menu_handler(void *handle, char *hit)
{
  handle = handle;

  switch(hit[0])
  {
    case menu_INFO:
    {
    dbox  d;

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

    case menu_QUIT:
      exit(0);
      break;
  }
}

BOOL initialise(void)
{
menu m;

  wimpt_init("Text Reader");
  res_init("TextRead");
  flex_init();
  template_init();

  baricon("!textread", 1, null_proc);
  win_register_event_handler(win_ICONBAR    , iconbar_handler, 0);
  win_register_event_handler(win_ICONBARLOAD, iconbar_handler, 0);
  win_claim_unknown_events(win_ICONBAR);

  m = menu_new("Text Reader", ">Info, Quit");
  event_attachmenu(win_ICONBAR, m, menu_handler, 0);

  return TRUE;
}

int main(int argc, char *argv[])
{
int i;

  if (!initialise())
    exit(1);

  for (i = 1; i < argc; i++)
    new_viewer(argv[i]);

  while(TRUE)
    event_process();

  return 0;
}
