#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "kernel.h"
#include "swis.h"

#include "inttypes.h"
#include "audio.h"
#include "choices.h"
#include "config.h"
#include "display.h"
#include "ka_hardware.h"
#include "ka_log.h"
#include "keyboard.h"
#include "menu.h"
#include "msg.h"
#include "mt.h"
#include "osd.h"
#include "playlist.h"
#include "sprite.h"
#include "timer1.h"
#include "player.h"
#include "wimp.h"
#include "ka_mem.h"

ka_config_t config;
uint32_t fn_time[10];
static int blanking_delay = 0;
static player_t* cplayer = NULL; // main structure
//static uint64_t neon_buffer[32];
static void* neon_context = NULL;

/**
 * Ensures the presence of an RMA module and load it if necessary.
 *
 * @param  name     Module name
 * @param  vers     minimal version * 100.
 * @param  path     Full filename of the module.
 *
 * @return Non zero on error.
 */
int rm_ensure(ka_error_t* error_block, const char* name, int vers, const char* path)
{
  int found_vers = rm_version(name);

  if (found_vers >= vers)
    return 0;

  // load, then module in RMA
  if (!_swix(OS_Module, _INR(0,1), 1, path))
  {
    found_vers = rm_version(name);

    if (found_vers >= vers)
      return 0;
  }

  ka_error_fill(error_block, "error29:%s %d.%02d", name, vers/100, vers%100);

  return -1;
}

/**
 * Parses the command line arguments.
 *
 * @param  argc  Number of arguments.
 * @param  argv  Array of arguments, argv[0] been the program itself.
 */
static void handle_args(player_t* player, int argc, char * argv[])
{
  int i = 0, check = 1, zoom = 0, error = 0;
  char *p;
  int timer = TIMER_MOD;
  int video_cfg = cfg_video_dither;
  int video_cfg_mask = 0;

  // setup a few defaults
  choices_init();

  if (argc == 1) // no options or file entered
    report_error(1, "error0"); // (click on mpeg file)

  while (++i<argc && !error)
  {
    p = argv[i];
    if (*p++ == '-') // option
    {
      switch (tolower(*p++))
      {
        case 'p': // (p)laylist
        {
          if (playlist_open(p))
            playlist_play(player, (config.control & cfg_ctrl_random) ? PL_RANDOM : 0);
          else
            error = 1;
        }
        break;
        case 'f': // set options from choices (f)ile
          choices_read(p);
        break;
        case 's': // force (s)tream type
        {
          check = 0;
          if (tolower(*p) == 'e') // elementary video stream
            config.video.demux_ps = 0;
          else if (tolower(*p) == 'p') // program stream
            config.video.demux_ps = 1;
          else
            error = 1;
        }
        break;
        case 'a': // save soundtrack as an (A)MPEG file
          config.audio.cfg |= cfg_audio_save;
        break;
        case 'v': // (v)ideo only, no soundtrack
          config.audio.cfg &= ~cfg_audio_play;
        break;
        case 'x': // define screen mode to use
        {
          video_mode* vmode;

          if (tolower(*p) == 'a')
          { // full screen auto mode select (preserve mono flag)
            config.video.mode = KA_MODE_AUTO;
            vmode = &config.video.modes[KA_MODE_AUTO];
          }
          else
          {
            config.video.mode = KA_MODE_MANUAL;
            vmode = &config.video.modes[KA_MODE_MANUAL];

            vmode->width = atoi(p); // width

            while (isdigit(*p)) p++;
            if (tolower(*p++) != 'y')
            {
              error = 1;
              break;
            }

            vmode->height = atoi(p); // height

            while (isdigit(*p)) p++;
            if (tolower(*p++) != 'c')
            {
              error = 1;
              break;
            }
          }

          // check for a colour limit
          if (strcmp(++p, "256") == 0)
            vmode->cols = KA_COL_256;
          else if ((strcmp(p, "4k") == 0)||(strcmp(p, "4K") == 0))
            vmode->cols = KA_COL_4K;
          else if ((strcmp(p, "32k") == 0)||(strcmp(p, "32K") == 0))
            vmode->cols = KA_COL_32K;
          else if ((strcmp(p, "64k") == 0)||(strcmp(p, "64K") == 0))
            vmode->cols = KA_COL_64K;
          else if ((strcmp(p, "16m") == 0)||(strcmp(p, "16M") == 0))
            vmode->cols = KA_COL_16M;
          else if (config.video.mode != KA_MODE_AUTO)
            error = 1;
        }
        break;
        case 'c': // audio (c)hannel
        {
          if ((config.audio.stream = atoi(p)) > 31)
            error = 1;
          if (config.audio.stream < 1)
            config.audio.stream = 1;
        }
        break;
        case 'd': // (d)ebug level
        {
          int i;
          sscanf(p, "%i", &i);
          config.debug |= i;
        }
        break;
        case 'z': // (Z)oom
          switch (*p)
          {
            case '4': zoom = 400; break;
            case '3': zoom = 300; break;
            case '1': zoom = 100; break;
            case '0': zoom = 50;  break;
            default : zoom = 200;
          }
        break;
        case '@': // Set position (desktop only)
          if (sscanf(p, "%d,%d", &config.video.position_x, &config.video.position_y) != 2)
            config.video.position_x = config.video.position_y = -1;
        break;
        case 'b': // Set (b)rightness/colour/contrast
          if (sscanf(p, "%d,%d,%d", &config.video.brightness, &config.video.contrast, &config.video.colour) != 3)
            config.video.brightness = config.video.contrast = config.video.colour = 100;
        break;
        case 'r': // (r)esync audio and video
          config.audio.cfg |= cfg_audio_sync;
        break;
        case 'm': // (m)onochrome
          video_cfg |= cfg_video_mono;
          video_cfg_mask |= cfg_video_mono;
        break;
        case 'w': // (w)imp - multitask
          config.video.mode = KA_MODE_MULTITASK;
        break;
        case 'l': // (l)ock ...
          switch (tolower(*p))
          {
            case 'a': // lock (a)spect ratio
              config.video.cfg |= cfg_video_lock_aspect;
            break;
            case 's': // lock (s)ize to window
              video_cfg |= cfg_video_fit_screen;
              video_cfg_mask |= cfg_video_fit_screen;
            break;
            default: error = 1;
          }
        break;
        case '-': // long option
          if (strcmp(argv[i], "--nodither") == 0)
          {
            video_cfg &= ~cfg_video_dither;
            video_cfg_mask |= cfg_video_dither;
          }
          else if (strcmp(argv[i], "--loop") == 0)
            config.control |= cfg_ctrl_loop;
          else if (strncmp(argv[i], "--skin,", 7) == 0)
            snprintf(config.skin, sizeof(config.skin), "%s", argv[i] + 7);
          else if (strcmp(argv[i], "--ctrl") == 0)
            config.control |= cfg_ctrl_controls;
          else if (strncmp(argv[i], "--vol", 5) == 0)
            config.audio.volume = atoi(p+3);
          else if (strcmp(argv[i], "--noexit") == 0)
            config.control &= ~cfg_ctrl_autoexit;
          else if (strcmp(argv[i], "--noscroll") == 0)
            config.control &= ~cfg_ctrl_scroll_bars;
          else if (strcmp(argv[i], "--ostime") == 0)
            timer = TIMER_OS;
          else if (strcmp(argv[i], "--paused") == 0)
            config.control |= cfg_ctrl_start_paused;
          else if (strcmp(argv[i], "--noerrors") == 0)
            config.control |= cfg_ctrl_hide_errors;
          else
            error = 1;
        break;
        default: error = 1;
      }
    }
    else // mpeg filename
      file_dropped(player, 0xbf8, argv[i]);
  }

  timer_selecthardware(timer);

  if (error)
    report_error(1, "error1:%s", argv[i-1]); // (invalid argument)

  if (!check) config.control |= cfg_ctrl_nocheck;

  if (zoom)
    config.video.modes[config.video.mode].zoom = zoom;

  video_cfg &= video_cfg_mask;
  for (i = 0; i < KA_MODE_MAX; i++)
  {
    config.video.modes[i].cfg &= ~video_cfg_mask;
    config.video.modes[i].cfg |= video_cfg;
  }

  choices_validate();
}

/**
 * Exit callback releasing all resources even after a crash.
 */
static void finishall(void)
{
  // Delete context for NEON
  if (neon_context != NULL)
  {
    // VFPSupport_DestroyContext
    _swix(0x58EC2, _INR(0,1), &neon_context, 0);
    neon_context = NULL;
  }

  // restore screen blanker
  _swix(ScreenBlanker_Control, _INR(0,1), 3, blanking_delay *100);

  if (cplayer != NULL)
  {
    int mode = cplayer->mode;

    delete_player(&cplayer);

    // if outside desktop, prevent "Press space"
    if ((mode == KA_MODE_AUTO)
    ||  (mode == KA_MODE_MANUAL))
    {
      _swix(Wimp_CommandWindow, _IN(0), -1);
    }
  }
  keyboard_final();
  timer_uninstall();
  mt_done();
  playlist_close();
  msg_close();
}

static const char* resync_cause[4] =
{ NULL
, "tasks"
, "audio"
, "video"
};

/**
 * Checks timings and perform resync is necessary.
 */
static void ensureAudioVideoSync(player_t* player)
{
  ka_stream_t* stream = player->stream;
  uint32_t cur_rtc = timer_getrtc();

  if (player->status_flags & player_status_readyforplay)
  {
    int resync = 0;
    uint32_t new_rtc = cur_rtc;
    uint32_t audio_pts = 0;
    uint32_t video_pts = 0;
    int32_t sync_error = cur_rtc - player->lastrtc;

    if ((config.debug & cfg_displayall) == 0)
    {
      // Seriously delayed by other tasks?
      if (sync_error > (int32_t) timer_getrate())
      {
        resync = 1;
        new_rtc = player->lastrtc;
      }

      if ((player->drawn_count > 0)
      &&  ((stream->state & ka_state_waittime) == 0)
      &&  ((stream->stack->video.status & ka_fifo_status_codecdone) == 0))
      {
        video_pts = player->draw.pts;
        sync_error = new_rtc - player->draw.pts;

        if (abs(sync_error) > timer_getcs(50))
        {
          resync = 3;
          new_rtc = player->draw.pts;
        }
      }

      // Check if rtc gets out of sync with audio?
      if ((stream->audio.astream != NULL)
      &&  ((stream->stack->audio.status & ka_fifo_status_codecdone) == 0))
      {
        const audio_info_t* ainfo = audio_info(stream->audio.astream);

        if (ainfo->status & AUDIO_STATUS_REPLAYOUTPUT)
        {
          audio_checkState(stream->audio.astream);

          // Audio pts is only provided if something is playing. So it may be absent because:
          // - there is no (decodable) audio
          // - there is a gap in the stream (corruption ?)
          // - we got delayed by nother application and all available output has been played
          // Large discrepencies between audio and video may occur due to:
          // - corruption
          // - after a large delay video resumes while audio is yet to be decoded
          if ((ainfo->pts) && (!player->drawn_count || (player->draw.section == ainfo->section)))
          {
            // Resync to audio if possible
            audio_pts = ainfo->pts;
            sync_error = new_rtc - ainfo->pts;

            if ((ainfo->cfg & cfg_audio_sync)
            &&  (abs(sync_error) > timer_getcs(12))) // allow +- 0.12 sec tolerance
            {
              if (((stream->stack->video.status & ka_fifo_status_codecdone) != 0) // no (more) video
              ||  (abs(sync_error) < 5*timer_getrate())) // else assume corrupted
              {
                resync = 2;
                new_rtc = ainfo->pts;
              }
            }
          }
        }
      }

      if (resync)
      {
        if (abs(new_rtc - cur_rtc) > timer_getcs(3)) // resync audio may revert video resync
        {
          // Ensure audio can start even in case of big pts corruption
          if ((resync == 3) & (new_rtc > stream->time.synced_start))
            stream->time.synced_start = new_rtc;

          if (config.debug & (cfg_printwarnings | cfg_printreplaystats))
          {
            ka_log( ka_log_warning | ka_log_replay
                  , "Resync, new rtc %10u, cur rtc %10u, delta %d (%s), video: %10u, audio: %10u"
                  , new_rtc, cur_rtc, new_rtc - cur_rtc, resync_cause[resync], video_pts, audio_pts);
          }

          timer_setrtc(new_rtc);
          cur_rtc = new_rtc;
        }
        else
        {
          if ((resync == 2)
          &&  (config.debug & cfg_printreplaystats))
          {
            ka_log( ka_log_note | ka_log_replay
                  , "Audio Sync cur rtc %10u, video: %10u, audio: %10u"
                  , cur_rtc, video_pts, audio_pts);
          }
        }
      }
    }

    if (stream->audio.astream != NULL)
      audio_checkStart(stream->audio.astream, cur_rtc, stream->displaySection);
  }

  player->lastrtc = cur_rtc;
}

/**
 * Opens/restarts/seeks and attempt to decode one video frame.
 *
 * @param  player  Player instance.
 *
 * @return Negative in case of error, 0 if stream completed, positive otherwise.
 */
static int decode(player_t* player)
{
  // Special processings
  if (player->status_flags & player_status_act)
  {
    // Start a new film?
    if (player->status_flags & player_status_new)
    { // play new film
      ka_delete_stream(&player->stream);
      player->stream = ka_new_stream( &player->error_block
                                    , playlist_getcurrentfilm()
                                    , player->hardware
                                    , config.program
                                    , config.video.stream
                                    , (config.audio.cfg & cfg_audio_autoselect) && !player->stream
                                    ? 1 : config.audio.stream
                                    , (config.subtitle.cfg & cfg_subtitles_autoselect) && !player->stream
                                    ? 1 : config.subtitle.stream);
      if (!player->stream)
      {
        report_error_block(&player->error_block);
        return -1;
      }

      player->status_flags &= ~player_status_new;
      player->status_flags |= player_status_restart;

      config.control &= ~cfg_ctrl_nocheck;
    }

    ka_stream_t* stream = player->stream;

    // Restart film?
    if (player->status_flags & player_status_restart)
    {
      player->status_flags &= ~(player_status_restart | player_status_readyforplay);


      player_reset(player);     // reset video decoder
      ka_stream_reset(stream);
    }

    // Restart after seek?
    if (player->status_flags & player_status_seek)
    {
      player_clear(player);     // reset video decoder
      player->status_flags &= ~(player_status_seek | player_status_readyforplay);
    }
  }

  // Decode
  {
    ka_stream_t* stream = player->stream;
    ka_stream_event event = ka_stream_event_wait;
    uint32_t start_time = timer_gettime();

    ensureAudioVideoSync(player);

    do
    {
      // non-blocking reporting
      if (stream->msg[0])
      {
        osd_report(player, NULL);
        stream->msg[0] = 0;
      }

      // set time processing to a limited time or to next frame if we not there yet
      uint32_t exit_time = timer_gettime() + timer_getcs(2);

      if (player->draw.timetonext
      &&  ((int32_t)(player->draw.timetonext - exit_time) > 0))
        exit_time = player->draw.timetonext;

      // reset start of wait period ?
      if (event != ka_stream_event_wait)
        player->time.wait_last = timer_gettime();

      // process
      event = ka_stream_process(stream, exit_time);

      if ((player->status_flags & player_status_readyforplay)
      &&  (stream->audio.astream != NULL))
        audio_checkStart(stream->audio.astream, timer_getrtc(), stream->displaySection);

      // perform video special action
      switch(event)
      {
        case ka_stream_event_inerror:
        {
          return -1;
        }
        break;
        case ka_stream_event_buffering:
          player->time.wait_last = timer_gettime();
        // no break;
        case ka_stream_event_wait:
        {
          uint32_t t = timer_gettime();

          // multitask (important for audio only or corrupted stream)
          if ((int32_t)(t - start_time) > (int32_t) timer_getcs(10))
          {
            osd_refresh(player);

            stream->time.end = t;

            return 1;
          }
        }
        break;
        case ka_stream_event_ready_for_play:
        {
          // Display setup even if audio only
          player_setupDisplay(player);

          // Start timer
          timer_setrtc(player->lastrtc = ka_stream_getRTCStart(stream));

          if (config.debug & (cfg_printreplaystats | cfg_printaudioall | cfg_printvideoall))
            ka_log(ka_log_demux, "Replay Started, set rtc to %10u", player->lastrtc);

          // Audio only, start replay now
          if (stream->audio.astream != NULL)
            audio_checkStart(stream->audio.astream, player->lastrtc, stream->displaySection);

          player->status_flags |= player_status_readyforplay;
          player->time.wait_last = timer_gettime();
        }
        break;
        case ka_stream_event_decode_frame:
        {
          player_onNewFrameStarted(player);
          continue;
        }
        break;
      }

      if (player->status_flags & player_status_readyforplay)
      {
        // time waiting so far to display frame
        int32_t wait = timer_gettime() - player->time.wait_last;

        // we may have a frame waiting to be displayed
        ka_vframe_t* frame = ka_stream_getFrame(player->stream, player->draw.picturenr);

        // section change ?
        event = ka_stream_checkSectionChange(player->stream, event, frame);

        if (stream->state & ka_state_waittime)
        {
          // Multitask
          return 1;
        }
        else if (frame != NULL)
        {
          // Frame to draw
          play_draw rc = player_onFrameToDraw(player, frame);

          switch(rc)
          {
            case play_draw_drawn:
            {
              stream->time.end = timer_gettime();

              if (player_getPlayStatus(player) == play_status_advance)
                player_setPlayStatus(player, play_status_pause);

              // drawn, we can finally add wait time
              if (wait > 0)
                player->cputime.wait += wait;
              player->time.wait_last = timer_gettime();
              // multi-task
              return 1;
            }
            break;
            case play_draw_early:
            {
              // reset possible end event
              event = ka_stream_event_buffering;
            }
            break;
            case play_draw_early_suspicious:
            {
              stream->time.end = timer_gettime();

              // sync issue, multi-task
              return 1;
            }
           break;
          }
        }
      }
    }
    while (event < ka_stream_event_end);

    stream->time.end = timer_gettime();

    if (event == ka_stream_event_discontinuity)
    {
      // Restart processing
      if (ka_stream_restart(stream))
          return -1;
      player_restartPlay(player, player_status_seek);
      return 1;
    }

  }

  return 0;
}

/**
 * Decodes a video frame, handle film/list loops.
 *
 * @param  player  Player instance.
 *
 * @return Negative in case of error, 0 if stream completed, positive otherwise.
 */
static int null_event(player_t* player)
{
  int state = 0;

  switch (player_getPlayStatus(player))
  {
    case play_status_pause:
    case play_status_stop:
    {
      return 1;
    }
    break;
    case play_status_play:
    case play_status_advance:
    {
      state = decode(player);

      // Allow menus to refresh
      if (player->stream && (player_getPlayStatus(player) == play_status_play))
      {
        int ret = ka_stream_navigate(player->stream, 0);
        if (ret & ka_nav_redraw_frame)
          player->status_flags |= player_status_redrawframe;
      }

      if (player->status_flags & player_status_redrawframe)
      {
        player_redrawLastFrame(player);
        player->time.wait_last = timer_gettime();
      }

      // If still some work, return
      if (state > 0)
        return state;

      // Decoding complete
    }
  }

  // Decoding complete

  player_setPlayStatus(player, play_status_stop);

  ka_logReset();

  // If error, reset player (so no overlays), toggle to multitasking and show error
  if ((state < 0)
  &&  (!(config.control & cfg_ctrl_hide_errors)))
  {
    player_reset(player);
    display_restoreDesktop(player, KA_MODE_MULTITASK, SCREEN_CHANGE);
    player->mode = KA_MODE_MULTITASK;
    display_setup(player, &player->draw.vinfo, SCREEN_CHANGE);
    report_lasterror();
  }

  // IF error and single film stop here
  if ((state < 0) && (playlist_num() <= 1))
    return 0; // exit

  // Attempt to play next film in list (only unplayed ones for random)
  if (playlist_play(player, (config.control & cfg_ctrl_random) ? PL_RANDOM : PL_NEXT))
    return 1;

  // Reset play status of films in playlist so we can repeat the list
  playlist_reset();

  if (config.control & cfg_ctrl_loop)
  {
    if (playlist_play(player, (config.control & cfg_ctrl_random) ? PL_RANDOM : 0))
      return 1;

    // Restart current film
    player_restartPlay(player, player_status_restart);

    return 1;
  }

  // No loop, stop if autoexit else wait command
  if (config.control & cfg_ctrl_autoexit)
      return 0; // exit

  player_setPlayStatus(player, play_status_stop);

  return 1;
}

static void do_wheel(player_t* player, int x, int y)
{
  uint32_t osdState = osd_getState(player);
  int var = x + y;
  if (var > 0) do_command(player, (osdState & OSD_STATE_NAV) ? CMD_OSD_VALUE_UP : CMD_VOLUME_UP);
  if (var < 0) do_command(player, (osdState & OSD_STATE_NAV) ? CMD_OSD_VALUE_DOWN : CMD_VOLUME_DOWN);
}

/*
 * main
 * ----
 */
int main(int argc,char *argv[])
{
  _kernel_swi_regs regs;
  int done = 0;
  char *task_name = "KinoAMP";
  int other_task_handle;
  int stream_loop;
  int b[256];
  int wheelx = 0, wheely = 0;
  uint32_t hardware = 0;

  srand(time(NULL));

  // check if already running
  {
    regs.r[0] = other_task_handle = 0;
    while((regs.r[0] >= 0) && !other_task_handle)
    {
      regs.r[1] = (int)b;
      regs.r[2] = sizeof(b);
      _kernel_swi(TaskManager_EnumerateTasks, &regs, &regs);
      if(strcmp((char *)b[1], task_name) == 0)
        other_task_handle = b[0];
    }
  }

  if (!msg_open("KinoIRes:Messages"))
    report_error(-1, "Cannot open Messages file");

  // LDRH/STRH support
  if (_swix(OS_PlatformFeatures, _IN(0)|_OUT(0), 0, &b[0]) == NULL)
  {
    if (b[0] & 0x80)
      hardware |= ka_hardware_ldrh;
  }

  // Check system register MVFR0 via VFPSupport_Features,0
  if (_swix(0x58EC8, _IN(0)|_OUT(1), 0, &b[0]) == NULL)
  {
    // bits 3:0, 0 no, 1 16x64-bit registers, 2 32x64-bit registers
    if ((b[0] & 0xf) > 1)
    {
      // Create context for NEON, VFPSupport_CreateContext
      _swix(0x58EC1, _INR(0,3)|_OUT(0), 1 + (1u<<31), 32, 0, 0, &neon_context);
      if (neon_context != NULL)
        hardware |= ka_hardware_neon;
    }
  }

  cplayer = new_player(hardware);
  if (!cplayer) exit(1);
  handle_args(cplayer, argc, argv);
  mt_init(task_name, config.skin);
  display_init(cplayer);

  if(other_task_handle && !(config.control & cfg_ctrl_multiple_wins))
  {
    // send film to existing instance
    regs.r[0] = 0;
    regs.r[1] = (int)b;
    _kernel_swi(Wimp_Poll, &regs, &regs);
    regs.r[0] = 17; // User message
    regs.r[1] = (int)b;
    regs.r[2] = other_task_handle;
    b[0] = sizeof(b);
    b[3] = 0;
    b[4] = MESSAGE_DATALOAD;
    b[10] = FILETYPE_MPEG;
    snprintf((char *)&b[11], sizeof(b)-44, "%s", playlist_getcurrentfilm());
    _kernel_swi(Wimp_SendMessage, &regs, &regs);
    exit(0);
  }

  // read screen blank time, then disable it
  regs.r[0] = 4;
  _kernel_swi(ScreenBlanker_Control, &regs, &regs);
  blanking_delay = regs.r[1];
  regs.r[0] = 3;
  regs.r[1] = 0;
  _kernel_swi(ScreenBlanker_Control, &regs, &regs);

  atexit(finishall);

  keyboard_init();
  update_controls(cplayer);
  read_menus();
  player_restartPlay(cplayer, player_status_new);

  // Init wheel mouse info for non-mutitask mode
  if (!_swix(OS_Module, _INR(0,1), 18, "WindowScroll"))
    _swix(OS_Pointer, _IN(0)|_OUTR(0, 1), 2, &wheelx, &wheely);

  do
  {
    stream_loop = 1;

    if (cplayer->status_flags & player_status_redrawframe)
      player_redrawLastFrame(cplayer);

    if (cplayer->mode == KA_MODE_MULTITASK)
    {
      int reason;

      // Multitasking
      regs.r[0] = 0;
      regs.r[1] = (int)b;
      _kernel_swi(Wimp_Poll, &regs, &regs);
      reason = regs.r[0];

      switch(reason)
      {
        case 0:
        {
          stream_loop = null_event(cplayer);

          if (stream_loop <= 0)
            break;

          update_playinfo(cplayer);

          wimp_mouse_t* m = (wimp_mouse_t*) b;
          mouse_get(cplayer, m);
          mouse_action(cplayer, m);
        }
        break;
        case 1: // redraw window
        {
          wimp_redraw_t* r = (wimp_redraw_t*) b;

          if (r->window == win_handle.player)
          {
            regs.r[1] = (int)r;
            _kernel_swi(Wimp_RedrawWindow, &regs, &regs);
            while (regs.r[0])
            {
              if(!done)
              {
                display_banner(r->vis.x0, r->vis.y0, r->vis.x1 - r->vis.x0, r->vis.y1 - r->vis.y0);
                done = 1;
              }
              player_plotFrame(cplayer, r);
              regs.r[1] = (int)r;
              _kernel_swi(Wimp_GetRectangle, &regs, &regs);
            }
          }
        }
        break;
        case 2: // open window
        {
          window_open_t* ws = (window_open_t*) b;

          if (ws->window == win_handle.player)
            mt_open(cplayer, ws);
          else if (ws->window == win_handle.control)
            open_controls(cplayer, ws, PANEL_DRAG);
          else
          {
            regs.r[1] = (int)ws;
            _kernel_swi(Wimp_OpenWindow, &regs, &regs);
          }
        }
        break;
        case 3: // close window request
        {
          if (b[0] == win_handle.player)
            stream_loop = 0; // if player window, quit
          else
          { // for any other window, just close it
            regs.r[1] = (int)b;
            _kernel_swi(Wimp_CloseWindow, &regs, &regs);
          }
        }
        break;
        case 6: // mouse click
        {
          wimp_mouse_t* m = (wimp_mouse_t*) b;

          mouse_action(cplayer, m);
        }
        break;
        case 7: // drag termination
        {
          drag_return();
        }
        break;
        case 8: // key press
        {
          switch(do_command(cplayer, b[6]))
          {
            case -1: stream_loop = 0; break;
            case 0:
            {
              // pass on unwanted keys
              regs.r[0] = b[6];
              _kernel_swi(Wimp_ProcessKey, &regs, &regs);
            }
            break;
            default:
            {
              update_controls(cplayer);
            }
          }
        }
        break;
        case 9: // menu selection
        {
          if (menu_select(cplayer, b) == -1)
            stream_loop = 0;
        }
        break;
        case 10: // window scroll
       {
          do_wheel(cplayer, (b[8] & 3) ? 0: (b[8] >> 2), (b[9] & 3) ? 0: (b[9] >> 2));
        }
        break;
        case 17:
        case 18:
        case 19: // message
        {
          switch (b[4])
          {
            case MESSAGE_QUIT:
            {
              stream_loop = 0;
            }
            break;
            case MESSAGE_DATALOAD:
            {
              file_dropped(cplayer, b[10], (char *)&b[11]);
            }
            break;
            case MESSAGE_PALETTECHANGE:
            case MESSAGE_MODECHANGE:
            {
              player_adaptDisplay(cplayer, cplayer->mode, MODE_CHANGE);
            }
            break;
            case MESSAGE_DATASAVEACK: // end of drag, save frame
            {
              save_frame(cplayer, (char *)&b[11]);
              savename_set((const char *)&b[11]); // update path in icon
            }
            break;
            case MESSAGE_HELPREQUEST:
            {
              msg_help(b);
            }
            break;
            case MESSAGE_MENUWARNING:
            {
              menu_warning(cplayer, b + 5); // skip msg header
            }
            break;
            case MESSAGE_MENUDELETED:
            {
              menu_deleted();
            }
            break;
          }
        }
      }
    }
    else
    {
      int newx = wheelx, newy = wheely;

      // Full screen
      stream_loop = null_event(cplayer);

      if (stream_loop <= 0)
        break;

      // We swich back to multitsking mode for error box
      // but the click on the error box will be detected as a key press if we continue
      if (cplayer->mode == KA_MODE_MULTITASK)
        continue;

      int key = keyboard_key();

      if (key)
      {
        if (do_command(cplayer, key) == -1)
          stream_loop = 0; // Exit
      }

      // Check mouse
      wimp_mouse_t* m = (wimp_mouse_t*) b;
      mouse_get(cplayer, m);
      mouse_action(cplayer, m);

      // Check wheel mouse
      if (!_swix(OS_Module, _INR(0,1), 18, "WindowScroll"))
      {
        _swix(OS_Pointer, _IN(0)|_OUTR(0, 1), 2, &newx, &newy);
        do_wheel(cplayer, newx - wheelx, newy - wheely);
      }
      wheelx = newx;
      wheely = newy;
    }
  }
  while(stream_loop > 0);

  player_setPlayStatus(cplayer, play_status_stop);

  return 0;
}
