#include "wimp.h"
#include "wimpt.h"
#include "win.h"
#include "event.h"
#include "baricon.h"
#include "res.h"
#include "resspr.h"
#include "menu.h"
#include "template.h"
#include "dbox.h"
#include "werr.h"
#include "flex.h"
#include "os.h"
#include "coords.h"
#include "colourtran.h"
#include "magnify.h"
#include "sprite.h"
#include "bbc.h"
#include "visdelay.h"
#include "kernel.h"

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

#define Modevar_Xeig     4
#define Modevar_Yeig     5
#define Modevar_Log2BPP  9

#define Osfile_Readinfo  17

#define XOS_Bit                  (1<<17)
#define ColourTrans_SelectTable  0x40740

/* Menu entires */
enum Iconbarmenu { Iconbarmenu_Info = 1, Iconbarmenu_Quit };

enum Viewermenu { Viewermenu_Zoom = 1 };

/* Dialogue box fields */
#define ProgInfo_Field    4

/* Version for info box */
static char *Version_String = "1.00 (17th November 1993)";

static int viewerwindow_open = FALSE;

static wimp_w viewer_window;

static sprite_area *sprite_area_ptr;

static menu menu_viewer, menu_iconbar;

/* Structure to hold magnify parameters for viewer sprite */
static struct {
  int mul, div;
  int oldmul, olddiv;
  } viewer_magnify = {1, 1, 1, 1};

/* Use ColourTrans to get mapping for sprite (RISC OS 2 Version) */
static os_error *translate_sprite_colours(sprite_header *addr, sprite_pixtrans *translate)
{
  wimp_paletteword sprite_palette[256];
  wimp_paletteword *ptr = (wimp_paletteword *)((int)addr + sizeof(sprite_header));
  int count, entries, colour;
  wimp_paletteword *palette_ptr;

  if (addr->image == sizeof(sprite_header))
    palette_ptr = 0;
  else
    {
      entries = (addr->image - sizeof(sprite_header)) / 8;

      for (count = 0; count < entries; count++)
        {
          sprite_palette[count] = *(ptr++);
          ptr++;
        }
      palette_ptr = sprite_palette;
    }

  if (entries == 256)
    {
      for (count = 0; count < 256; count++)
          {
            colourtran_return_colournumber(sprite_palette[count], &colour);
            *(translate + count) = (sprite_pixtrans)colour;
          }
      return NULL;
    }
  else
    return (colourtran_select_table(addr->mode, palette_ptr, -1, (wimp_paletteword *)-1, translate));
}

/* Use ColourTrans to get mapping for sprite (RISC OS 3 Version) */
/*static os_error *translate_sprite_colours(sprite_header *addr, sprite_pixtrans *translate)
{
  _kernel_swi_regs r;

  r.r[0] = (int)sprite_area_ptr;
  r.r[1] = (int)addr;
  r.r[2] = -1;
  r.r[3] = -1;
  r.r[4] = (int)translate;
  r.r[5] = 1;

  return ((os_error *)_kernel_swi(XOS_Bit | ColourTrans_SelectTable,&r, &r));
}*/

/* Redraw viewer window contents */
static void redraw_sprite(sprite_area *sarea, wimp_redrawstr *r, int scale_up, int scale_down)
{
  int origin_x, origin_y;
  sprite_pixtrans translate[256];
  sprite_factors scale;
  sprite_id id;
  sprite_pixtrans *transptr;
  sprite_info info;

  wimpt_checkmode();

  id.tag = 0x74527053;
  id.s.addr = (sprite_ptr)((int)sarea + sarea->sproff);

  if (sprite_readsize(sarea, &id, &info) != NULL)
    return;

  origin_x = coords_x_toscreen(0, (coords_cvtstr *)&r->box);
  origin_y = coords_y_toscreen(0, (coords_cvtstr *)&r->box) - ((info.height << bbc_modevar(info.mode, Modevar_Yeig)) * scale_up / scale_down);

  if (wimp_readpixtrans(sarea, &id, &scale, translate) != NULL)
    return;

  if (!(wimpt_bpp() == 8 && bbc_modevar(info.mode, Modevar_Log2BPP) == 3
      && (((sprite_header *)id.s.addr)->image - sizeof(sprite_header)) / 8 != 256))
        {
          if (translate_sprite_colours(id.s.addr, translate))
            return;
          transptr = translate;
        }
      else
        transptr = NULL;

  if (scale_up != scale_down)
    {
      scale.xmag *= scale_up;
      scale.ymag *= scale_up;
      scale.xdiv *= scale_down;
      scale.ydiv *= scale_down;
    }

  sprite_put_scaled(sarea, &id, 0, origin_x, origin_y, &scale, transptr);
}

/* Find size of buffer needed for sprite */
static int sprite_size(void)
{
  _kernel_osfile_block block;

  if (_kernel_osfile(Osfile_Readinfo, "<Display$Dir>.Picture", &block) == _kernel_ERROR)
    return -1;
  else
    return (block.start + 4);
}

/* Close viewer window */
static void close_viewer(void)
{
  if (viewerwindow_open)
    {
      wimpt_noerr(wimp_close_wind(viewer_window));
      flex_free((flex_ptr)&sprite_area_ptr);
      viewerwindow_open = FALSE;
    }
}

/* Find out size of sprite in OS coordinates */
static BOOL find_sprite_size(sprite_id *id, int *xsize, int *ysize)
{
  sprite_info info;
  int xeigfactor, yeigfactor;

  if (sprite_readsize(sprite_area_ptr, id, &info) != NULL)
    return FALSE;

  xeigfactor = bbc_modevar(info.mode, Modevar_Xeig);
  yeigfactor = bbc_modevar(info.mode, Modevar_Yeig);

  *xsize = info.width << xeigfactor;
  *ysize = info.height << yeigfactor;

  return TRUE;
}

/* Create a sprite area of the desired length */
static BOOL make_sprite_area(sprite_area **ptr)
{
  int size = sprite_size();

  if (size == -1)
    return FALSE;
  
  if (!flex_alloc((flex_ptr)ptr, size))
    return FALSE;

  sprite_area_initialise(*ptr, size);
    return TRUE;
}

/* Load sprite */
static BOOL load_sprite(void)
{
  if (!make_sprite_area(&sprite_area_ptr))
    return FALSE;

  return (sprite_area_load(sprite_area_ptr, "<Display$Dir>.Picture") == NULL);
}

/* Set extent of viewer window */
static BOOL set_viewer_size(void)
{
  sprite_id id;
  wimp_redrawstr r;
  int xextent, yextent;

  id.tag = 0x74527053;
  id.s.addr = (sprite_ptr)((int)sprite_area_ptr + sprite_area_ptr->sproff);

  if (!find_sprite_size(&id, &xextent, &yextent))
    return FALSE;

  r.w = viewer_window;
  r.box.x0 = 0;
  r.box.y0 = -yextent * viewer_magnify.mul / viewer_magnify.div;
  r.box.x1 = xextent * viewer_magnify.mul / viewer_magnify.div;
  r.box.y1 = 0;

  wimpt_complain(wimp_set_extent(&r));

  return TRUE;  
}

/* Open sprite viewer */
static void display_sprite(void)
{
  wimp_wstate state;

  visdelay_begin();

  if (load_sprite())
    {
      set_viewer_size();

      if (wimpt_complain(wimp_get_wind_state(viewer_window, &state)) == 0)
        {   
          state.o.behind = -1;
          wimpt_noerr(wimp_open_wind(&state.o));
          viewerwindow_open = TRUE;
        }
    }
  else
    werr (0, "Unable to load sprite file");

  visdelay_end();
}

/* Update display when viewer zoom changes */
static void zoom_changed(void *handle)
{
  wimp_wstate wstate;
  wimp_redrawstr r;
  int depth, width;

  handle = handle;

  wimpt_noerr(wimp_get_wind_state(viewer_window, &wstate));
  depth = (wstate.o.box.y1 - wstate.o.box.y0) * (viewer_magnify.mul * viewer_magnify.olddiv) / (viewer_magnify.oldmul * viewer_magnify.div);
  width = (wstate.o.box.x1 - wstate.o.box.x0) * (viewer_magnify.mul * viewer_magnify.olddiv) / (viewer_magnify.oldmul * viewer_magnify.div);

  set_viewer_size();

  wstate.o.box.x1 = wstate.o.box.x0 + width;
  wstate.o.box.y0 = wstate.o.box.y1 - depth;
  wstate.o.x = wstate.o.x * (viewer_magnify.mul * viewer_magnify.olddiv) / (viewer_magnify.oldmul * viewer_magnify.div);
  wstate.o.y = wstate.o.y * (viewer_magnify.mul * viewer_magnify.olddiv) / (viewer_magnify.oldmul * viewer_magnify.div);
  wimpt_noerr(wimp_open_wind(&wstate.o));

  wimpt_noerr(wimp_get_wind_state(viewer_window, &wstate));
  r.w = -1;
  r.box = wstate.o.box;
  wimpt_noerr(wimp_force_redraw(&r));

  viewer_magnify.oldmul = viewer_magnify.mul;
  viewer_magnify.olddiv = viewer_magnify.div;
}

/* Create a window, returning TRUE if OK */
static BOOL create_window(char *name, wimp_w *handle)
{
  wimp_wind *window;

  window = template_syshandle(name);
  if (window==0)
    return FALSE;
  return (wimpt_complain(wimp_create_wind(window,handle)) == 0);
}

/* Set up menu structures needed for program */
static BOOL create_menus()
{
  if (menu_iconbar = menu_new("Display", ">Info,Quit"), menu_iconbar == NULL)
    return FALSE;

  if (menu_viewer = menu_new("Display", ">Zoom"), menu_viewer == NULL)
    return FALSE;

  return TRUE;
}

/* Deal with non-menu icon bar clicks */
static void iconclick(wimp_i icon)
{
  display_sprite();
}

/* Display info dialogue box */
static void info_about_program(void)
{
  dbox d;

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

/* Display and process magnifier box for viewer sprite */
static void viewer_zoom(void)
{
  magnify_select(&viewer_magnify.mul, &viewer_magnify.div, 999, 999, zoom_changed, 0);
}

/* Handle icon bar menu clicks */
static void iconbar_menuproc(void *handle, char *hit)
{
  handle = handle;

  switch (hit[0])
  {
    case Iconbarmenu_Info:
      info_about_program();
      break;

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

/* Handle viewer menu clicks */
static void viewer_menuproc(void *handle, char *hit)
{
  handle = handle;

  switch (hit[0])
  {
    case Viewermenu_Zoom:
      viewer_zoom();
      break;
  }
}

/* Handle redraw requests for viewer window */
static void redraw_request_viewer(wimp_w w)
{
  int more;
  wimp_redrawstr r;

  r.w = w;
  wimpt_noerr(wimp_redraw_wind(&r, &more));

  while (more)
  {
    redraw_sprite(sprite_area_ptr, &r, viewer_magnify.mul, viewer_magnify.div);
    wimp_get_rectangle(&r, &more);
  }
}

/* Handle events for viewer window */
static void viewer_window_handler(wimp_eventstr *e, void *handle)
{
  handle = handle;

  switch (e->e)
  {
    case wimp_EREDRAW:
      redraw_request_viewer(e->data.o.w);
      break;

    case wimp_EOPEN:
      wimpt_noerr(wimp_open_wind(&e->data.o));
      break;

    case wimp_ECLOSE:
      close_viewer();
      break;

    case wimp_EBUT:
      break;
  }
}

static BOOL initialise(void)
{
  flex_init();
  wimpt_init("Display");
  res_init("Display");
  resspr_init();
  template_init();
  dbox_init();

  if (!create_menus())
    return FALSE;

  if (!create_window("Viewer",&viewer_window))
    return FALSE;
  win_register_event_handler(viewer_window, viewer_window_handler, 0);
  if (!event_attachmenu(viewer_window, menu_viewer, viewer_menuproc, 0))
    return FALSE;

  event_setmask(wimp_EMNULL | wimp_EMPTRLEAVE | wimp_EMPTRENTER);

  baricon("!display", (int)resspr_area(), iconclick);
  if (!event_attachmenu(win_ICONBAR, menu_iconbar, iconbar_menuproc, 0))
    return FALSE;

  /* All went alright, so return TRUE */
  return TRUE;
}

/* Perform initialisation, and assuming all is OK, just enter event loop
   (Event processor handles exit condition automatically) */
int main()
{
  if (initialise())
    {
      while(TRUE)
        event_process();
    }
  return 0; /* Don't complain if we can't initialise */
}
