#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include "assert.h"
#include "kernel.h"
#include "swis.h"
#include "inttypes.h"
#include "player.h"
#include "playlist.h"
#include "display.h"
#include "osd.h"
#include "audio.h"
#include "audiosaver.h"
#include "timer1.h"
#include "config.h"
#include "keyboard.h"
#include "mt.h"
#include "wimp.h"
#include "ka_hardware.h"
#include "ka_log.h"
#include "ka_mem.h"
#include "ka_codecs.h"
#include "colorspace/ka_colconv.h"
#include "scalers/ka_scaler.h"
#include "videooverlay.h"

typedef void (*FNmix_fast8)(uint8_t* dst, const uint8_t* src1, const uint8_t* src2, uint32_t width, uint32_t stride, uint32_t height);

extern void dup_fast8(const uint8_t* src, uint8_t* dst1, uint8_t* dst2, int width, int stride, int lines);

extern void mix_fast8_neon(uint8_t* dst, const uint8_t* src1, const uint8_t* src2, uint32_t width, uint32_t stride, uint32_t height);

/**
 * Creates a new player instance.
 *
 * @return New player instance.
 */
player_t* new_player(uint32_t hardware)
{
  player_t* player = ka_mem_calloc(sizeof(*player));

  player->idma = new_inteldma();
  player->play = play_status_stop;
  player->vo_mode_index = -1;
  player->hardware = hardware;
  osd_attach(player);

  return player;
}

/**
 * Destroy player instance.
 *
 * @param  player  Player instance.
 */
void delete_player(player_t** pplayer)
{
  player_t* player = *pplayer;
  *pplayer = NULL;

  if (!player)
    return;

  display_restoreDesktop(player, player->mode, FINAL_RESTORE);
  display_final(player);

  ka_delete_stream(&player->stream);
  delete_inteldma(&player->idma);
  osd_detach(player);

  ka_mem_free(player);
}

/**
 * Clear play parameters.
 *
 * @param  player  Player instance.
 */
void player_clear(player_t* player)
{
  player->prev_frame = NULL;
  player->curr_frame = NULL;
  player->frames_late = 0;
  player->I_pts = 0;
  player->P_pts = 0;
  player->B_pts = 0;
  player->I_delta = player->P_delta = player->B_delta = timer_getrate();
  player->I_skip = 0;
  player->F_valid = 0;
  player->B_valid = 0;
  player->draw.pts = 0;
  player->draw.fieldnr = 0;
  player->draw.picturenr = 0;
  player->draw.duration = 0;
  player->draw.timetonext = 0;

  player->frame_count = 0;
  player->drawn_count = 0;
  player->drawn_field_count = 0;
  player->I_count = 0;
  player->P_count = 0;
  player->B_count = 0;
  player->I_drawn = 0;
  player->P_drawn = 0;
  player->B_drawn = 0;

  player->time.fps_delta = 0;
  player->time.fps_last = 0;
  player->time.wait_last = 0;

  memset(&player->draw.vinfo, 0, sizeof(player->draw.vinfo));

  osd_reset(player);
}

/**
 * Resets play parameters.
 *
 * @param  player  Player instance.
 */
void player_reset(player_t* player)
{
  player_clear(player);

  player->cputime.draw = 0;
  player->cputime.scale = 0;
  player->cputime.wait = 0;
  player->cputime.sync = 0;
}

/**
 * Plots the required rectangle of the rgb frame buffer on the screen.
 * Scales x and y as required.
 *
 * @param  player  Player instance.
 * @param  b       Window redraw rectangle block.
 */
void player_plotFrame(player_t* player, const wimp_redraw_t* b)
{
  const ka_screen_t* screen = &player->screen_plot;

  if (player->vo_overlay)
  {
    _swix(VideoOverlay_RedrawWindow, _INR(0,1), player->vo_overlay, b);
    return;
  }

  switch (player->scaleconv.type)
  {
    case ka_scaler_system:
    {
      sprite_header_t* sp_area = player->rgb_area[SAVEAREA_PLOTTER].frame;
      int factors[4];

      factors[0] = player->pos.width;
      factors[1] = player->pos.height;
      factors[2] = player->plot.src_pix_width << screen->xeig;
      factors[3] = player->plot.src_height << screen->yeig;

      _swix(OS_SpriteOp, _INR(0,7)
           , 52 + 512
           , sp_area
           , ((uint32_t) sp_area + sp_area->offset)
           , b->vis.x0 - b->scroll.x
           , b->vis.y1 - b->scroll.y - player->pos.height
           , 0
           , &factors[0]
           , player->ptrans);
    }
    break;
    default:
    {
      int w, x, y;
      w = screen->bytesperrow;
      x = (b->in.x0 >> screen->xeig) << screen->log2Bpp;
      y = screen->height - (b->in.y1 >> screen->yeig);
      player->plot.dst = display_getDriverScreenBank() + player->plot.dst_offset;
      player->plot.x_mag = player->x_mag;                         // x scale factor
      player->plot.y_mag = player->y_mag;                         // y scale factor
      player->plot.wdst_x0 = (b->in.x0 - b->vis.x0 + b->scroll.x) >> screen->xeig; // in pixels
      player->plot.wdst_y0 = (b->vis.y1 - b->in.y1 - b->scroll.y) >> screen->yeig; // in pixels
      player->plot.wdst_width  = (b->in.x1 - b->in.x0) >> screen->xeig; // in pixels
      player->plot.wdst_height = (b->in.y1 - b->in.y0) >> screen->yeig; // in pixels
      player->plot.wdst = display_getDriverScreenBank() + w*y + x;       // dst top left address
      player->plot.wdst_bpr = w;
      player->plot.col_depth = screen->col_depth;
      player->plot.hardware = player->hardware;
      if ((player->plot.wdst_width > 0) && (player->plot.wdst_height > 0))
        ka_scale(&player->plot, &player->scaleconv);
    }
  }
}

static void dup_field(ka_vframe_t* pdest, const ka_vframe_t* psrc, int field, const ka_vinfo_t* vinfo)
{
  int width  = vinfo->luminance.width;
  int height = vinfo->luminance.height;
  int offset = field ? width : 0;

  dup_fast8
    ( psrc->planes.y + offset
    , pdest->planes.y
    , pdest->planes.y + width
    , width
    , width
    , height / 2
    );
  if (psrc->planes.cb)
  {
    offset >>= vinfo->chroma.wshift;
    width  >>= vinfo->chroma.wshift;
    height >>= vinfo->chroma.hshift;

    dup_fast8
      ( psrc->planes.cb + offset
      , pdest->planes.cb
      , pdest->planes.cb + width
      , width
      , width
      , height / 2
      );
    dup_fast8
      ( psrc->planes.cr + offset
      , pdest->planes.cr
      , pdest->planes.cr + width
      , width
      , width
      , height / 2
      );
  }
}

// Note (a + b + 1)/2 = a/2 + b/2 + (a | b) & 1
static void mix_fast8_c(uint8_t* dst, const uint8_t* src1, const uint8_t* src2, uint32_t width, uint32_t stride, uint32_t height)
{
  uint32_t* dst3 = (uint32_t*) dst;
  const uint32_t* srcp = (const uint32_t*) src1;
  const uint32_t* srcc = (const uint32_t*) src1;
  const uint32_t* srcn = (const uint32_t*) src2;
  uint32_t mask1 = 0x01010101u;
  uint32_t mask2 = 0x7f7f7f7fu;
  uint32_t prev, curr, next;
  uint32_t val, val2;

  width >>= 2;
  stride >>= 2;

  do
  {
    for (int i = width; i > 0; i--)
    {
      curr = *srcc++;
      prev = *srcp++;
      next = *srcn++;
      val = (prev | next) & mask1;
      val += (prev >> 1) & mask2;
      val += (next >> 1) & mask2;
      val2 = (val | curr) & mask1;
      val2 += (val >> 1) & mask2;
      val2 += (curr >> 1) & mask2;
      *dst3++ = val2;
    }
    srcp = srcc - width;
    srcc = srcn - width;
    srcn = (height <= 2) ? srcc : srcp + ((width + stride) << 1);
    dst += stride;
  }
  while(--height);
}

static void mix_fields(const player_t* player, ka_vframe_t* pdest, const ka_vframe_t* psrc1, const ka_vframe_t* psrc2, int field, const ka_vinfo_t* vinfo)
{
  int width  = vinfo->luminance.width;
  int height = vinfo->luminance.height;
  FNmix_fast8 fmix = (player->hardware & ka_hardware_neon) ? mix_fast8_neon : mix_fast8_c;
  // pscr1 should alway point to top field to avoid introducing some shaking
  if (field)
  {
    const ka_vframe_t* psrc;
    psrc = psrc1;
    psrc1 = psrc2;
    psrc2 = psrc;
  }

  fmix
    ( pdest->planes.y
    , psrc1->planes.y
    , psrc2->planes.y + width
    , width
    , 0
    , height
    );

  if (psrc1->planes.cb)
  {
    width  >>= vinfo->chroma.wshift;
    height >>= vinfo->chroma.hshift;

    if (vinfo->chroma.hshift)
    {
      fmix
        ( pdest->planes.cb
        , psrc1->planes.cb
        , psrc2->planes.cb + width
        , width
        , 0
        , height
        );
      fmix
        ( pdest->planes.cr
        , psrc1->planes.cr
        , psrc2->planes.cr + width
        , width
        , 0
        , height
        );
    }
    else
    {
      fmix
        ( pdest->planes.cb
        , psrc1->planes.cb
        , psrc2->planes.cb + width
        , width
        , 0
        , height
        );
      fmix
        ( pdest->planes.cr
        , psrc1->planes.cr
        , psrc2->planes.cr + width
        , width
        , 0
        , height
        );
    }
  }
}

/**
 * Plots the display:
 * - plot yuv frame to rgb screen or buffer (multitasking)
 * - plot osd to rgb screen or buffer
 * - when multitasking, plot rgb buffer into window
 *
 * @param  player  Player instance.
 */
void player_redrawLastFrame(player_t* player)
{
  ka_paint_t* paint = &player->paint;
  uint32_t t = timer_gettime();

  player->status_flags &= ~player_status_redrawframe;

  if (!player->drawer)
    return;

  // If video, don't refresh screen before the first frame is drawn
  if ((player->draw.frame.valid > 0)
  &&  (player->stream->state & ka_state_playing_video)
  &&  (player->drawn_count == 0))
    return;

  // Cannot start drawing before at least one vsync occured since last time
  display_waitsync(player);

  // Setup final output (cf screen banks and overlays)
  const _kernel_oserror *e = NULL;
  if (player->vo_overlay)
  {
    const gvoverlayplane_t* planes;
    e = display_getDriverOverlayBank(player, &planes);
    if (!e)
    {
      switch(player->screen_paint.col_depth)
      {
        case ka_col_depth_NV12:
        case ka_col_depth_NV21: // (2x2 sub-sampling with uv in single plane)
        {
          paint->dst = planes[0].ba;
          paint->dst_bpr = planes[0].stride;
          paint->dst_skip = paint->dst_bpr - paint->yc_width;
          paint->dst_u = planes[1].ba;
          paint->dst_u_bpr = planes[1].stride;
          paint->dst_v = NULL;
          paint->dst_v_bpr = 0;
        }
        break;
        case ka_col_depth_YV12: // (2x2 sub-sampling)
        {
          paint->dst = planes[0].ba;
          paint->dst_bpr = planes[0].stride;
          paint->dst_skip = paint->dst_bpr - paint->yc_width;
          paint->dst_u = planes[1].ba;
          paint->dst_u_bpr = planes[1].stride;
          paint->dst_v = planes[2].ba;
          paint->dst_v_bpr = planes[2].stride;
        }
        break;
        case ka_col_depth_YV16: // (2x1 sub-sampling)
        {
          paint->dst = planes[0].ba;
          paint->dst_bpr = planes[0].stride;
          paint->dst_skip = paint->dst_bpr - paint->yc_width;
          paint->dst_u = planes[1].ba;
          paint->dst_u_bpr = planes[1].stride;
          paint->dst_v = planes[2].ba;
          paint->dst_v_bpr = planes[2].stride;
        }
        break;
        default:
        {
          paint->dst = planes[0].ba;
          paint->dst_bpr = planes[0].stride;
          paint->dst_skip = paint->dst_bpr - paint->yc_width*player->screen_paint.bytes_per_pixel;
          paint->dst_u = NULL;
          paint->dst_u_bpr = 0;
          paint->dst_v = NULL;
          paint->dst_v_bpr = 0;
        }
      }
    }
  }
  else
  {
    paint->dst = (paint->dst_base ? paint->dst_base : display_getDriverScreenBank())
               + paint->dst_offset;
  }

  if (player->curr_frame)
  {
    ka_vframe_t* pframe;
    const ka_vinfo_t* vinfo = &player->draw.vinfo;

    // Avoid redraw if not valid
    if (player->curr_frame->valid <= 0)
      return;

    //
    // Produce a deinterlaced version from the current frame
    //

    // Copy current frame info
    pframe = &player->draw.frame;
    pframe->type = player->curr_frame->type;
    pframe->pts = player->curr_frame->pts;
    pframe->duration = player->curr_frame->duration;
    pframe->display_type = player->curr_frame->display_type;
    pframe->valid = player->curr_frame->valid;
    pframe->index = player->curr_frame->index;
    pframe->reference = player->curr_frame->reference;

    if (player->draw.fieldtype == 2)
    {
      if (ka_subtitles_getMatch(player->stream->subtitle.sstream, player->draw.pts, NULL))
      {
        // copy so we can add subtitles
        int width  = vinfo->luminance.width;
        int height = vinfo->luminance.height;

        memcpy(pframe->planes.y, player->curr_frame->planes.y, width * height);
        if (player->curr_frame->planes.cb)
        {
          width  >>= vinfo->chroma.wshift;
          height >>= vinfo->chroma.hshift;
          memcpy(pframe->planes.cb, player->curr_frame->planes.cb, width * height);
          memcpy(pframe->planes.cr, player->curr_frame->planes.cr, width * height);
        }
      }
      else
      {
        // Use current frame directly
        pframe = player->curr_frame;
      }
    }
    // Blend, update first field, avg other with prev
    else if (player->draw.deinterlace == KA_DEINTERLACE_BLEND)
    {
      mix_fields(player, pframe, player->curr_frame, player->curr_frame, 0, vinfo);
    }
    // Bob + blend, double frame rate, update one field, avg other with prev
    else if (player->draw.deinterlace == KA_DEINTERLACE_BLEND_X2)
    {
      ka_vframe_t* prev_frame;
      int field;

      if (player->draw.fieldtype == 1) // top first
        field = player->draw.fieldnr; // 0, 1
      else // bottom first
        field = 1 - player->draw.fieldnr; // 1, 0


      if (player->draw.fieldnr) // second field
        prev_frame = player->curr_frame;
      else
      {
        if ((player->prev_frame != NULL) && (player->prev_frame->valid > 0))
          prev_frame = player->prev_frame;
        else
          prev_frame = player->curr_frame;
      }

      mix_fields(player, pframe, player->curr_frame, prev_frame, field, vinfo);
    }
    // Double frame rate, display one field at double height
    else
    {
      int field;

      if (player->draw.fieldtype == 1) // top first
        field = player->draw.fieldnr; // 0, 1
      else // bottom first
        field = 1 - player->draw.fieldnr; // 1, 0

      dup_field(pframe, player->curr_frame, field, vinfo);
    }

    // add subtitles
    {
      const ka_sub_rect* rect = NULL;
      do
      {
        rect = ka_subtitles_getMatch(player->stream->subtitle.sstream, player->draw.pts, rect);

        if (rect != NULL)
        {
          uint32_t rtc = timer_getrtc();

          if (rtc < pframe->pts) rtc = pframe->pts;

          if (player->stream->demux.demuxer->vptr->FNsubtitleRect != NULL)
          {
            int display = 0;

            do
            {
              ka_sub_rect finalRect = *rect;

              display = player->stream->demux.demuxer->vptr->FNsubtitleRect(player->stream->demux.demuxer, rtc, &finalRect, display);

              if (display)
                ka_subtitles_render(vinfo, pframe, &finalRect);
            }
            while(display);
          }
          else
          {
            if (rect->typ == player->stream->subtitle.typ)
              ka_subtitles_render(vinfo, pframe, rect);
          }
        }
      }
      while (rect);
    }

    //
    // Perform colour space conversion of frame to screen/rgb buffer or overlay
    //
    paint->base.y = pframe->planes.y + paint->yc_offset;
    paint->base.cr = pframe->planes.cr + paint->uv_offset;
    paint->base.cb = pframe->planes.cb + paint->uv_offset;
    player->paint_sprite.base.y = pframe->planes.y + player->paint_sprite.yc_offset;
    player->paint_sprite.base.cr = pframe->planes.cr + player->paint_sprite.uv_offset;
    player->paint_sprite.base.cb = pframe->planes.cb + player->paint_sprite.uv_offset;

    if (!e)
    {
      player->drawer(paint);
    }
    else
    {
      ka_log(ka_log_error | ka_log_draw, "VideoOverlay : %s", e->errmess);
    }
  }
  else
  {
    //
    // No frame (audio only): Draw banner to screen/rgb buffer
    //

    display_paintbanner(player);
  }
  // Plot osd to screen/rgb buffer
  osd_draw(player);

  //
  // If color conversion was directly to screen or overlay, we're done.
  // If it was to rgb buffer we still need to plot that buffer to screen
  // (taking into account possible DMA transfers).
  //
  const ka_screen_t* screen = &player->screen_plot;

  uint32_t tn = timer_gettime();
  player->cputime.draw += tn - t;

  if (player->vo_overlay)
  {
    // action will be complete by display_swapBanks
    goto done;
  }
  //
  // Multitasking, plot rgb buffer to window
  //
  else if (player->mode == KA_MODE_MULTITASK)
  {
    wimp_redraw_t b;
    int more, dma_flag;
    intel_dma_t* idma = player->idma;
    uint8_t* screen_pos;
    uint8_t* data_pos;
    int width, height;

    b.window = win_handle.player;
    _swix(Wimp_GetWindowState, _IN(1), &b);
    b.vis.x1 += b.scroll.x - b.vis.x0;
    b.vis.x0 = b.scroll.x;
    b.vis.y0 += b.scroll.y - b.vis.y1;
    b.vis.y1 = b.scroll.y;

    // Check if we use DMA transfer
    if ((player->acceleration_mode_used == ACCEL_MODE_INTELDMA)
    &&  (player->x_mag == (1 << FRAC))
    &&  (player->y_mag == (1 << FRAC)))
      dma_flag = inteldma_startScreen(idma, player->plot.dst_bpr, player->plot.src_bpr);
    else dma_flag = 0;

    // Redraw loop
    _swix(Wimp_UpdateWindow, _IN(1)|_OUT(0), &b, &more);
    while (more)
    {
      // Try to handle block through DMA ?
      if (dma_flag)
      {
        // line by line from top of screen
        screen_pos = display_getDriverScreenBank()
                   + ((b.in.x0 >> screen->xeig) << screen->log2Bpp)
                   + (screen->height - (b.in.y1 >> screen->yeig)) * player->plot.dst_bpr;
        data_pos = player->plot.src
                 + (((b.in.x0 - b.vis.x0 + b.scroll.x) >> screen->xeig) << screen->log2Bpp)
                 + (((b.vis.y1 - b.scroll.y - b.in.y1) >> screen->yeig) * player->plot.src_bpr);
        width = ((b.in.x1 - b.in.x0) >> screen->xeig) << screen->log2Bpp;
        height = (b.in.y1 - b.in.y0) >> screen->yeig;

        dma_flag = inteldma_addScreenRect(idma, screen_pos, data_pos, width, height);
      }

      if (!dma_flag)
      {
        // Use scaler (like OS_SpriteOp) to do the plotting
        player_plotFrame(player, &b);
      }

      // Get next redraw block
      _swix(Wimp_GetRectangle, _IN(1)|_OUT(0), &b, &more);
    }
  }
  // DMA acceleration only if present and no scaling takes place
  else if (player->acceleration_mode_used == ACCEL_MODE_INTELDMA)
  {
    intel_dma_t* idma = player->idma;
    uint8_t* screen_pos;
    uint8_t* data_pos;
    int width, height;

    if ((player->mode == KA_MODE_AUTO)
    ||  (player->mode == KA_MODE_MANUAL))
    {
      inteldma_startScreen(idma, player->plot.dst_bpr, player->plot.src_bpr);

      // Single transfer of whole plotter savearea (area size = screen size)
      screen_pos = display_getDriverScreenBank();
      data_pos = ((uint8_t*) player->rgb_area[SAVEAREA_PLOTTER].frame)
               + player->rgb_area[SAVEAREA_PLOTTER].frame->offset
               + player->rgb_area[SAVEAREA_PLOTTER].frame->s_offset;
      width = player->plot.dst_bpr;
      height = screen->height;

      if (!inteldma_addScreenRect(idma, screen_pos, data_pos, width, height))
        memcpy(screen_pos, data_pos, width * height);
    }
    else if (player->mode == KA_MODE_DESKTOP)
    {
      inteldma_startScreen(idma, player->plot.dst_bpr, player->plot.src_bpr);

      screen_pos = display_getDriverScreenBank() + player->plot.dst_offset;
      data_pos = player->plot.src;
      width = (player->pos.width >> screen->xeig) << screen->log2Bpp;
      height = player->pos.height >> screen->yeig;

      if (!inteldma_addScreenRect(idma, screen_pos, data_pos, width, height))
      {
        // Do it by hand
        for (int i = height; i > 0; i--)
        {
          memcpy(screen_pos, data_pos, width);
          data_pos += player->plot.src_bpr;
          screen_pos += player->plot.dst_bpr;
        }
      }
    }
  }
  //
  // Full screen, plot rgb buffer to screen
  //
  else if (player->scaleconv.type != ka_scaler_none)
  {
    // Use scaler (like OS_SpriteOp) to do the plotting
    wimp_redraw_t b;

    b.window = -1;
    b.vis.x0 = player->pos.x;
    b.vis.y0 = player->pos.y;
    b.vis.x1 = player->pos.x + player->pos.width;
    b.vis.y1 = player->pos.y + player->pos.height;
    b.scroll.x = 0;
    b.scroll.y = 0;
    b.in.x0 = player->pos.x;
    b.in.y0 = player->pos.y;
    b.in.x1 = player->pos.x + player->pos.width;
    b.in.y1 = player->pos.y + player->pos.height;

    player_plotFrame(player, &b);
  }

  if (player->acceleration_mode_used == ACCEL_MODE_INTELDMA)
    inteldma_transferScreen(player->idma);

done:
  display_swapBanks(player);
  player->cputime.scale += timer_gettime() - tn;
}

/**
 * Initializes the display in function of the video stream characteristics.
 *
 * @param  player  Player instance.
 */
void player_setupDisplay(player_t* player)
{
  const ka_vinfo_t* vinfo = ka_stream_getVideoInfo(player->stream);

  // Assume 1st frame
  player->draw.pts = 0;
  player->draw.duration = 0;
  if (vinfo)
    player->draw.vinfo = *vinfo;

  display_setup(player, vinfo, INIT_DISPLAY);
  osd_navigate(player, OSD_NAV_UPDATE);
}

static void player_checkVideoInfo(player_t* player, uint32_t sequence_ref)
{
  const ka_vinfo_t* old = &player->draw.vinfo;
  const ka_vinfo_t* vinfo = ka_stream_getVideoInfo(player->stream);

  if (player->draw.sequence_ref != sequence_ref)
  {
    player->draw.sequence_ref = sequence_ref;

    if ((old->picture.width != vinfo->picture.width)
    ||  (old->picture.height != vinfo->picture.height)
    ||  (old->aspect_ratio.width != vinfo->aspect_ratio.width)
    ||  (old->aspect_ratio.height != vinfo->aspect_ratio.height))
    {
      player->draw.vinfo = *vinfo;
      player_adaptDisplay(player, player->mode, REDIM_CHANGE);
    }
  }
}

/**
 * Reinitializes the display (after full screen toggle or mode change)
 *
 * @param  player  Player instance.
 * @param  reason  Cause of the new setup.
 */
void player_adaptDisplay(player_t* player, int newmode, int reason)
{
  audio_t* astream = (player->stream) ? player->stream->audio.astream : NULL;
  int paused = (player->play == play_status_pause);

  if (!paused)
  {
    if (astream) audio_pause(astream, 1);
    timer_pausertc(1);
  }
  timer_pausetime(1);

  display_restoreDesktop(player, newmode, reason);
  if (reason == SCREEN_CHANGE)
    player->mode = newmode;
  display_setup(player, &player->draw.vinfo, reason);
  osd_navigate(player, OSD_NAV_UPDATE);
  player_redrawLastFrame(player);

  timer_pausetime(0);
  if (!paused)
  {
    timer_pausertc(0);
    if (astream) audio_pause(astream, 0);
  }
}

/**
 * Sets config flags to enable switching between multitasking desktop
 * and single tasking full screen modes. If a manual full screen mode
 * was selected as default then this is used, else the auto selected
 * full screen mode is used.
 */
void player_toggleFullScreen(player_t* player)
{
  switch(player->mode)
  {
    case KA_MODE_AUTO:
    case KA_MODE_MANUAL:
      player_adaptDisplay(player, KA_MODE_MULTITASK, SCREEN_CHANGE);
    break;
    default:
      player_adaptDisplay(player, KA_MODE_AUTO, SCREEN_CHANGE);
  }
}

/**
 * Checks if we must skip the decoding of a new frame.
 *
 * @param  player  Player instance.
 */
void player_onNewFrameStarted(player_t* player)
{
  const ka_vinfo_t* vinfo = ka_stream_getVideoInfo(player->stream);
  ka_vframe_t* pframe = ka_stream_getDecodeFrame(player->stream);

  player->frame_count++;

  // Stats
  switch(pframe->type)
  {
    case ka_vframe_type_B:
    {
      player->B_count++;

      // Must be dropped?
      if (player->I_skip || !player->F_valid || !player->B_valid)
        pframe->valid = 0;
      else
        pframe->valid = (player->frames_late <= (int) vinfo->frame_period);
    }
    break;
    case ka_vframe_type_P:
    {
      player->F_valid = player->B_valid;
      player->P_count++;

      if (player->I_skip || !player->F_valid)
        pframe->valid = 0;
      else
        pframe->valid = (player->frames_late <= (int) (8*vinfo->frame_period));

      player->B_valid = (pframe->valid > 0);
    }
    break;
    default: // I/D TYPE
    {
      player->F_valid = player->B_valid;
      player->I_count++;

      if (player->I_skip)
      {
        pframe->valid = 0;
        player->I_skip = 0;
      }
      else
        pframe->valid = 1;

      player->B_valid = (pframe->valid > 0);
    }
  }

  if ((pframe->type != ka_vframe_type_B)
  &&  (player->P_delta > 0)
  &&  (player->frames_late > (player->I_delta >> 1))
  &&  (player->I_delta > (player->P_delta << 1))
  &&  (player->P_delta >= (player->B_delta << 1)))
    player->B_valid = 0; // Force skipping of B/P-frames

  player->I_skip |= (player->frames_late > (int32_t) timer_getcs(50));

  // All frames
  if (config.debug & cfg_displayall)
    pframe->valid = 1;

  // Debug
  // static const char* stype[3] = {"I", "P", "B"};
  // ka_log(ka_log_demux, "%s frame, sec %02x, pts %10u valid %d, late %d"
  //      , stype[pframe->type], pframe->section & 0xff, pframe->pts, pframe->valid, player->frames_late);

  if (pframe->valid <= 0)
  {
    ka_stream_skipDecodeFrame(player->stream);
  }
}

/**
 * Draws the decoded frame at the correct time.
 *
 * @param  player  Player instance.
 *
 * @returns no frame, drawn, too early, suspciously early
 */
play_draw player_onFrameToDraw(player_t* player, ka_vframe_t* frame)
{
  if (frame == NULL)
    return play_draw_noframe;

  // Ensure display is unchanged (same aspect ratio, ...)
  player_checkVideoInfo(player, frame->sequence_ref);

  // Determine nr of fields to display and deinterlacing mode
  uint32_t rtc = timer_getrtc();
  const ka_vinfo_t* vinfo = &player->draw.vinfo;
  int valid = frame->valid;
  int ptsDiscrepency = 0;
  int field_type = frame->display_type;
  int deinterlace = KA_DEINTERLACE_NONE;
  static int fields_per_draw[KA_DEINTERLACE_MAX] = {2, 2, 1, 1};
  if (field_type != 2)
  {
    if (config.video.deinterlace == KA_DEINTERLACE_NONE)
      field_type = 2;
    else
      deinterlace = config.video.deinterlace;
  }

  audio_t* astream = (player->stream) ? player->stream->audio.astream : NULL;
  const char *st, *s0, *s1 = "", *s2 = "";
  static const char* stype[3] = {"I", "P", "B"};
  uint32_t avpts;

  //
  // Time keeping, allow a +- one frame time tolerance
  //
  s0 = stype[frame->type];

  if (player->draw.duration == 0)
  {
    player->draw.duration = vinfo->frame_period;
  }

  st = (player->draw.fieldnr) ? "field" : "frame";

  // Use estimated pts (presentation time stame) unless the frame has one.
  if (frame->pts)
  {
    int32_t deltapts = (int32_t)(frame->pts - player->draw.pts);

    if ((valid > 0)
    &&  player->drawn_count
    &&  (abs(deltapts) >= timer_getrate()))
    {
      ptsDiscrepency = 1;
      ka_log(ka_log_error | ka_log_draw, "Pts discrepency frame: %10u expected: %10u", frame->pts, player->draw.pts);
    }
    // assume time discrepency on section change
    if (frame->section != player->draw.section)
      ptsDiscrepency = 1;

    // first field ?
    if (player->draw.fieldnr == 0)
    {
      player->draw.section = frame->section;
      player->draw.pts = frame->pts;
    }
  }
  else
  {
    s1 = ", estimated";
    // first frame should have a pts for sync with audio
    if (!player->drawn_count && !(player->stream->stack->properties & ps_stack_prop_notimestamp))
      valid = 0;
  }

  int32_t deltartc = (int32_t)(rtc - player->draw.pts);

  if (deltartc > 0)
  {
    s2 = ", late";

    // drop second field draw if we are too late (never the first one, time spent decoding the frame would be wated)
    if ((player->draw.fieldnr)
    &&  (deltartc > player->draw.duration))
      valid = 0;
  }
  else if ((player->draw.duration >> 1) < -deltartc)
  {
    s2 = ", early";

    // Wait if too early, unless asap display option is enabled
    if ((config.debug & cfg_displayall) == 0)
    {
      avpts = player->draw.pts - (player->draw.duration >> 1);

      if (player->drawn_count // Cf discard blocks before first I frame
      && ((int32_t)(avpts - rtc) > (int32_t)timer_getcs(50)))
      {
         return play_draw_early_suspicious;
      }

      if ((avpts > rtc) && (valid || player->drawn_count)
      &&  ((player->frame_count < 100) || (5*player->drawn_count > 4*player->frame_count)))
      {
        return play_draw_early; // way still do some work till then
      }
    }
  }

  if (astream) audio_checkStart(astream, (rtc >= player->draw.pts) ? rtc : player->draw.pts, player->stream->displaySection);

  //
  // Do we keep older frames ?
  //
  if (player->curr_frame != NULL)
  {
    if (player->curr_frame->index != frame->index)
    {
      if (fields_per_draw[deinterlace] == 2)
      {
        // Both fields of new frame displayed at once, discard current frame
        ka_stream_releaseFrame(player->stream, player->draw.picturenr - 1);
        player->prev_frame = NULL;
      }
      else
      {
        // We still have some use for second field
        player->prev_frame = player->curr_frame;
      }
      player->curr_frame = frame;
    }
    else
    {
      // Second field of previous frame displayed, previous frame is of no more use
      if (player->prev_frame)
      {
        ka_stream_releaseFrame(player->stream, player->draw.picturenr - 1);
        player->prev_frame = NULL;
      }
    }
  }
  else
  {
    player->curr_frame = frame;
    player->prev_frame = NULL;
  }

  //
  // Draw field/picture (if its a valid one), determine next field/picture
  //
  if (valid > 0)
  {
    player->draw.fieldtype = field_type;
    player->draw.deinterlace = deinterlace;

    player->status_flags |= player_status_redrawframe;
//    player_redrawLastFrame(player);

    if (player->draw.fieldnr == 0)
    {
      player->drawn_count++;
      // Stats
      switch(frame->type)
      {
        case ka_vframe_type_P: player->P_drawn++; break;
        case ka_vframe_type_B: player->B_drawn++; break;
        default: // I/D TYPE
          player->I_drawn++;
      }
    }
    player->drawn_field_count++;

    if (config.debug & cfg_printdrawstats)
      ka_log(ka_log_draw, "%s %s, sec %02x, pts %10u%s%s", s0, st, frame->section & 0xff, player->draw.pts, s1, s2);

    player->draw.fieldnr += fields_per_draw[deinterlace];
    if (player->draw.fieldnr > 1)
    {
      player->draw.picturenr++;
      player->draw.fieldnr = 0;
    }

    if (player->draw.fieldnr == 0)
    {
      uint32_t tn = timer_gettime();

      if (player->drawn_count == 1)
        player->time.fps_last = tn;
      else if (player->drawn_count < 8)
      {
          player->time.fps_delta = (tn - player->time.fps_last) / (player->drawn_count - 1);
      }
      else if (!(player->drawn_count & 7))
      {
        player->time.fps_delta = (tn - player->time.fps_last) >> 3;
        player->time.fps_last = tn;
      }
    }
  }
  else
  {
    if (config.debug & cfg_printdrawstats)
      ka_log(ka_log_draw, "%s %s, sec %02x, pts %10u%s, %s", s0, st, frame->section & 0xff, player->draw.pts, s1, (valid == 0) ? "dropped" : "invalid");

    player->draw.fieldnr += fields_per_draw[deinterlace];
    if (player->draw.fieldnr > 1)
    {
      player->draw.picturenr++;
      player->draw.fieldnr = 0;
    }
  }

  //
  // Determine time for next draw, and by how much frame we are behind shedule
  //
  if (player->draw.fieldnr)
  {
    // Second field yet to display, return after updating display time for second field
    player->draw.duration = frame->duration / 2;
    player->draw.pts += player->draw.duration;
    player->draw.timetonext = timer_gettime() + (timer_getrtc() - player->draw.pts);
    return play_draw_drawn;
  }

  // Next is new picture of first field
  switch(frame->type)
  {
    case ka_vframe_type_I:
    {
      player->I_delta = (int) (player->draw.pts - player->I_pts);
      if (2*player->I_delta < (int) frame->duration) player->I_delta = timer_getrate();
      player->I_pts = player->draw.pts;
    }
    // no break;
    case ka_vframe_type_P:
    {
      player->P_delta = (int) (player->draw.pts - player->P_pts);
      if (2*player->P_delta < (int) frame->duration) player->P_delta = timer_getrate();
      player->P_pts = player->draw.pts;
    }
    // no break;
    default:
      player->B_delta = (int) (player->draw.pts - player->B_pts);
      if (2*player->B_delta < (int) frame->duration) player->B_delta = timer_getrate();
      player->B_pts = player->draw.pts;
  }

  // Estimate time for next frame
  player->draw.duration = (fields_per_draw[deinterlace] == 2) ? frame->duration : (frame->duration / 2);
  player->draw.pts += player->draw.duration;
  player->draw.timetonext = timer_gettime() + (timer_getrtc() - player->draw.pts);

  if (player->drawn_count > 1)
  {
    // See if we are somewhat late and need to skip some frames
    avpts = player->draw.pts - (vinfo->frame_period >> 1);
    player->frames_late = (int) (timer_getrtc() - avpts);
    // limit skipping, it could be a corrupted stream
    if ((player->frames_late > (int32_t)timer_getcs(400))
    ||  (player->frames_late < 0)
    ||  ptsDiscrepency)
      player->frames_late = 0;
  }

  return play_draw_drawn;
}

const player_infostats_t* player_buildInfoStats(const player_t* player)
{
  const ka_stream_t* stream = player->stream;
  const ka_vinfo_t* vinfo = NULL;
  const audio_info_t* ainfo = NULL;
  int i, vtime;
  float playing_time;
  char str1[64], str2[64];
  static player_infostats_t is;

  for(i=0; i< 10; i++)
    *is.s[i] = 0; // clear strings

  if (!stream)
    return &is;

  vinfo = &player->draw.vinfo;

  vtime = (int)(stream->time.end - stream->time.replay_start);
  if (vtime <= 0) vtime = 1;

  const char* leafname = strrchr(playlist_getcurrentfilm(), '.');
  leafname = (leafname) ? leafname + 1 : playlist_getcurrentfilm();
  if (!stricmp(leafname, "VIDEO_TS"))
    leafname = playlist_getcurrentfilm();

  snprintf(is.s[0], sizeof(is.s[0]), "%s: %s, %s"
                 , report_string(str1, sizeof(str1), "info_film")
                 , leafname
                 , (stream->demux.demuxer)
                 ? stream->demux.demuxer->vptr->name
                 : report_string(str2, sizeof(str2), "info_ust"));

  if (vinfo && vinfo->videotype)
  {
    float film_fps = vinfo->frame_period
                   ? (float) timer_getrate() / (float)vinfo->frame_period
                   : 0;

    snprintf(is.s[1], sizeof(is.s[1]), "%s, %s %d x %d %c, %s %d:%d"
                   , vinfo->videotype
                   , report_string(str1, sizeof(str1), "info_size")
                   , vinfo->picture.width
                   , vinfo->picture.height
                   , vinfo->progressive ? 'p' : 'i'
                   , report_string(str2, sizeof(str2), "info_aspect")
                   , vinfo->aspect_ratio.width
                   , vinfo->aspect_ratio.height);

    snprintf(is.s[2], sizeof(is.s[2]), "%s %d Kbit/sec, %s"
                   , report_string(str1, sizeof(str1), "info_bitrate")
                   , vinfo->bitrate
                   , ColourConverter_ChromaTypeName(player->colourspace.chroma_type));

    snprintf(is.s[3], sizeof(is.s[3]), "%d %s (I=%d P=%d B=%d) %.2f fps"
                   , player->frame_count
                   , report_string(str1, sizeof(str1), "info_frames")
                   , player->I_count
                   , player->P_count
                   , player->B_count
                   , film_fps);
  }

  // The audio information
  if(stream->audio.astream)
    ainfo = audio_info(stream->audio.astream);

  if (ainfo && ainfo->name[0])
  {
    snprintf(is.s[4], sizeof(is.s[4]), "%s", ainfo->name);

    snprintf(is.s[5], sizeof(is.s[5]), "%s %d Kbit/sec, %s %.3f KHz"
                   , report_string(str1, sizeof(str1), "info_bitrate")
                   , ainfo->bitrate
                   , report_string(str2, sizeof(str2), "info_freq")
                   , (float) (ainfo->samp_rate / 1000.F));

    snprintf(is.s[6], sizeof(is.s[6]), "%d %s"
                   , ainfo->channels
                   , (ainfo->channels > 1)
                   ? report_string(str1, sizeof(str1), "info_channels")
                   : report_string(str1, sizeof(str1), "info_channel")
                   );
  }

  // These two are about the player's performance
  playing_time = (float)vtime / (float) timer_getrate();
  snprintf(is.s[7], sizeof(is.s[7]), "%s %.2f secs"
                 , report_string(str1, sizeof(str1), "info_play")
                 , playing_time);

  if (vinfo)
  {
    int drawn_count = player->drawn_count;
    float actual_fps = (playing_time)
                     ? (float)drawn_count / playing_time
                     : 0.F;
    float perc = (player->frame_count)
               ? (float)(((drawn_count * 100.F) / (float)player->frame_count))
               : 0.F;

    snprintf(is.s[8], sizeof(is.s[8]), "%d (+%d) %s (%.2f%%) %s (I=%d P=%d B=%d) %.2f fps"
                   , player->drawn_count
                   , player->drawn_field_count - player->drawn_count
                   , report_string(str1, sizeof(str1), "info_frames")
                   , perc
                   , report_string(str2, sizeof(str2), "info_displayed")
                   , player->I_drawn
                   , player->P_drawn
                   , player->B_drawn
                   , actual_fps);

    if (player->scaleconv.type != ka_scaler_none)
      snprintf(is.s[9], sizeof(is.s[9]), "%s %s, %s x=%4.1f%% y=%4.1f%%"
                     , ColourConverter_ColourDepthName(player->screen_paint.col_depth)
                     , report_string(str1, sizeof(str1), "info_colours")
                     , report_string(str2, sizeof(str2), "info_zoom")
                     , (player->x_mag)
                       ? (100<<FRAC)/(double)player->x_mag
                       : 100.F
                     , (player->y_mag)
                       ? (100<<FRAC)/(double)player->y_mag
                       : 100.F);
    else
      snprintf(is.s[9], sizeof(is.s[9]), "%s %s, %s %d%%"
                     , ColourConverter_ColourDepthName(player->screen_paint.col_depth)
                     , report_string(str1, sizeof(str1), "info_colours")
                     , report_string(str2, sizeof(str2), "info_zoom")
                     , player->zoom);
  }

  return &is;
}

/**
 * Write Info summary statistics on the stream played.
 *
 * @param  player  Player instance.
 */
static void player_writeInfoStats(const player_t* player)
{
  const player_infostats_t* is = player_buildInfoStats(player);
  int i, len;
  FILE* f;
  static const char info[] = "Inf2";
  const int vers = 10;

  if (!player->stream)
    return;

  if ((f = fopen("<KinoChoices$Dir>.Info", "wb")) != 0)
  {

    fwrite(&info, sizeof(char), 4, f);
    fwrite(&vers, sizeof(int), 1, f);

    for (i = 0; i < 10; i++)
    {
      len = strlen(is->s[i]) + 1;
      fwrite(&len, sizeof(int), 1, f);
      fwrite(is->s[i], sizeof(char), len, f);
    }

    fclose(f);
  }
}

/**
 * Logs summary statistics on the stream played.
 *
 * @param  player  Player instance.
 */
static void player_logStats(const player_t* player)
{
  const player_infostats_t* is = player_buildInfoStats(player);
  const ka_stream_t* stream = player->stream;
  char str1[64];
  float time;
  float cpu;

  if (!stream)
    return;

  ka_log(ka_log_stats, "\n%s", is->s[0]);
  ka_log(ka_log_stats, "%s", is->s[7]);
  ka_log(ka_log_stats, "");

  ka_stream_logStats(stream);

  if (*is->s[1])
  {
    ka_log(ka_log_stats, "\n%s:", report_string(str1, sizeof(str1), "info_video"));
    ka_log(ka_log_stats, "%s", is->s[1]);
    ka_log(ka_log_stats, "%s", is->s[2]);
    ka_log(ka_log_stats, "%s", is->s[3]);
    ka_log(ka_log_stats, "%s", is->s[8]);
    ka_log(ka_log_stats, "%s", is->s[9]);
  }

  if (*is->s[4])
  {
    ka_log(ka_log_stats, "\n%s:", report_string(str1, sizeof(str1), "info_audio"));
    ka_log(ka_log_stats, "%s", is->s[4]);
    ka_log(ka_log_stats, "%s", is->s[5]);
    ka_log(ka_log_stats, "%s", is->s[6]);
  }

  time = (float)(int)(stream->time.end - stream->time.start);
  cpu = (float)(int)(stream->cputime.io
      + stream->cputime.demux
      + stream->cputime.subtitles
      + stream->cputime.audio
      + stream->cputime.video
      + player->cputime.draw
      + player->cputime.sync
      + player->cputime.wait
      );
  if (time <= cpu) time = cpu; // occurs when cannot display stream
  if (time <= 0.F) time = 1.F;

  ka_log(ka_log_stats, "\n%s:     %10d (%1.2f seconds)"
        , report_string(str1, sizeof(str1), "info_time")
        , (int) time, (float)(time / (float) timer_getrate()));
  time /= 100.F;
  ka_log(ka_log_stats, "fread   = %10d (%5.2f%%)", stream->cputime.io, (float)(stream->cputime.io / time));
  ka_log(ka_log_stats, "demux   = %10d (%5.2f%%)", stream->cputime.demux, (float)(stream->cputime.demux / time));
  ka_log(ka_log_stats, "audio   = %10d (%5.2f%%)", stream->cputime.audio, (float)(stream->cputime.audio / time));
  ka_log(ka_log_stats, "video   = %10d (%5.2f%%)", (stream->cputime.video + stream->cputime.subtitles)
                                                 , (float)((uint32_t)(stream->cputime.video + stream->cputime.subtitles) / time));
//  ka_log(ka_log_stats, " slices = %10d (%5.2f%%)", fn_time[3], (float)(fn_time[3] / time));
  ka_log(ka_log_stats, "drawer  = %10d (%5.2f%%)", player->cputime.draw, (float)(player->cputime.draw / time));
  ka_log(ka_log_stats, "scaler  = %10d (%5.2f%%)", player->cputime.scale, (float)(player->cputime.scale / time));
  ka_log(ka_log_stats, "sync    = %10d (%5.2f%%)", player->cputime.sync, (float)(player->cputime.sync / time));
  ka_log(ka_log_stats, "wait    = %10d (%5.2f%%)", player->cputime.wait, (float)(player->cputime.wait / time));
}

/**
 * Returns the current play status.
 *
 * @param  player  Player instance.
 *
 * @return Current play status.
 */
play_status player_getPlayStatus(const player_t* player)
{
 return player->play;
}

/**
 * Sets new player status, updates timer/audio/osd, refresh controls.
 *
 * @param  player  Player instance.
 * @param  status  New play status.
 */
void player_setPlayStatus(player_t* player, play_status status)
{
  audio_t* astream = (player->stream) ? player->stream->audio.astream : NULL;

  // Validate
  if (status == player->play)
    return;

  if (player->play == play_status_stop)
  {
    if ((status != play_status_play)
    &&  (status != play_status_advance))
      return;
  }

  switch (status)
  {
    case play_status_play:
    {
      if (player->play != play_status_stop)
      {
        if (astream) audio_pause(astream, 0);
        player->frames_late = 0;
      }
      else
      {
        if (!(player->status_flags & player_status_act))
          player->status_flags |= player_status_restart;
      }
      timer_pause(0);
      osd_navigate(player, OSD_NAV_PAUSE_OFF);
    }
    break;
    case play_status_pause:
    {
      if (astream) audio_pause(astream, 1);
      timer_pause(1);
      osd_navigate(player, OSD_NAV_PAUSE_ON);
    }
    break;
    case play_status_advance:
    {
      // Advance one frame
      if (astream) audio_pause(astream, 0);
      timer_pause(0);
      player->frames_late = 0;
    }
    break;
    case play_status_stop:
    {
      player->status_flags &= ~player_status_act;
      if (astream) audio_pause(astream, 1);
      timer_pause(1);

      if (config.debug & cfg_printstatssummary)
        player_logStats(player);
      player_writeInfoStats(player);
    }
    break;
  }

  player->play = status;
  update_controls(player);
}

/**
 * Updates the player state flags and set play status to play.
 *
 * @param  player  Player instance.
 * @param  state   New additional state flags.
 */
void player_restartPlay(player_t* player, uint32_t state)
{
  if (state & player_status_new)
    player->status_flags =  player_status_new;
  else
    player->status_flags |= state;
  player_setPlayStatus
    ( player
    , (config.control & cfg_ctrl_start_paused)
      ? play_status_advance
      : play_status_play
    );
}

/**
 * Go to a film position, either relative to current, or
 * from the start, in percent of filesize
 */
void player_seek(player_t* player, int val, int range, int relative)
{
  if (player->stream)
  {
    ka_demux_pos_t pos;
    int64_t step, jump;
    ka_stream_getPosInfo(player->stream, &pos);

    step = pos.endPos / (uint64_t) range;
    jump = step * (uint64_t) val;

    if (relative) // to current position
    {
      if ((jump < 0) && (pos.curPos < step)) // stop autorepeating back to start
        return;

      jump = pos.curPos + jump;
    }
    // limit forward jumps
    if (jump > pos.endPos)
      return;

    // limit backward jumps
    if (jump < 0)
      jump = 0;

    if (ka_stream_seek(player->stream, jump))
      player_restartPlay(player, player_status_seek);
  }
}
