#include <stdio.h>
#include <stdlib.h>
#include <string.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)
{
}

/**
 * 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_state_t* st)
{
  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;

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

  if (st == NULL)
  {
    win.window = win_handle.player;
    _swix(Wimp_GetWindowState, _IN(1), &win);
  }
  else
    win = *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 (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
    int top, right, bottom;

    get_border_sizes(&top, &right, &bottom);

    if (config.video.cfg & cfg_video_lock_aspect)
    {
      // Will the picture be too high or too wide for the screen when enlarged ?
      int w_scrn = screen->width << screen->xeig;
      int h_scrn = (screen->height << screen->yeig) - top;

      if (config.control & cfg_ctrl_scroll_bars)
      {
        w_scrn -= right;
        h_scrn -= bottom;
      }

      if ((h_scrn * 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_setup(player, NULL, FRAC_CHANGE);

  // 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);

  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);
}
