/*
 * keyboard.c
 * ----------
 * This file contains the keyboard command handler and most of the
 * multitasking code. command_entry() is called once every frame from
 * the frame draw function within the decoder.
 */

#include <string.h>
#include <stdlib.h>
#include "kernel.h"
#include "swis.h"
#include "inttypes.h"
#include "player.h"
#include "audio.h"
#include "config.h"
#include "display.h"
#include "keyboard.h"
#include "menu.h"
#include "osd.h"
#include "sprite.h"
#include "mt.h"
#include "wimp.h"
#include "choices.h"
#include "playlist.h"
#include "ka_stream.h"
#include "ka_log.h"

// controls window
#define ICON_BACKGROUND 0
#define ICON_PLAY      1
#define ICON_STEP      2
#define ICON_PAUSE     3
#define ICON_STOP      4
#define ICON_REW       5
#define ICON_FF        6
#define ICON_AUDIO     7
#define ICON_LINE_BACK 8
#define ICON_LINE      9
#define ICON_PREV     10
#define ICON_NEXT     11
#define ICON_PLAYLIST 12
#define ICON_CLOSE    13

// frame save window
#define ICON_SAVE      0
#define ICON_FILENAME  1
#define ICON_OK        2
static char * save_name; // pointer to icon indirected filename string
static int save_name_size = 0;

#define DRAG_SAVEFRAME 1
#define DRAG_PLAYER    2
#define DRAG_CONTROLS  3
static int drag;

static char key_config[12];

// distance in OS units of control panel docking snap range.
#define H_SNAP 100
#define V_SNAP  40
static int undocked = 0; // see below
/* 0 = docked.
 * 1 = within snap range when initially dragging from the player.
 * 2 = undocked, will snap back if within range when moving control panel.
 */

#define TOOLSIZE 40 // size in OS units of virtual scroll bars.

typedef struct
{
  ka_demux_pos_t           pos;
  ka_demux_audio_info_t    audio;
  ka_demux_subtitle_info_t subtitle;
  ka_demux_angle_info_t    angle;
} ctrlInfo;

static ctrlInfo lastInfo = {{0, 0, 0, 0}, 0, 0};

/*
 * allowed_char
 * ------------
 * Returns 1 if the supplied character is a valid filename character
 */
static int allowed_char(char c)
{
  if(c < 0x20)
    return 0;
  switch(c)
  {
    case '$': case '&': case '%': case '@': case 0x5c:
    case '^': case ':': case '.': case ',': case '#':
    case '*': case '"': case '|':
      return 0;
  }
  return 1;
}

void saveas_init(player_t* player)
{
  int i, blk[10];
  _kernel_swi_regs regs;
  char* s;

  // update filename string
  blk[0] = win_handle.xfer_send;
  blk[1] = ICON_FILENAME;
  regs.r[1] = (int) blk;
  _kernel_swi(Wimp_GetIconState, &regs, &regs);
  save_name = (char*) blk[7]; // pointer will be used elsewhere
  save_name_size = blk[9];

  // make sure save_name is terminated properly
  i = 0;
  while ((i < save_name_size) && allowed_char(save_name[i]))
    i++;
  save_name[i] = 0;

  // Use any previously dragged or typed directory
  if ((s = strrchr(save_name, '.')) == 0)
    s = save_name;
  else
    s++;
  snprintf
    ( s
    , save_name_size + (s - save_name)
    , "frame%05d_%1d"
    , player->draw.picturenr, player->draw.fieldnr
    );
}

void savename_set(const char* text)
{
  snprintf(save_name, save_name_size, "%s", text);
}

/*
 * drag_start
 * ----------
 * Called when drag is begun.
 * entry,
 *   mse : mouse click poll block
 *   win : window block
 */
static void drag_start(const wimp_mouse_t* m, const window_state_t* win, int new_drag)
{
  _kernel_swi_regs  regs;
  int box[14], drag_type;
  char* spr = "file_ff9";

  switch (new_drag)
  {
    case DRAG_SAVEFRAME:
      box[0] = m->window;
      box[1] = m->icon;
      regs.r[1] = (int) box;
      _kernel_swi(Wimp_GetIconState, &regs, &regs);

      box[2] += win->vis.x0 - win->scroll.x;
      box[3] += win->vis.y1 - win->scroll.y;
      box[4] += win->vis.x0 - win->scroll.x;
      box[5] += win->vis.y1 - win->scroll.y;

      regs.r[0] = 0;
      regs.r[1] = 1;
      regs.r[2] = (int) spr;
      regs.r[3] = (int) &box[2];
      _kernel_swi(DragASprite_Start, &regs, &regs);
    break;

    case DRAG_PLAYER:
      // determine drag type, from mouse position in window
      if(m->pos.x > (win->vis.x1 - TOOLSIZE))   // right edge
      {
        if(m->pos.y < (win->vis.y0 + TOOLSIZE)) // bottom edge
          drag_type = 2;    // resize
        else if(m->button == 4*16)
          drag_type = 4;    // vertical scroll (select)
        else
          drag_type = 12;   // both scrolls (adjust)
      }
      else if(m->pos.y < (win->vis.y0 + TOOLSIZE)) // bottom edge
      {
        if(m->button == 4*16)
          drag_type = 3;    // horizontal scroll (select)
        else
          drag_type = 12;   // both scrolls (adjust)
      }
      else
        return;             // no drag for central area

      box[0] = m->window;
      box[1] = drag_type;
      box[2] = win->vis.x0;
      box[3] = win->vis.y0;
      box[4] = win->vis.x1;
      box[5] = win->vis.y1;
      regs.r[1] = (int) box;
      _kernel_swi(Wimp_DragBox, &regs, &regs);
    break;

    case DRAG_CONTROLS:
      box[0] = m->window;
      box[1] = 1;      // drag_type = position
      box[2] = win->vis.x0;
      box[3] = win->vis.y0;
      box[4] = win->vis.x1;
      box[5] = win->vis.y1;
      regs.r[1] = (int) box;
      _kernel_swi(Wimp_DragBox, &regs, &regs);
      undocked = 1;
    break;
  }

  drag = new_drag;
}

/*
 * drag_return
 * -----------
 * called when a drag is ended.
 */
void drag_return(void)
{
  _kernel_swi_regs  regs;
  int blk[16];

  regs.r[1] = (int) blk;
  _kernel_swi(Wimp_GetPointerInfo, &regs, &regs);

  if (drag == DRAG_SAVEFRAME)
  {
    char * leafname = strrchr(save_name,'.');
    if(leafname)
      leafname++;
    else
      leafname = save_name;

    blk[5] = blk[3]; // window handle
    blk[6] = blk[4]; // icon handle
    blk[7] = blk[0]; // mouse x
    blk[8] = blk[1]; // mouse y
    blk[0] = 56;
    blk[1] = 0;
    blk[2] = 0;
    blk[3] = 0;
    blk[4] = MESSAGE_DATASAVE;
    blk[9] = 1024; // size
    blk[10] = FILETYPE_SPRITE;
    snprintf((char*) &blk[11], 20, "%s", leafname);

    regs.r[0] = 17;
    regs.r[1] = (int) blk;
    regs.r[2] = blk[5]; // window handle
    regs.r[3] = blk[6]; // icon handle
    _kernel_swi(Wimp_SendMessage, &regs, &regs);

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

  drag = 0;
}

/*
 * keyboard_init
 * -------------
 */
void keyboard_init(void)
{
  _kernel_swi_regs regs;
  int i;

  if (config.video.mode == KA_MODE_MULTITASK) return;

  // cursor keys act as function keys
  regs.r[0] = 4;
  regs.r[1] = 2;
  _kernel_swi(OS_Byte, &regs, &regs);
  key_config[0] = regs.r[1];

  // set tab value
  regs.r[0] = 0xdb;
  regs.r[1] = 0x8a;
  regs.r[2] = 0;
  _kernel_swi(OS_Byte, &regs, &regs);
  key_config[1] = regs.r[1];

  // interpretation of codes C0-CF
  for (i = 0xdd; i <= 0xe4; i++)
  {
    regs.r[0] = i;
    regs.r[1] = 2;
    regs.r[2] = 0;
    _kernel_swi(OS_Byte, &regs, &regs);
    key_config[i - 0xdb] = regs.r[1];
  }

  // disable esc
  regs.r[0] = 0xe5;
  regs.r[1] = 1;
  regs.r[2] = 0;
  _kernel_swi(OS_Byte, &regs, &regs);
  key_config[11] = regs.r[1];

  // clear any pending Esc condition
  regs.r[0] = 0x7c;
  _kernel_swi(OS_Byte, &regs, &regs);
}

/*
 * keyboard_final
 * --------------
 */
void keyboard_final(void)
{
  _kernel_swi_regs regs;
  int i;

  // restore esc status
  regs.r[0] = 0xe5;
  regs.r[1] = key_config[11];
  regs.r[2] = 0;
  _kernel_swi(OS_Byte, &regs, &regs);

  // restore interpretation of codes C0-CF
  for (i = 0xdd; i <= 0xe4; i++)
  {
    regs.r[0] = i;
    regs.r[1] = key_config[i - 0xdb];
    regs.r[2] = 0;
    _kernel_swi(OS_Byte, &regs, &regs);
  }

  // restore set tab value
  regs.r[0] = 0xdb;
  regs.r[1] = key_config[1];
  regs.r[2] = 0;
  _kernel_swi(OS_Byte, &regs, &regs);

  // restore cursor keys
  regs.r[0] = 4;
  regs.r[1] = key_config[0];
  _kernel_swi(OS_Byte, &regs, &regs);
}

/*
 * keyboard_pollShift
 * ------------------
 */
static int keyboard_pollShift(void)
{
  int pressed;

  _swix(OS_Byte, _INR(0,1)|_OUT(1), 121, 0x80, &pressed);

  return (pressed != 0);
}

/*
 * keyboard_key
 * ------------
 */
int keyboard_key(void)
{
  _kernel_swi_regs regs;
  int key = -1;

  regs.r[0] = 129;
  regs.r[1] = 0;
  regs.r[2] = 0;
  _kernel_swi(OS_Byte, &regs, &regs);

  if (!regs.r[2])
  {
    if (regs.r[1])
      key = regs.r[1];
    else
    {
      // Ctrl keys are configured to return a leading 0
      regs.r[0] = 129;
      regs.r[1] = 0;
      regs.r[2] = 0;
      _kernel_swi(OS_Byte, &regs, &regs);

      if (!regs.r[2])
      {
        if (regs.r[1])
          key = 0x100 + regs.r[1];
        else
          key = 0;
      }
    }
  }

  return (key != -1) ? key : 0;
}

/*
 * set_zoom
 * --------
 */
void set_zoom(player_t* player, int zoom, int inc)
{
  if (player->scaleconv.type != ka_scaler_none)
  {
    int old = player->x_mag;

    if (zoom == 0) zoom = (100<<FRAC)/player->x_mag;
    zoom += inc;
    if (zoom < 10) zoom = 10;
    if (zoom > 1000) zoom = 1000;

    player->x_mag = (100<<FRAC)/zoom;

    if (old == player->x_mag)
      return;

    player_adaptDisplay(player, player->mode, ZOOM_CHANGE);
  }
  else
  {
    if (zoom == 0) zoom = player->zoom;

    if (inc > 0)
    {
      if (zoom < 50) zoom = 50;
      else if (zoom < 100) zoom = 100;
      else if (zoom < 200) zoom = 200;
      else if (zoom < 300) zoom = 300;
      else zoom = 400;
    }
    else if (inc < 0)
    {
      if (zoom > 400) zoom = 400;
      else if (zoom > 300) zoom = 300;
      else if (zoom > 200) zoom = 200;
      else if (zoom > 100) zoom = 100;
      else zoom = 50;
    }

    config.video.modes[player->mode].zoom = zoom;

    if (player->zoom == zoom)
      return;

    player_adaptDisplay(player, player->mode, ZOOM_CHANGE);
  }
}

/*
 * setAudioStream
 * --------------
 * Select an audio stream.
 */
static void setAudioStream(player_t* player, uint32_t channel)
{
  ka_demux_audio_info_t info;

  config.audio.stream = ka_stream_selectAudioStream(player->stream, channel);

  ka_stream_getAudioInfo(player->stream, -1, &info);

  if (info.channels)
     osd_report
       ( player
       , "msg_streamaudio_ch:%d %s %d %s"
       , player->stream->audio.channel, info.type, info.channels, info.lang
       );
   else
     osd_report
      ( player
      , "msg_streamaudio:%d %s %s"
      , player->stream->audio.channel, info.type, info.lang
      );
}

/*
 * setAngle
 * --------
 * Select an angle.
 */
static void setAngle(player_t* player, uint32_t angle)
{
  ka_demux_angle_info_t info;

  if (ka_stream_setAngle(player->stream, angle) > 0)
    player->status_flags |= player_status_seek;

  ka_stream_getAngleInfo(player->stream, &info);
  osd_report(player, "msg_streamangle:%d", info.angleId);
}

/*
 * update_volume
 * -------------
 */
static void update_volume(player_t* player, int delta)
{
  if (player->stream && player->stream->audio.astream)
  {
    int val = config.audio.volume + delta;
    if (val < 0) val = 0;
    if (val > 127) val = 127;

    config.audio.volume = val;
    audio_volume(player->stream->audio.astream, config.audio.volume);
    osd_report(player, "msg_volume:%d", config.audio.volume);
  }
}

/*
 * update_timeline
 * ---------------
 */
static void update_timeline(void)
{
  _kernel_swi_regs regs;
  int b[10], line_len, new_pos, old_pos;

  b[0] = win_handle.control;
  b[1] = ICON_LINE_BACK;
  regs.r[1] = (int)b;
  _kernel_swi(Wimp_GetIconState, &regs, &regs);
  line_len = b[4] - b[2];
  b[1] = ICON_LINE;
  _kernel_swi(Wimp_GetIconState, &regs, &regs);
  old_pos = b[4];
  new_pos = b[2] + (int) ((((uint64_t) line_len) * lastInfo.pos.curPos) / lastInfo.pos.endPos);

  if (old_pos != new_pos)
  {
    regs.r[0] = b[0];
    regs.r[1] = b[1];
    regs.r[2] = b[2];
    regs.r[3] = b[3];
    regs.r[4] = new_pos;
    regs.r[5] = b[5];
    _kernel_swi(Wimp_ResizeIcon, &regs, &regs);
    regs.r[0] = b[0];
    regs.r[2] = b[3];
    regs.r[4] = b[5];
    if (old_pos < new_pos)
    {
      regs.r[1] = old_pos;
      regs.r[3] = new_pos;
    }
    else
    {
      regs.r[1] = new_pos;
      regs.r[3] = old_pos;
    }
    _kernel_swi(Wimp_ForceRedraw, &regs, &regs);
  }
}

/*
 * update_controls
 * ---------------
 */
void update_controls(player_t* player)
{
  int b[4];
  _kernel_swi_regs regs;

  // to reflect the effect of keypresses, mouseclicks, and menu selections

  if ((player->mode != KA_MODE_MULTITASK)
  || !(config.control & cfg_ctrl_controls))
    return;

  regs.r[1] = (int) b;
  b[0] = win_handle.control;
  b[3] = IC_SELECTED | IC_SHADED;

  b[1] = ICON_PLAY;
  b[2] = (player_getPlayStatus(player) == play_status_play) ? IC_SELECTED : 0;
  _kernel_swi(Wimp_SetIconState, &regs, &regs);

  b[1] = ICON_STEP;
  b[2] = (player_getPlayStatus(player) == play_status_advance) ? IC_SELECTED : 0;
  _kernel_swi(Wimp_SetIconState, &regs, &regs);

  b[1] = ICON_PAUSE;
  b[2] = (player_getPlayStatus(player) == play_status_pause) ? IC_SELECTED : 0;
  _kernel_swi(Wimp_SetIconState, &regs, &regs);

  b[1] = ICON_STOP;
  b[2] = (player_getPlayStatus(player) == play_status_stop) ? IC_SELECTED : 0;
  _kernel_swi(Wimp_SetIconState, &regs, &regs);

  b[1] = ICON_AUDIO;
    b[2] = (config.audio.cfg & cfg_audio_mute) ? IC_SELECTED : 0;
  if (!(config.audio.cfg & cfg_audio_play))
    b[2] |= IC_SHADED;
  _kernel_swi(Wimp_SetIconState, &regs, &regs);

  b[1] = ICON_PLAYLIST;
  b[2] = 0;
  _kernel_swi(Wimp_SetIconState, &regs, &regs);
}

/*
 * update_playinfo
 * ---------------
 */
void update_playinfo(player_t* player)
{
  ctrlInfo info;

  if (player->stream)
  {
    ka_stream_getPosInfo(player->stream, &info.pos);
    ka_stream_getAudioInfo(player->stream, -1, &info.audio);
    ka_stream_getSubtitleInfo(player->stream, -2, &info.subtitle);
    ka_stream_getAngleInfo(player->stream, &info.angle);
    while (info.pos.endPos > (1<<16))
    {
      info.pos.curPos >>= 1;
      info.pos.endPos >>= 1;
    }
  }
  else
  {
    info.pos.titleNr = 0;
    info.pos.chapterNr = 0;
    info.pos.curPos = 0;
    info.pos.endPos = 100;
    info.audio.streamId = 0;
    info.audio.nrStreams = 0;
    info.subtitle.streamId = 0;
    info.subtitle.nrStreams = 0;
    info.angle.angleId = 1;
    info.angle.nrAngles = 1;
  }

  if ((lastInfo.pos.titleNr != info.pos.titleNr)
  ||  (lastInfo.pos.chapterNr != info.pos.chapterNr)
  ||  (lastInfo.audio.streamId != info.audio.streamId)
  ||  (lastInfo.audio.nrStreams != info.audio.nrStreams)
  ||  (lastInfo.subtitle.streamId != info.subtitle.streamId)
  ||  (lastInfo.subtitle.nrStreams != info.subtitle.nrStreams))
    menu_refresh(player, NULL);

  lastInfo = info;

  if (config.control & cfg_ctrl_controls)
  {
    static int old_play;
    int cur_play;

    update_timeline();

    // catches playlists with both es and ps streams
    cur_play = config.audio.cfg & cfg_audio_play;
    if (cur_play != old_play)
      update_controls(player); // update mute button
    old_play = cur_play;
  }
}

/*
 * open_controls
 * -------------
 * The parameter block 'b' is only used when the action is PANEL_DRAG,
 * when it should be the control panel open window request block.
 */
void open_controls(player_t* player, const window_open_t* w, int action)
{
  window_state_t c; // controls window block
  window_state_t p; // player window outline block

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

  if (action == PANEL_TRACK)
  {
    if (!(config.control & cfg_ctrl_controls)) return;
    if (undocked > 0) return;
  }

  if (action == PANEL_CLOSE)
  {
    _swix(Wimp_CloseWindow, _IN(1), &win_handle.control);
    return;
  }

  if (w == NULL)
  {
    c.window = win_handle.control;
    _swix(Wimp_GetWindowState, _IN(1), &c);
  }
  else
  {
    memcpy(&c, w, sizeof(*w));
    c.flags = 0; // not provided
  }

  p.window = win_handle.player;
  _swix(Wimp_GetWindowOutline, _IN(1), &p);

  // check if within snap range of player
  if ((c.vis.x0 > (p.vis.x0 - H_SNAP)) && (c.vis.x0 < (p.vis.x0 + H_SNAP))
  &&  (c.vis.y1 > (p.vis.y0 - V_SNAP)) && (c.vis.y1 < (p.vis.y0 + V_SNAP)))
  {
    if (undocked == 2)
      undocked = 0;
  }
  else if (undocked == 1)
    undocked = 2;

  if (undocked == 0)
  {
    // attach controls below player
    c.vis.x1 += p.vis.x0 - c.vis.x0;
    c.vis.x0 = p.vis.x0;
    c.vis.y0 += p.vis.y0 - c.vis.y1;
    c.vis.y1 = p.vis.y0;
  }
  else if(action == PANEL_DRAG)
  {
    _swix(Wimp_OpenWindow, _IN(1), &c);
    return;
  }

  _swix(Wimp_GetWindowState, _IN(1), &p);
  c.behind = p.behind; // behind the window that is in front of the player
  _swix(Wimp_OpenWindow, _IN(1), &c);
}

/**
 * Get mouse position and action.
 * Behave as MouseClick event of player window, i.e. x*256 for normal clicks, x for double-clicks.
 *
 * @param player Player.
 * @param player m mouse structure to fill in.
 */
void mouse_get(player_t* player, wimp_mouse_t* m)
{
  static wimp_mouse_t clickMouse = {{0, 0}, 0, -1, -1};
  static int clickTime = 0;
  static int oldButton = 0;
  int time;

  // Mouse info, Wimp_GetPointerInfo works only while multitasking
  if (player->mode == KA_MODE_MULTITASK)
  {
    _swix(Wimp_GetPointerInfo, _IN(1), m);
    oldButton = m->button = 0; // Ignore, let MouseClick event do the job
    return;
  }

  _swix(OS_Mouse, _OUTR(0, 2), &m->pos.x, &m->pos.y, &m->button);
  m->window = win_handle.player;
  m->icon = -1;
  // SELECT and ADJUST * 256
  m->button = (m->button & 2) | ((m->button & 5) << 8);

  _swix(OS_ReadMonotonicTime, _OUT(0), &time);

  // buttons changed ?
  int changes = m->button ^ oldButton;
  oldButton = m->button;

  // Keep only newly pressed ones
  m->button &= changes;

  // click or double-click
  if (m->button)
  {
    // same button as first click
    if ((clickMouse.button == m->button)
    // did not move much since first click
    &&  (abs(m->pos.x - clickMouse.pos.x) <= 8)
    &&  (abs(m->pos.y - clickMouse.pos.y) <= 8)
    // nor did take too much time
    &&  ((time - clickTime) < 50))
    {
      // double-ckick
      m->button >>= 8;
      // clear first click
      clickMouse.button = 0;
    }
    else
    {
      // single-click
      clickMouse = *m;
      clickTime = time;
    }
  }
}

/**
 * Handle mouse movement and clicks.
 *
 * @param player Player.
 * @param player m mouse structure.
 */
void mouse_action(player_t* player, const wimp_mouse_t* m)
{
  window_state_t win;

  // OSD may react on mouse mouvements even outside main window / full screen
  if (!m->button
  ||  (m->window == win_handle.player))
  {
    if (osd_mouseAction(player, m))
      return;
  }

  if (!m->button)
    return;

  win.window = m->window;
  _swix(Wimp_GetWindowState, _IN(1), &win);

  // Save frame window
  //-------------------
  if (m->window == win_handle.xfer_send)
  {
    switch (m->icon)
    {
      case ICON_SAVE:
        if (m->button == 4*16) // start a drag
          drag_start(m, &win, DRAG_SAVEFRAME);
      break;

      case ICON_OK: // save frame now
      {
        char * filename, buf[64];

        // if filename contains a path, use it, else save to default dir
        if (strrchr(save_name, '.'))
          filename = save_name;
        else
        {
          snprintf(buf, sizeof(buf), "<KinoSave$Dir>.%s", save_name);
          filename = buf;
        }
        save_frame(player, filename);
      }
      break;
    }
  }
  // Player window or full screen
  //-----------------------------
  else if (m->window == win_handle.player)
  {
    switch (m->button)
    {
      case 1*256: // Adjust
      {
        if (player->mode == KA_MODE_MULTITASK)
        {
          if (!(config.control & cfg_ctrl_scroll_bars))
            if ((m->pos.x > (win.vis.x1 - TOOLSIZE))  // right edge or
            ||  (m->pos.y < (win.vis.y0 + TOOLSIZE))) // bottom edge
              break;
          do_command(player, CMD_REPLAY_STEP); // single step
          // gain input focus
          _swix(Wimp_SetCaretPosition, _INR(0,5), win_handle.player, -1, -1000, -1000, 40, 0);
        }
        else
        {
          do_command(player, CMD_REPLAY_STEP); // single step
        }
      }
      break;

      case 2: // Menu
      {
        if (player->mode == KA_MODE_MULTITASK)
          menu_open(player, MENU_MAIN, m->pos.x-64, m->pos.y);
        else
          do_command(player, CMD_SCREEN_MULTITASK);
      }
      break;

      case 4*256: // Select
      {
        if (player->mode == KA_MODE_MULTITASK)
        {
          if (!(config.control & cfg_ctrl_scroll_bars))
            if ((m->pos.x > (win.vis.x1 - TOOLSIZE))  // right edge or
            ||  (m->pos.y < (win.vis.y0 + TOOLSIZE))) // bottom edge
              break;
          do_command(player, CMD_SCREEN_CLICK); // toggle pause
          // gain input focus
         _swix(Wimp_SetCaretPosition, _INR(0,5), win_handle.player, -1, -1000, -1000, 40, 0);
        }
        else
        {
          do_command(player, CMD_SCREEN_CLICK); // toggle pause
        }
      }
      break;

      case 1*16: // adjust drag start
      case 4*16: // select drag start
      {
        if (!(config.control & cfg_ctrl_scroll_bars))
          drag_start(m, &win, DRAG_PLAYER);
      }
      break;

      case 4: // select double-click
      {
        do_command(player, CMD_SCREEN_DBL_CLICK); // toggle screen and pause (cf. single click)
      }
      break;
    }
  }
  // Control Panel
  //---------------
  else if (m->window == win_handle.control)
  {
    switch (m->icon)
    {
      case ICON_BACKGROUND:
        if ((m->button == 1*16) || (m->button == 4*16))
          drag_start(m, &win, DRAG_CONTROLS);
      break;
      case ICON_PLAY:
          do_command(player, CMD_REPLAY_TOGGLE);
      break;
      case ICON_STEP:
       if (player_getPlayStatus(player) != play_status_pause)
          do_command(player, CMD_REPLAY_TOGGLE);
        do_command(player, CMD_REPLAY_STEP);
      break;
      case ICON_PAUSE:
        do_command(player, CMD_REPLAY_TOGGLE);
      break;
      case ICON_STOP:
        do_command(player, CMD_REPLAY_STOP);
      break;

      case ICON_REW:
        do_command(player, (m->button == 4) ? CMD_REPLAY_REWIND : CMD_REPLAY_FORWARD);
      break;
      case ICON_FF:
        do_command(player, (m->button == 4) ? CMD_REPLAY_FORWARD : CMD_REPLAY_REWIND);
      break;

      case ICON_LINE:
      case ICON_LINE_BACK:
        if (player_getPlayStatus(player) != play_status_stop)
        {
          int blk[10], win_x_min;
          win_x_min = win.vis.x0;
          blk[0] = win_handle.control;
          blk[1] = ICON_LINE_BACK;
          _swix(Wimp_GetIconState, _IN(1), blk);
          player_seek(player, m->pos.x - win_x_min - blk[2], blk[4] - blk[2], 0);
        }
      break;

      case ICON_AUDIO:
        do_command(player, CMD_AUDIO_ON_OFF);
      break;

      case ICON_PREV:
        do_command(player, (m->button == 4) ? CMD_REPLAY_PREVIOUS : CMD_REPLAY_RND_PREV);
      break;
      case ICON_NEXT:
        do_command(player, (m->button == 4) ? CMD_REPLAY_NEXT : CMD_REPLAY_RND_NEXT);
      break;

      case ICON_PLAYLIST:
        menu_open(player, MENU_PLAYLIST, m->pos.x, m->pos.y);
      break;

      case ICON_CLOSE:
        config.control &= ~cfg_ctrl_controls;
        open_controls(player, NULL, PANEL_CLOSE);
      break;
    }
  }
}

/*
 * do_command
 * ----------
 * This does most of the actual commands and is called from both
 * single and multitasking modes with a command keycode.
 * returns non zero if key has been actioned.
 */
int do_command(player_t* player, int cmd)
{
  video_mode* vmode = &config.video.modes[player->mode];

  if (!(osd_getState(player) & OSD_STATE_NAV))
  {
    // commands which may be handled differently in playlist and outside playlists (like DVD)
    switch (cmd)
    {
      case CMD_REPLAY_REWIND:
        cmd = 0x18c; // left arrow
      break;
      case CMD_REPLAY_FORWARD:
        cmd = 0x18d; // right arrow
      break;
      case CMD_REPLAY_PREVIOUS:
      {
        if (playlist_play(player, (config.control & cfg_ctrl_random) ? PL_RANDOM : PL_PREVIOUS))
          return 1;
        // handle as ctrl left arrow
        cmd = 0x1ac;
      }
      break;
      case CMD_REPLAY_RND_PREV:
      {
        if (playlist_play(player, (config.control & cfg_ctrl_random) ? PL_PREVIOUS : PL_RANDOM))
          return 1;
        // handle as ctrl right arrow
        cmd = 0x1ad;
      }
      break;
      case CMD_REPLAY_NEXT:
      {
        if (playlist_play(player, (config.control & cfg_ctrl_random) ? PL_RANDOM : PL_NEXT))
          return 1;
        // handle as ctrl right arrow
        cmd = 0x1ad;
      }
      break;
      case CMD_REPLAY_RND_NEXT:
      {
        if (playlist_play(player, (config.control & cfg_ctrl_random) ? PL_NEXT : PL_RANDOM))
          return 1;
        // handle as ctrl left arrow
        cmd = 0x1ac;
      }
      break;
    }

    int ret = (player_getPlayStatus(player) == play_status_play)
            ? ka_stream_navigate(player->stream, cmd)
            : 0;

    if (ret & ka_nav_redraw_frame)
      player_redrawLastFrame(player);
    if (ret & ka_nav_audio_change)
    {
      ka_stream_selectAudioStream(player->stream, -1);
    }
    if (ret & ka_nav_subtitle_change)
    {
      ka_stream_selectSubtitleStream(player->stream, -1);
    }
    if (ret & ka_nav_angle_change)
    {
      ka_demux_angle_info_t info;
      ka_stream_getAngleInfo(player->stream, &info);
      setAngle(player, info.angleId);
    }
    if (ret & ka_nav_discontinuity)
    {
      player_restartPlay(player, player_status_seek);
    }

    if (ret)
      return 1;
  }

  switch (cmd)
  {
    case CMD_REPLAY_STOP:
    case -1:
      player_setPlayStatus(player, play_status_stop);
    break;
    case CMD_REPLAY_STEP:
    case ' ': // Advance 1 frame while paused
      if (player_getPlayStatus(player) == play_status_pause)
        player_setPlayStatus(player, play_status_advance);
    break;
    case CMD_REPLAY_REWIND:
      player_seek(player, -5, 100, 1);
    break;
    case CMD_REPLAY_FORWARD:
      player_seek(player, 5, 100, 1);
    break;

    // else follow through to 'Q'
    case 'Q':
    case 'q':
      // Exit
      return -1;
    break;
    case 'T':
    case 't':
    {
      // toggle time code display
      if (osd_getState(player) & OSD_STATE_TC)
        osd_navigate(player, OSD_NAV_TIME_OFF);
      else
        osd_navigate(player, OSD_NAV_TIME_ON);
    }
    break;
    case CMD_SCREEN_DBL_CLICK: // full-screen/desktop toggle + pause toggle
      player_toggleFullScreen(player);
    // no break;
    case CMD_SCREEN_CLICK:
    case CMD_REPLAY_TOGGLE:
    case 13: // Return, toggle Pause
    {
      int b = (player_getPlayStatus(player) == play_status_play);
      player_setPlayStatus(player, b ? play_status_pause : play_status_play);
    }
    break;

    case 0x1ac: // ctrl left arrow, previous film in playlist
      playlist_play(player, (config.control & cfg_ctrl_random) ? PL_RANDOM : PL_PREVIOUS);
    break;
    case 0x1ad: // ctrl right arrow, next film in playlist
      playlist_play(player, (config.control & cfg_ctrl_random) ? PL_RANDOM : PL_NEXT);
    break;

    case 0x1bc: // ctrl+shift left arrow, previous film in playlist
      playlist_play(player, (config.control & cfg_ctrl_random) ? PL_PREVIOUS : PL_RANDOM);
    break;
    case 0x1bd: // ctrl+shift right arrow, next film in playlist
      playlist_play(player, (config.control & cfg_ctrl_random) ? PL_NEXT : PL_RANDOM);
    break;

    case CMD_OSD_VALUE_DOWN:
    case 0x18c: // left arrow
      if (osd_getState(player) & OSD_STATE_NAV)
        osd_navigate(player, OSD_NAV_LEFT);
      else
        player_seek(player, -5, 100, 1); // Rewind in % of film length
    break;
    case CMD_OSD_VALUE_UP:
    case 0x18d: // right arrow
      if (osd_getState(player) & OSD_STATE_NAV)
        osd_navigate(player, OSD_NAV_RIGHT);
      else
        player_seek(player, 5, 100, 1); // Fast forward in % of film length
    break;
    case CMD_VOLUME_DOWN:
    case 0x18e: // down arrow
      if (osd_getState(player) & OSD_STATE_NAV)
        osd_navigate(player, OSD_NAV_DOWN);
      else
        update_volume(player, -1);
    break;
    case CMD_VOLUME_UP:
    case 0x18f: // up arrow
      if (osd_getState(player) & OSD_STATE_NAV)
        osd_navigate(player, OSD_NAV_UP);
      else
        update_volume(player, +1);
    break;

    case '+':   // Increase volume
      update_volume(player, +1);
    break;
    case '-':   // Decrease volume
      update_volume(player, -1);
    break;

    case 'S':
    case 's':   // save frame as sprite
      if (player_getPlayStatus(player) == play_status_pause)
      {
        char filename[64];

        snprintf
          ( filename, sizeof(filename)
          , "<KinoSave$Dir>.frame%05d_%1d"
          , player->draw.picturenr, player->draw.fieldnr
          );
        save_frame(player, filename);
        osd_report(player, "msg_savesprite:%s", strrchr(filename, '.') + 1);
      }
    break;

    // numeric keys 1..9 : audio channel select
    case '1':
    case '2':
    case '3':
    case '4':
    case '5':
    case '6':
    case '7':
    case '8':
    case '9':
    {
      if (player->stream)
      {
        config.audio.stream = cmd - '0';
        setAudioStream(player, config.audio.stream);
      }
    }
    break;

    case CMD_ZOOM_IN:
    case 'Z':
    case 'z': // Increase zoom
      set_zoom(player, 0, +1);
    break;

    case CMD_ZOOM_OUT:
    case 'X':
    case 'x': // Decrease zoom
      set_zoom(player, 0, -1);
    break;

    case 0x1A: // Ctrl+Z, Increase zoom
      set_zoom(player, 0, +50);
    break;

    case 0x18: // Ctrl+X, Decrease zoom
      set_zoom(player, 0, -50);
    break;

    case CMD_REPLAY_RESTART:
    case 'R':
    case 'r': // Restart film
    {
      player_restartPlay(player, player_status_restart);
    }
    break;

    case CMD_LOOP_TOGGLE:
    case 'L':
    case 'l': // Loop continuously
      config.control ^= cfg_ctrl_loop;
      osd_report(player, "msg_loop:%s", (config.control & cfg_ctrl_loop) ? "on" : "off");
    break;

    case CMD_AUTOEXIT_TOGGLE:
    case 'A':
    case 'a': // Auto exit
      config.control ^= cfg_ctrl_autoexit;
      osd_report(player, "msg_autoexit:%s", (config.control & cfg_ctrl_autoexit) ? "on" : "off");
    break;

    case CMD_AUDIO_ON_OFF:
    case 'M':
    case 'm': // Mute-Unmute Audio
      if(config.audio.cfg & cfg_audio_play)
      {
        config.audio.cfg ^= cfg_audio_mute;
        if (player->stream && player->stream->audio.astream)
        {
          audio_volume(player->stream->audio.astream, (config.audio.cfg & cfg_audio_mute) ? -2: -3);
          osd_report(player, "msg_audio:%s", (config.audio.cfg & cfg_audio_mute) ? "off" : "on");
        }
      }
    break;

    case CMD_SCREEN_TOGGLE:
    case 'F':
    case 'f': // full-screen/desktop toggle
      player_toggleFullScreen(player);
      update_controls(player);
    break;

    case CMD_SCREEN_MULTITASK:
    case 27:  // Esc, Exit
    case 0x185: // F5, multitasking mode
      if (player->mode != KA_MODE_MULTITASK)
      {
        player_adaptDisplay(player, KA_MODE_MULTITASK, SCREEN_CHANGE);
        update_controls(player);
      }
    break;

    case CMD_SCREEN_DESKTOP:
    case 0x186: // F6, deskop mode
      player_adaptDisplay(player, KA_MODE_DESKTOP, SCREEN_CHANGE);
    break;

    case CMD_SCREEN_AUTO:
    case 0x187: // F7, automatic mode
      player_adaptDisplay(player, KA_MODE_AUTO, SCREEN_CHANGE);
    break;

    case CMD_SCREEN_MANUAL:
    case 0x188: // F8, manual mode
      player_adaptDisplay(player, KA_MODE_MANUAL, SCREEN_CHANGE);
    break;

    case 0x183: // F3, save choices
      choices_save(player);
      osd_report(player, "msg_savechoices");
    break;

    case 0x18A: // Tab, cycle on videos/programs
    {
      uint32_t old_video = player->stream->video.channel;
      config.video.stream = ka_stream_selectVideoStream(player->stream, old_video + 1);
      if (config.video.stream <= old_video)
      {
        config.program = ka_stream_selectProgram(player->stream, player->stream->program + 1
                                   , 0, config.audio.stream, config.subtitle.stream);
        config.video.stream = player->stream->video.channel;
        config.audio.stream = player->stream->audio.channel;
        config.subtitle.stream = player->stream->subtitle.channel;
      }
      player_restartPlay(player, player_status_seek);
    }
    break;

    case 0x19A: // Shift+Tab, cycle on audios
    {
      if (player->stream)
      {
        ka_demux_audio_info_t info;

        ka_stream_getAudioInfo(player->stream, -1, &info);

        if (info.streamId < info.nrStreams)
          config.audio.stream = info.streamId + 1;
        else
          config.audio.stream = 1; // First audio channel

        setAudioStream(player, config.audio.stream);
      }
    }
    break;
    case 0x1AA: // Ctrl+Tab, cycle on subtitles
    {
      if (player->stream)
      {
        ka_demux_subtitle_info_t info;

        ka_stream_getSubtitleInfo(player->stream, -3, &info);

        if (info.streamId < info.nrStreams)
          config.subtitle.stream = info.streamId + 1;
        else
          config.subtitle.stream = 0; // Off

        config.subtitle.stream = ka_stream_selectSubtitleStream(player->stream, config.subtitle.stream);

        ka_stream_getSubtitleInfo(player->stream, -3, &info);

        if (player->stream->subtitle.channel)
        {
          osd_report
            ( player
            , "msg_streamsubtitle:%d %s %s"
            , player->stream->subtitle.channel, info.type, info.lang
            );
        }
        else
          osd_report(player, "msg_streamsubtitlenone");
      }
    }
    break;
    case 0x1BA: // Ctrl+Shit+Tab, cycle on angles
    {
      if (player->stream)
      {
        ka_demux_angle_info_t info;
        ka_stream_getAngleInfo(player->stream, &info);
        info.angleId++;
        if (info.angleId > info.nrAngles)
          info.angleId = 1;
        setAngle(player, info.angleId);
      }
    }
    break;

    case CMD_COLOR_TOGGLE:
    case 'C':
    case 'c': // Colour/Monochrome toggle
    {
      vmode->cfg ^= cfg_video_mono;
      if (player->stream)
        ka_stream_setMonochromeDecode(player->stream, (vmode->cfg & cfg_video_mono) != 0);
      player_adaptDisplay(player, player->mode, MODE_CHANGE);
    }
    break;

    case 'D':
    case 'd': // Deinterlacing toggle
    {
      if (keyboard_pollShift())
      {
        config.video.deinterlace--;
        if (config.video.deinterlace < 0)
          config.video.deinterlace = KA_DEINTERLACE_MAX -1;
      }
      else
      {
        config.video.deinterlace++;
        if (config.video.deinterlace >= KA_DEINTERLACE_MAX)
          config.video.deinterlace = 0;
      }

      osd_report(player, "msg_deinterlace:%s", menu_getdeinterlace());
    }
    break;

    case 'P':
    case 'p':  // resizemode toggle
    case 0x10: // Ctrl+P
    case 0x110: // Ctrl+Shift+P, resizeto toggle
    {
      int x, y;
      char* s;

      if ((cmd == 0x10) || (cmd == 0x110))
      {
        if (keyboard_pollShift())
        {
          config.video.resizeto--;
          if (config.video.resizeto < 0)
            config.video.resizeto = KA_RESIZETO_MAX - 1;
        }
        else
        {
          config.video.resizeto++;
          if (config.video.resizeto >= KA_RESIZETO_MAX)
            config.video.resizeto = 0;
        }
      }
      else
      {
        if (keyboard_pollShift())
        {
          config.video.resizemode--;
          if (config.video.resizemode < 0)
            config.video.resizemode = KA_RESIZE_MAX - 1;
        }
        else
        {
          config.video.resizemode++;
          if (config.video.resizemode >= KA_RESIZE_MAX)
            config.video.resizemode = 0;
        }
      }

      if ((cmd != 0x10) || (config.video.resizemode != KA_RESIZE_NONE))
        player_adaptDisplay(player, player->mode, REDIM_CHANGE);

      config_aspect_ratio(&config, &x, &y);

      switch(config.video.resizemode)
      {
        case KA_RESIZE_PANSCAN:
          if (config.video.resizeto == KA_RESIZETO_MONITOR)
            s = "msg_panscanm:%d %d";
          else
            s = "msg_panscanx:%d %d";
        break;
        case KA_RESIZE_STRETCH:
          if (config.video.resizeto == KA_RESIZETO_MONITOR)
            s = "msg_stretchm:%d %d";
          else
            s = "msg_stretchx:%d %d";
        break;
        default:
          s = "msg_source";

        break;
      }

      osd_report(player, s, x, y);
    }
    break;

    case 1: // Ctrl + A, start/stop audio saving
    {
      audiosaver_t* saver = (player->stream) ? player->stream->audio.saver : NULL;
      if (saver)
      {
        if (audiosaver_issaving(saver))
        {
          if (audiosaver_closeoutfile(saver) >= 0)
          {
            osd_report(player, "audiosaveoff");
          }
        }
        else
        {
          audio_t* astream = (player->stream) ? player->stream->audio.astream : NULL;
          if (astream)
          {
            player->stream->audio.savertyp = player->stream->audio.typ;
            if (audiosaver_openoutfile(saver, &player->error_block, audio_getParams(astream)) >= 0)
            {
              osd_report(player, "audiosaveon");
            }
          }
        }
      }
    }
    break;

    default: return 0; // unwanted key
  }
  return 1;
}
