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

#include "kernel.h"
#include "swis.h"
#include "msg.h"
#include "mt.h"
#include "sprite.h"
#include "config.h"
#include "timer1.h"
#include "wimp.h"
#include "keyboard.h"
#include "menu.h"
#include "display.h"
#include "playlist.h"
#include "ka_mem.h"

extern ka_config_t config;
win_handle_t win_handle = {-1,-1,-1,-1};
int task_handle;

/**
 * Creates a window from the given template.
 *
 * @param  templates  Name of window template to use.
 * @param  sprites    Address of sprite area to assign to the window.
 *
 * @returns window handle or -1 in case of error.
 */
static int load_template(char* templates, char* sprites)
{
  _kernel_swi_regs regs;
  int name[4];
  int* buffer;
  char* workspace;

  snprintf((char*) name, sizeof(name), "%s", templates); // make sure it's 3 aligned words
  regs.r[1] = 0;  // find size
  regs.r[5] = (int) name;
  regs.r[6] = 0;
  _kernel_swi(Wimp_LoadTemplate, &regs, &regs);
  buffer = ka_mem_alloc(regs.r[1]);
  workspace = ka_mem_alloc(regs.r[2]);

  // Load template
  regs.r[1] = (int) buffer;
  regs.r[3] = (int) workspace + regs.r[2];
  regs.r[2] = (int) workspace;
  regs.r[4] = -1;
  regs.r[6] = 0;
  _kernel_swi(Wimp_LoadTemplate, &regs, &regs);

  if (!regs.r[6])
    return 0;

  if (sprites)
  {
    int* area;

    // Need to load sprites
    regs.r[0] = READ_CATINFO;
    regs.r[1] = (int) sprites;
    _kernel_swi(OS_File, &regs, &regs);
    if (regs.r[0] != IS_FILE)
      report_error(0, "error2:%s", "Sprites"); // (Cannot open Sprites file)

    area = ka_mem_alloc(regs.r[4]+4);
    area[0] = regs.r[4]+4;
    area[1] = 0;
    area[2] = 16;
    area[3] = 16;
    regs.r[0] = 10+256;
    regs.r[1] = (int) area;
    regs.r[2] = (int) sprites;
    _kernel_swi(OS_SpriteOp, &regs, &regs);
    buffer[16] = (int) area;
  }

  // Create window
  regs.r[1] = (int) buffer;
  _kernel_swi(Wimp_CreateWindow, &regs, &regs);

  return regs.r[0]; // handle
}

static int msgs[] =
{ MESSAGE_MENUWARNING
, MESSAGE_MODECHANGE
, MESSAGE_MENUDELETED
, MESSAGE_HELPREQUEST
, MESSAGE_PALETTECHANGE
, MESSAGE_DATALOAD
, MESSAGE_DATASAVEACK
, MESSAGE_DATASAVE
, MESSAGE_QUIT
};

/**
 * Initialises the task and creates all the windows.
 *
 * @param  name  task name to use.
 * @param  skin  skin to load.
 */
void mt_init(const char* name, const char* skin)
{
  _kernel_swi_regs regs;
  char* templates = "KinoIRes:Templates";
  static char skin_templates[256];
  static char skin_sprites[256];

  regs.r[0] = 350;
  regs.r[1] = *(int*) "TASK";
  regs.r[2] = (int) name;
  regs.r[3] = (int) msgs;  // 0-terminated messageno list
  _kernel_swi(Wimp_Initialise, &regs, &regs);
  task_handle = regs.r[1];

  // Load templates and create windows

  regs.r[0] = READ_CATINFO;
  regs.r[1] = (int) templates;
  _kernel_swi(OS_File, &regs, &regs);
  if (regs.r[0] != IS_FILE)
    report_error(1, "error2:%s", "Templates"); // (Cannot open templates file)

  regs.r[1] = (int) templates;
  _kernel_swi(Wimp_OpenTemplate, &regs, &regs);

  if ((win_handle.filminfo = load_template("FilmInfo", NULL)) == -1)
    report_error(1, "error18:%s", "FilmInfo"); // (FilmInfo template not found)

  if ((win_handle.xfer_send = load_template("xfer_send", NULL)) == -1)
    report_error(1, "error18:%s", "xfer_send"); // (xfer_send template not found)

  _kernel_swi(Wimp_CloseTemplate, &regs, &regs);

  // Load control panel skin

  snprintf(skin_templates, sizeof(skin_templates)
         , "%s.%s.%s", "<KinoSkin$Dir>", skin, "Templates");
  snprintf(skin_sprites, sizeof(skin_sprites)
         , "%s.%s.%s", "<KinoSkin$Dir>", skin, "Sprites");

  regs.r[0] = READ_CATINFO;
  regs.r[1] = (int) skin_templates;
  _kernel_swi(OS_File, &regs, &regs);
  if (regs.r[0] != IS_FILE)
    report_error(1, "error20: %s", skin); // (Cannot find skin)

  regs.r[1] = (int) skin_templates;
  _kernel_swi(Wimp_OpenTemplate, &regs, &regs);

  if ((win_handle.control = load_template("Control", skin_sprites)) == -1)
    report_error(1, "error18:%s", "Control"); // (Control template not found)

  _kernel_swi(Wimp_CloseTemplate, &regs, &regs);
}


void mt_done(void)
{
}

static void mt_getBorders(int window, wimp_rect_t* r)
{
  window_state_t st;
  struct
  {
    int         window;
    wimp_rect_t rect;
  } outline;

  st.window = window;
  _swix(Wimp_GetWindowState, _IN(1), &st);
  if ((st.flags & (1<<16)) == 0) // window closed ?
  {
    st.behind = -3;
    _swix(Wimp_OpenWindow, _IN(1), &st);
    _swix(Wimp_GetWindowState, _IN(1), &st);
  }

  outline.window = window;
  _swix(Wimp_GetWindowOutline, _IN(1), &outline);
  r->x0 = st.vis.x0 - outline.rect.x0;
  r->x1 = outline.rect.x1 - st.vis.x1;
  r->y0 = st.vis.y0 - outline.rect.y0;
  r->y1 = outline.rect.y1 - st.vis.y1;
}

/*
 * Creates/updates the player's window once the plotting size is known.
 *
 * Note: Due to the necessity to attach overlays to a window, in non-multitasking mode
 * a window without title area, scolling areas nor borders is created
 * on top of the screen to position the overlay.
 */
void mt_setupWindow(player_t* player, int reason)
{
  static int initial = 1;
  const video_mode* vmode = &config.video.modes[player->mode];
  const ka_screen_t* screen = &player->screen_plot;
  int b[64], dw, dh, sw, sh;

  // Displayed frame size in os units
  dw = player->pos.width;
  dh = player->pos.height;

  // Screen size in os units
  sw = screen->width  << screen->xeig;
  sh = screen->height << screen->yeig;

  // If screen change, delete window has they don't have identical flags
  if ((reason == SCREEN_CHANGE)
  &&  (win_handle.player != -1))
  {
    _swix(Wimp_DeleteWindow, _IN(1), &win_handle.player);
    win_handle.player = -1;
  }

  // Create player window with work area equal to the screen size
  if (win_handle.player == -1)
  {
    b[0]  = 0;
    b[1]  = 0;
    b[2]  = b[0] + dw;
    b[3]  = b[1] + dh;
    b[4]  = b[5] = 0;      // scroll
    b[6]  = -1;            // on top
    // window flags
    if (player->mode != KA_MODE_MULTITASK)
    {
      b[7]  = (int) 0x80000042;  // window flags
      b[8]  = (int) 0xff0002ff;  // colours (no border + no backround)
    }
    else
    {
      if(config.control & cfg_ctrl_scroll_bars)
        b[7]  = (int) 0xff00c002;  // window flags
      else
        b[7]  = (int) 0x8f00c002;  // window flags
      b[8]  = (int) 0xff000207;    // colours (no background)
    }
    b[9]  = 0x020c0000;    // colours + extended scroll requests
    b[10] = 0;             // work area coords
    if (player->mode != KA_MODE_MULTITASK)
    {
      b[11] = -sh;
      b[12] = sw;
    }
    else
    {
      b[11] = -dh;
      b[12] = dw;
    }
    b[13] = 0;
    b[14] = 0x00000109;    // title flags
    b[15] = 10<<12;        // workarea button type
    b[16] = 1;             // spritearea
    b[17] = 0x00100010;    // min size
    b[18] = (int)playlist_getcurrentfilm();
    b[19] = 0;             // no val str
    b[20] = 1024;
    b[21] = 0;             // no icons
    _swix(Wimp_CreateWindow, _IN(1)|_OUT(0), b, &win_handle.player);
  }
  else // resize work area in case screen size has been changed
  {
    b[0] = 0;
    if (player->mode != KA_MODE_MULTITASK)
    {
      b[1] = -sh;
      b[2] = sw;
    }
    else
    {
      b[1] = -dh;
      b[2] = dw;
    }
    b[3] = 0;
    _swix(Wimp_SetExtent, _INR(0,1), win_handle.player, b);
  }

  if (player->vo_overlay)
  {
    _swix(VideoOverlay_SetWindow, _INR(0,1), player->vo_overlay, win_handle.player);
//    _swix(VideoOverlay_SetPosition, _INR(0,6)
//        , player->vo_overlay
//        , (dw > sw) ? (sw - dw) / 2 : 0
//        , (dh > sh) ? (sh - dh) / 2 : 0
//        , INT_MIN, INT_MIN, INT_MAX, INT_MAX
//        );
  }

  window_state_t* st = (window_state_t*) b;

  st->window = win_handle.player;
  _swix(Wimp_GetWindowState, _IN(1), st);

  if (((st->flags & (1<<16)) == 0) // window closed ?
  ||  (player->mode != KA_MODE_MULTITASK)
  ||  ((player->mode == KA_MODE_MULTITASK) && (vmode->cfg & cfg_video_fit_screen)))
  {
    if (player->mode != KA_MODE_MULTITASK)
    {
      // center on screen
      st->vis.x0 = (sw - dw) / 2;
      st->vis.x1 = st->vis.x0 + dw;
      st->vis.y0 = (sh - dh) / 2;
      st->vis.y1 = st->vis.y0 + dh;
      st->scroll.x = st->scroll.y = 0;
      st->behind = -1; // on top
    }
    else
    {
      wimp_rect_t borders;
      mt_getBorders(win_handle.player, &borders);

      sw -= borders.x0 + borders.x1;
      sh -= borders.y0 + borders.y1;

      if ((st->flags & (1<<16)) == 0) // window closed ?
      {
        // center on screen
        st->vis.x0 = (sw - dw) / 2;
        st->vis.y1 = (sh + dh) / 2;
      }

      // Adjust window size to take account of scale factors
      st->vis.x1 = st->vis.x0 + dw;
      st->vis.y0 = st->vis.y1 - dh;
      st->scroll.x = st->scroll.y = 0; // disable scroll action
      // The title bar text, indirected from b[19] has already
      // been updated. But Wimp_OpenWindow will only update the title
      // if the window size has changed. We must therefore force a
      // redraw of the title bar area.
      _swix(Wimp_ForceRedraw, _INR(0,4)
           , -1
           , st->vis.x0
           , st->vis.y1
           , st->vis.x1
           , st->vis.y1 + borders.y1
           );
// nested wimp version
//      _swix(Wimp_ForceRedraw, _INR(0,2), win_handle.player, *(int *)"TASK", 3);
    }

    _swix(Wimp_OpenWindow, _IN(1), st);

    // Gain input focus, but not every time a new file is played
    // from a playlist.
    if (!(player->status_flags & player_status_new) || initial)
    {
      _swix(Wimp_SetCaretPosition, _INR(0,5), win_handle.player, -1, -1000, -1000, 40, 0);
    }

    open_controls(player, NULL, PANEL_TRACK);
  }

  initial = 0;
}

/**
 * Checks the lock_aspect and lock_size options and modifies the
 * window size, scaling, and scroll offsets to suit.
 *
 * @param  player  Pointer to player structure.
 * @param  b       Pointer to window state block.
 */
void mt_open(player_t* player, const window_open_t* st)
{
  static wimp_rect_t lastpos = {0, 0, 0, 0}; // for size toggle

  const ka_screen_t* screen = &player->screen_plot;
  const video_mode* vmode = &config.video.modes[player->mode];
  int w, h, w0, h0, h1;
  window_state_t win;
  wimp_rect_t borders;
  int wmax, hmax;

  if (player->mode != KA_MODE_MULTITASK)
    return;

  mt_getBorders(win_handle.player, &borders);

  // Will the picture be too high or too wide for the screen when enlarged ?
  wmax = screen->width << screen->xeig;
  hmax = screen->height << screen->yeig;
  wmax -= borders.x0 + borders.x1;
  hmax -= borders.y0 + borders.y1;

  if (st == NULL)
  {
    win.window = win_handle.player;
    _swix(Wimp_GetWindowState, _IN(1), &win);
  }
  else
  {
    memcpy(&win, st, sizeof(*st));
    win.flags = 0; // not provided by st
  }

  // Check for zero window size (can happen using mouseAxess)
  if ((win.vis.x1 - win.vis.x0) <= 0)
    win.vis.x1 = win.vis.x0 + 16;
  if ((win.vis.y1 - win.vis.y0) <= 0)
    win.vis.y0 = win.vis.y1 - 16;

  // Source frame size in os units scaled for x_mag, y_mag
  w0 = player->paint.dst_pix_width << screen->xeig;
  h0 = player->paint.dst_height << screen->yeig;

  if ((config.video.cfg & cfg_video_lock_aspect)
  &&  (config.video.cfg & cfg_video_pixel_aspect))
    h1 = player->aspect.height << screen->yeig;
  else
    h1 = h0;

  w = win.vis.x1 - win.vis.x0;
  h = win.vis.y1 - win.vis.y0;

  // If toogle button pressed, change to full size of no zoom
  // Note:
  {
    wimp_mouse_t m;

    _swix(Wimp_GetPointerInfo, _IN(1), &m);
    if ((m.window == win_handle.player)
    &&  (m.icon == -5)
    &&  (m.button))
    {
      if ((((lastpos.x1 - lastpos.x0) == w0) && ((lastpos.y1 - lastpos.y0) == h1))
      ||  ((lastpos.x1 - lastpos.x0) == 0)  // first time
      ||  ((lastpos.y1 - lastpos.y0) == 0))
      {
        w = wmax;
        h = hmax;
      }
      else
      {
        // no zoom
        w = w0;
        h = h1;
      }
      // center on screen
      win.vis.x0 = (wmax - w) / 2;
      win.vis.y1 = (hmax + h) / 2;
      win.vis.x1 = win.vis.x0 + w;
      win.vis.y0 = win.vis.y1 - h;
    }
  }

  if (vmode->cfg & cfg_video_fit_screen)
  {
    // Lock the picture size to the visible window size,
    // work out new magnifications so that the frame just fills the window

    if (config.video.cfg & cfg_video_lock_aspect)
    {
      // Will the picture be too high or too wide for the screen when enlarged ?

      if ((hmax * w0) > (w * h1))
      {
        // Lock height to width
        win.vis.y0 = win.vis.y1 - ((h1 * w) / w0);
      }
      else
      {
        // Lock width to height
        win.vis.x1 = win.vis.x0 + ((w0 * h) / h1);
      }

      w = win.vis.x1 - win.vis.x0;
      h = win.vis.y1 - win.vis.y0;
    }

    player->x_mag = (w0 << FRAC) / w;
    player->y_mag = (h0 << FRAC) / h;
  }
  else // Picture size not locked to window size
  {
    // If the frame width or height are smaller than the window,
    // adjust the mag's otherwise leave alone.
    int ws, hs;

    // Just in case of switching just before
    if (config.video.cfg & cfg_video_lock_aspect)
    {
      if (config.video.cfg & cfg_video_pixel_aspect)
        player->y_mag = (player->x_mag * player->paint.dst_height) / player->aspect.height;
      else
        player->y_mag = player->x_mag;
    }

    ws = (w0 << FRAC) / player->x_mag;
    if (ws < w)
    {
      player->x_mag = (w0 << FRAC) / w;
    }

    hs = (h0 << FRAC) / player->y_mag;
    if (hs < h)
    {
      player->y_mag = (h0 << FRAC) / h;
      if (config.video.cfg & cfg_video_lock_aspect)
      {
        if (config.video.cfg & cfg_video_pixel_aspect)
          player->x_mag = (player->y_mag * player->aspect.height) / player->paint.dst_height;
        else
          player->x_mag = player->y_mag;
      }
    }
  }

  display_fracChange(player);

  // Ensure correct extent
  {
    wimp_rect_t ext;
    ext.x0 = 0;
    ext.y0 = -player->pos.height;
    ext.x1 = player->pos.width;
    ext.y1= 0;
    _swix(Wimp_SetExtent, _INR(0,1), win_handle.player, &ext);
  }

  // Limit scroll to new frame size
  if (player->vo_overlay)
  {
    w = player->pos.width;
    h = player->pos.height;
  }
  else
  {
    w = (w0 << FRAC) / player->x_mag;
    h = (h0 << FRAC) / player->y_mag;
  }

  w -= win.vis.x1 - win.vis.x0;
  if (w <= 0)
    win.scroll.x = 0;
  else if (win.scroll.x > w)
    win.scroll.x = w;

  h -= win.vis.y1 - win.vis.y0;
  if (h <= 0)
    win.scroll.y = 0;
  else if (win.scroll.y < -h)
    win.scroll.y = -h;

  _swix(Wimp_OpenWindow, _IN(1), &win);

  // Save current pos for toggle size
  _swix(Wimp_GetWindowState, _IN(1), &win);
  lastpos = win.vis;

  player->status_flags |= player_status_redrawframe;

  open_controls(player, NULL, PANEL_TRACK);
}

/**
 * Shows/Hides the player window's scroll bars.
 */
void mt_setScrolls(player_t* player)
{
  _kernel_swi_regs regs;
  int c[6], b[64], have_focus;

  // Requirement changed, need to delete then recreate the window.
  // (there must be a better way to do this)
  regs.r[1] = (int) c;
  _kernel_swi(Wimp_GetCaretPosition, &regs, &regs);
  have_focus = (c[0] == win_handle.player) && (win_handle.player != -1);

  b[0] = win_handle.player;
  regs.r[1] = (int) b;
  _kernel_swi(Wimp_GetWindowInfo, &regs, &regs);
  _kernel_swi(Wimp_DeleteWindow, &regs, &regs);
  if (config.control & cfg_ctrl_scroll_bars)
    b[8] |= (7 << 28);
  else
    b[8] &= ~(7 << 28);
  regs.r[1] = (int) &b[1];
  _kernel_swi(Wimp_CreateWindow, &regs, &regs);
  win_handle.player = b[0] = regs.r[0];
  regs.r[1] = (int) b;
  _kernel_swi(Wimp_OpenWindow, &regs, &regs);
  if (player->vo_overlay)
  {
    _swix(VideoOverlay_SetWindow, _INR(0,1), player->vo_overlay, win_handle.player);
  }

  if (have_focus)
  {
    regs.r[0] = b[0];
    regs.r[1] = c[1];
    regs.r[2] = c[2];
    regs.r[3] = c[3];
    regs.r[4] = c[4];
    _kernel_swi(Wimp_SetCaretPosition, &regs, &regs);
  }

  player->status_flags |= player_status_redrawframe;

  open_controls(player, NULL, PANEL_TRACK);
}

/*
 * msg_help
 * --------
 * Supplies help text. Requires the HelpRequest message block and current menu tag.
 */
void msg_help(int *b)
{
  _kernel_swi_regs regs;
  int ok = 0;
  char* dst = (char *)&b[5];
  int dstsize = 256 - 20;

  // Player window
  if(b[8] == win_handle.player)
    ok = msg_lookup("player_wh", 0, dst, dstsize);

  // Save frame dialogue box
  else if(b[8] == win_handle.xfer_send)
    ok = msg_lookup("saveframe_wh", 0, dst, dstsize);

  // Control panel
  else if(b[8] == win_handle.control)
    ok = msg_lookup("control_wh", b[9], dst, dstsize);

  // Film information window
  else if(b[8] == win_handle.filminfo)
    ok = msg_lookup("filminfo_wh", 0, dst, dstsize);

  // unknown window, check for a menu
  else
  {
    ok = menu_help(b[8], b[9], dst, dstsize);
  }

  if(!ok)
    return; // no help available

  // send help text to Help application
  regs.r[0] = 17;     // User message
  regs.r[1] = (int)b;
  regs.r[2] = b[1];   // sender task handle
  b[0] = 256; // buffer length
  b[3] = b[2];
  b[4] = MESSAGE_HELPREPLY;
  _kernel_swi(Wimp_SendMessage, &regs, &regs);
}
