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

#include "kernel.h"
#include "swis.h"
#include "inttypes.h"
#include "player.h"
#include "config.h"
#include "msg.h"
#include "osd.h"
#include "osd_font.h"
#include "ka_mem.h"
#include "display.h"
#include "timer1.h"

#define OSD_UPDATE_VAL 0x80000000

typedef struct osditem_t
{
  char *title;
  int min, max;
  int diff;     // if 0, the value cannot be changed using left/right - set() redraws OSD
  int nul;      // if diff==0, nul is passed to set() (instead of *val) when redrawing
  int *val;     // val is not used if diff==0
  int (* set)(player_t* vo, int val);
} osditem_t;

static int caudio_volume(player_t* player, int vol)
{
  audio_t* astream = (player->stream) ? player->stream->audio.astream : NULL;
  if (!astream) return (vol >= 0) ? vol : 0;
  return audio_volume(astream, vol);
}

static const osditem_t
  osditem_empty      = { "",           0, 100, 0,  -1, NULL,               NULL },
  osditem_brightness = { "Brightness", 0, 200, 2, 100, &config.video.brightness, display_brightness },
  osditem_contrast   = { "Contrast",   0, 200, 2, 100, &config.video.contrast,   display_contrast },
  osditem_colour     = { "Colour",     0, 200, 2, 100, &config.video.colour,     display_colour },
  osditem_volume     = { "Volume",     0, 127, 1,  -1, &config.audio.volume, caudio_volume };

struct osd_s
{
  uint32_t state;
  int      zoom;
  int      current;
  char*    bitmap;
  char*    emptyline;
  int      sizex;
  int      sizey;
  int      changed[4];
  const    osditem_t* osditems[10];
  uint32_t draw_timer;
  uint32_t msg_timer;
  char     msg[256];
};

static uint8_t palette_8[8]   = { 0, 1, 2, 3, 4, 5, 6, 7};
static const uint8_t palette_y[8]   = { 0x00, 0x4c, 0x95, 0xe1, 0x1d, 0x69, 0xb2, 0xff};
//static const uint8_t palette_u[8]   = { 0x80, 0x00, 0x00, 0x00, 0x6f, 0x49, 0x25, 0x80};
//static const uint8_t palette_v[8]   = { 0x80, 0x9c, 0x00, 0x19, 0x00, 0x83, 0x00, 0x80};

static const uint16_t palette_12[8]   = { 0x0000, 0x000f, 0x00f0, 0x00ff,
                                       0x0f00, 0x0f0f, 0x0ff0, 0x0fff };
static const uint16_t palette_12_r[8] = { 0x0000, 0x0f00, 0x00f0, 0x0ff0,
                                       0x000f, 0x0f0f, 0x00ff, 0x0fff };
static const uint16_t palette_15[8]   = { 0x0000, 0x001f, 0x03e0, 0x03ff,
                                       0x7c00, 0x7c1f, 0x7fe0, 0x7fff };
static const uint16_t palette_15_r[8] = { 0x0000, 0x7c00, 0x03e0, 0x7fe0,
                                       0x001f, 0x7c1f, 0x03ff, 0x7fff };
static const uint16_t palette_16[8]   = { 0x0000, 0x001f, 0x07e0, 0x07ff,
                                                0xf800, 0xf81f, 0xffe0, 0xffff };
static const uint16_t palette_16_r[8] = { 0x0000, 0xf800, 0x07e0, 0xffe0,
                                                0x001f, 0xf81f, 0x07ff, 0xffff };
static const uint32_t palette_32[8]   = { 0x00000000, 0x000000ff, 0x0000ff00, 0x0000ffff,
                                     0x00ff0000, 0x00ff00ff, 0x00ffff00, 0x00ffffff };
static const uint32_t palette_32_r[8] = { 0x00000000, 0x00ff0000, 0x0000ff00, 0x00ffff00,
                                     0x000000ff, 0x00ff00ff, 0x0000ffff, 0x00ffffff };
static const uint16_t palette_a12[8]   = { 0xf000, 0xf00f, 0xf0f0, 0xf0ff,
                                        0xff00, 0xff0f, 0xfff0, 0xffff };
static const uint16_t palette_a12_r[8] = { 0xf000, 0xff00, 0xf0f0, 0xfff0,
                                        0xf00f, 0xff0f, 0xf0ff, 0xffff };
static const uint16_t palette_a15[8]   = { 0x8000, 0x801f, 0x83e0, 0x83ff,
                                        0xfc00, 0xfc1f, 0xffe0, 0xffff };
static const uint16_t palette_a15_r[8] = { 0x8000, 0xfc00, 0x83e0, 0xffe0,
                                        0x801f, 0xfc1f, 0x83ff, 0xffff };
static const uint32_t palette_a32[8]   = { 0xff000000, 0xff0000ff, 0xff00ff00, 0xff00ffff,
                                      0xffff0000, 0xffff00ff, 0xffffff00, 0xffffffff };
static const uint32_t palette_a32_r[8] = { 0xff000000, 0xffff0000, 0xff00ff00, 0xffffff00,
                                      0xff0000ff, 0xffff00ff, 0xff00ffff, 0xffffffff };
#define CHANGED_AREA_EMPTY    -123456

typedef enum
{ OSD_COLOUR_TRANSPARENT   = 255
, OSD_COLOUR_BLACK         = 0
, OSD_COLOUR_RED           = 1
, OSD_COLOUR_GREEN         = 2
, OSD_COLOUR_YELLOW        = 3
, OSD_COLOUR_BLUE          = 4
, OSD_COLOUR_MAGENTA       = 5
, OSD_COLOUR_CYAN          = 6
, OSD_COLOUR_WHITE         = 7
} osd_colour;
/*
static void osd_save(const osd_t* osd, char* filename)
{
  _kernel_swi_regs regs;
  FILE* f;
  sprite_header_t h;
  int width, height;

  width = osd->sizex >> 2; // width is now always in words
  height = osd->sizey;

  if((f = fopen(filename,"wb")) == 0)
  {
    report_error(0, "error10:%s", filename); // (Cannot create Sprite file)
    return;
  }

  // write sprite file header
  h.num = 1;
  h.offset = 16;
  h.size_1 = 4 * width * height + 60;
  h.size_2 = h.size_1 - 16;
  h.name[10] = 0; // ensure it's padded with zeros,
  h.name[11] = 0; // should use \0 in sprintf but it didn't always work ?
  sprintf(h.name,"frameframe");
  h.width = width - 1;
  h.height = height - 1;
  h.first = 0;
  h.last = 31;
  h.s_offset = 44;
  h.m_offset = h.s_offset; // no mask
  h.mode = 28;
  fwrite(&h.num,sizeof(sprite_header_t)-4,1,f);

  // write sprite data
  fwrite(osd->bitmap,1,4*width*height,f);

  fclose(f);

  // set filetype
  regs.r[0] = WRITE_CATINFO;
  regs.r[1] = (int)filename;
  regs.r[2] = SPRITE;
  _kernel_swi(OS_File, &regs, &regs);
}
*/
static void get_char_min_max_x(unsigned char c, int *min, int *max)
{
  int y, x;
  const unsigned short *glyph;

  *max = 0;
  *min = FONTSIZE_X;
  glyph = font12x24[c];
  for (y = 0; y < FONTSIZE_Y; y++)
  {
    unsigned short mask;

    mask = glyph[y];

    for (x = 0; (mask & (1<<x)) == 0 && x < FONTSIZE_X; x++)
      ;
    if (x < *min)
      *min = x;
    for (x = FONTSIZE_X-1; (mask & (1<<x)) == 0 && x > 0; x--)
      ;
    if (x > *max)
      *max = x;
  }
  if (*min >= *max)
    *min = *max = 0;
  if (c == ' ')
    *max += 3;
}

#define DRAW_BITMAP_LINE(wr0, rd, x0, x1, type, palette) \
    { type* wr = (type*) wr0;                    \
      int x;                                     \
      for (x = x1-1; x >= x0; x--)               \
        if (rd[x] != OSD_COLOUR_TRANSPARENT)     \
          wr[x] = palette[rd[x] & 7];            \
    }

#define DRAW_BITMAP_LINEXY(wr0, rd, x0, x1, offset, step, palette) \
    { uint8_t* wr = ((uint8_t*) wr0) + offset;   \
      int x;                                     \
      for (x = x1-1; x >= x0; x--)               \
        if (rd[x] != OSD_COLOUR_TRANSPARENT)     \
          wr[x*step] = palette[rd[x] & 7];       \
    }

#define DRAW_BITMAP_LINE_SCALED(wr0, rd, x0, x1, type, palette) \
    { type* wr = (type*) wr0;                           \
      int x;                                            \
      for (x = x1-1; x >= x0; x--)                      \
        if (rd[x] != OSD_COLOUR_TRANSPARENT)            \
          wr[2*x+0] = wr[2*x+1] = palette[rd[x] & 7];   \
    }

#define DRAW_BITMAP_LINEXY_SCALED(wr0, rd, x0, x1, offset, step, palette) \
    { uint8_t* wr = ((uint8_t*) wr0) + offset;          \
      int x;                                            \
      for (x = x1-1; x >= x0; x--)                      \
        if (rd[x] != OSD_COLOUR_TRANSPARENT)            \
          wr[step*(2*x+0)] = wr[step*(2*x+1)] = palette[rd[x] & 7];   \
    }

#define DRAW_BITMAP_LINE_8(wr, rd, x0, x1)              \
            DRAW_BITMAP_LINE(wr, rd, x0, x1, uint8_t,  palette_8)
#define DRAW_BITMAP_LINE_12(wr, rd, x0, x1)             \
            DRAW_BITMAP_LINE(wr, rd, x0, x1, uint16_t, palette_12)
#define DRAW_BITMAP_LINE_12_R(wr, rd, x0, x1)             \
            DRAW_BITMAP_LINE(wr, rd, x0, x1, uint16_t, palette_12_r)
#define DRAW_BITMAP_LINE_15(wr, rd, x0, x1)             \
            DRAW_BITMAP_LINE(wr, rd, x0, x1, uint16_t, palette_15)
#define DRAW_BITMAP_LINE_15_R(wr, rd, x0, x1)             \
            DRAW_BITMAP_LINE(wr, rd, x0, x1, uint16_t, palette_15_r)
#define DRAW_BITMAP_LINE_16(wr, rd, x0, x1)             \
            DRAW_BITMAP_LINE(wr, rd, x0, x1, uint16_t, palette_16)
#define DRAW_BITMAP_LINE_16_R(wr, rd, x0, x1)             \
            DRAW_BITMAP_LINE(wr, rd, x0, x1, uint16_t, palette_16_r)
#define DRAW_BITMAP_LINE_32(wr, rd, x0, x1)             \
            DRAW_BITMAP_LINE(wr, rd, x0, x1, uint32_t, palette_32)
#define DRAW_BITMAP_LINE_32_R(wr, rd, x0, x1)             \
            DRAW_BITMAP_LINE(wr, rd, x0, x1, uint32_t, palette_32_r)
#define DRAW_BITMAP_LINE_A12(wr, rd, x0, x1)             \
            DRAW_BITMAP_LINE(wr, rd, x0, x1, uint16_t, palette_a12)
#define DRAW_BITMAP_LINE_A12_R(wr, rd, x0, x1)             \
            DRAW_BITMAP_LINE(wr, rd, x0, x1, uint16_t, palette_a12_r)
#define DRAW_BITMAP_LINE_A15(wr, rd, x0, x1)             \
            DRAW_BITMAP_LINE(wr, rd, x0, x1, uint16_t, palette_a15)
#define DRAW_BITMAP_LINE_A15_R(wr, rd, x0, x1)             \
            DRAW_BITMAP_LINE(wr, rd, x0, x1, uint16_t, palette_a15_r)
#define DRAW_BITMAP_LINE_A32(wr, rd, x0, x1)             \
            DRAW_BITMAP_LINE(wr, rd, x0, x1, uint32_t, palette_a32)
#define DRAW_BITMAP_LINE_A32_R(wr, rd, x0, x1)             \
            DRAW_BITMAP_LINE(wr, rd, x0, x1, uint32_t, palette_a32_r)
#define DRAW_BITMAP_LINE_Y(wr, rd, x0, x1)              \
            DRAW_BITMAP_LINE(wr, rd, x0, x1, uint8_t,  palette_y)

#define DRAW_BITMAP_LINE_8_SCALED(wr, rd, x0, x1)       \
            DRAW_BITMAP_LINE_SCALED(wr, rd, x0, x1, uint8_t,  palette_8)
#define DRAW_BITMAP_LINE_12_SCALED(wr, rd, x0, x1)      \
            DRAW_BITMAP_LINE_SCALED(wr, rd, x0, x1, uint16_t, palette_12)
#define DRAW_BITMAP_LINE_12_R_SCALED(wr, rd, x0, x1)      \
            DRAW_BITMAP_LINE_SCALED(wr, rd, x0, x1, uint16_t, palette_12_r)
#define DRAW_BITMAP_LINE_15_SCALED(wr, rd, x0, x1)      \
            DRAW_BITMAP_LINE_SCALED(wr, rd, x0, x1, uint16_t, palette_15)
#define DRAW_BITMAP_LINE_15_R_SCALED(wr, rd, x0, x1)      \
            DRAW_BITMAP_LINE_SCALED(wr, rd, x0, x1, uint16_t, palette_15_r)
#define DRAW_BITMAP_LINE_16_SCALED(wr, rd, x0, x1)      \
            DRAW_BITMAP_LINE_SCALED(wr, rd, x0, x1, uint16_t, palette_16)
#define DRAW_BITMAP_LINE_16_R_SCALED(wr, rd, x0, x1)      \
            DRAW_BITMAP_LINE_SCALED(wr, rd, x0, x1, uint16_t, palette_16_r)
#define DRAW_BITMAP_LINE_32_SCALED(wr, rd, x0, x1)      \
            DRAW_BITMAP_LINE_SCALED(wr, rd, x0, x1, uint32_t, palette_32)
#define DRAW_BITMAP_LINE_32_R_SCALED(wr, rd, x0, x1)      \
            DRAW_BITMAP_LINE_SCALED(wr, rd, x0, x1, uint32_t, palette_32_r)
#define DRAW_BITMAP_LINE_A12_SCALED(wr, rd, x0, x1)      \
            DRAW_BITMAP_LINE_SCALED(wr, rd, x0, x1, uint16_t, palette_a12)
#define DRAW_BITMAP_LINE_A12_R_SCALED(wr, rd, x0, x1)      \
            DRAW_BITMAP_LINE_SCALED(wr, rd, x0, x1, uint16_t, palette_a12_r)
#define DRAW_BITMAP_LINE_A15_SCALED(wr, rd, x0, x1)      \
            DRAW_BITMAP_LINE_SCALED(wr, rd, x0, x1, uint16_t, palette_a15)
#define DRAW_BITMAP_LINE_A15_R_SCALED(wr, rd, x0, x1)      \
            DRAW_BITMAP_LINE_SCALED(wr, rd, x0, x1, uint16_t, palette_a15_r)
#define DRAW_BITMAP_LINE_A32_SCALED(wr, rd, x0, x1)      \
            DRAW_BITMAP_LINE_SCALED(wr, rd, x0, x1, uint32_t, palette_a32)
#define DRAW_BITMAP_LINE_A32_R_SCALED(wr, rd, x0, x1)      \
            DRAW_BITMAP_LINE_SCALED(wr, rd, x0, x1, uint32_t, palette_a32_r)
#define DRAW_BITMAP_LINE_Y_SCALED(wr, rd, x0, x1)       \
            DRAW_BITMAP_LINE_SCALED(wr, rd, x0, x1, uint8_t,  palette_y)

/**
 * Plots a rectangle from the OSD bitmap to the rgb screen or buffer.
 *
 * @param osd
 * @param player
 * @param x0     Horizontal start of the rectangle.
 * @param y0     Vertical start of the rectangle.
 * @param x1     Horizontal end the rectangle.
 * @param y1     Vertical end of the rectangle.
 */
static void draw_bitmap(osd_t* osd, player_t* player, int x0, int y0, int x1, int y1)
{
  int y;
  uint8_t *scr, *scr_u, *scr_v;
  char *bm;

  scr = player->paint.dst;
  scr_u = player->paint.dst_u;
  scr_v = player->paint.dst_v;
  bm = osd->bitmap;

  for (y = y0; y < y1; y++)
  {
    if (!osd->emptyline[y])
    {
      if (osd->zoom > 100)
      {
        switch (player->screen_paint.col_depth)
        {
          case ka_col_depth_256:
            DRAW_BITMAP_LINE_8_SCALED(scr, bm, x0, x1);
            scr += player->paint.dst_bpr;
            DRAW_BITMAP_LINE_8_SCALED(scr, bm, x0, x1);
          break;

          case ka_col_depth_NV12:
          case ka_col_depth_NV21:
          case ka_col_depth_YV12:
          case ka_col_depth_YV16:
            // Y
            DRAW_BITMAP_LINE_Y_SCALED(scr, bm, x0, x1);
            scr += player->paint.dst_bpr;
            DRAW_BITMAP_LINE_Y_SCALED(scr, bm, x0, x1);
          break;
          case ka_col_depth_UYVY:
            // Y
            DRAW_BITMAP_LINEXY_SCALED(scr, bm, x0, x1, 1, 2, palette_y);
            scr += player->paint.dst_bpr;
            DRAW_BITMAP_LINEXY_SCALED(scr, bm, x0, x1, 1, 2, palette_y);
          break;
          case ka_col_depth_YUY2:
            // Y
            DRAW_BITMAP_LINEXY_SCALED(scr, bm, x0, x1, 0, 2, palette_y);
            scr += player->paint.dst_bpr;
            DRAW_BITMAP_LINEXY_SCALED(scr, bm, x0, x1, 0, 2, palette_y);
          break;

          case ka_col_depth_TBGR12:
            DRAW_BITMAP_LINE_12_SCALED(scr, bm, x0, x1);
            scr += player->paint.dst_bpr;
            DRAW_BITMAP_LINE_12_SCALED(scr, bm, x0, x1);
          break;
          case ka_col_depth_TBGR15:
            DRAW_BITMAP_LINE_15_SCALED(scr, bm, x0, x1);
            scr += player->paint.dst_bpr;
            DRAW_BITMAP_LINE_15_SCALED(scr, bm, x0, x1);
          break;
          case ka_col_depth_TBGR16:
            DRAW_BITMAP_LINE_16_SCALED(scr, bm, x0, x1);
            scr += player->paint.dst_bpr;
            DRAW_BITMAP_LINE_16_SCALED(scr, bm, x0, x1);
          break;
          case ka_col_depth_TBGR32:
            DRAW_BITMAP_LINE_32_SCALED(scr, bm, x0, x1);
            scr += player->paint.dst_bpr;
            DRAW_BITMAP_LINE_32_SCALED(scr, bm, x0, x1);
          break;

          case ka_col_depth_TRGB12:
            DRAW_BITMAP_LINE_12_R_SCALED(scr, bm, x0, x1);
            scr += player->paint.dst_bpr;
            DRAW_BITMAP_LINE_12_R_SCALED(scr, bm, x0, x1);
          break;
          case ka_col_depth_TRGB15:
            DRAW_BITMAP_LINE_15_R_SCALED(scr, bm, x0, x1);
            scr += player->paint.dst_bpr;
            DRAW_BITMAP_LINE_15_R_SCALED(scr, bm, x0, x1);
          break;
          case ka_col_depth_TRGB16:
            DRAW_BITMAP_LINE_16_R_SCALED(scr, bm, x0, x1);
            scr += player->paint.dst_bpr;
            DRAW_BITMAP_LINE_16_R_SCALED(scr, bm, x0, x1);
          break;
          case ka_col_depth_TRGB32:
            DRAW_BITMAP_LINE_32_R_SCALED(scr, bm, x0, x1);
            scr += player->paint.dst_bpr;
            DRAW_BITMAP_LINE_32_R_SCALED(scr, bm, x0, x1);
          break;

          case ka_col_depth_ABGR12:
            DRAW_BITMAP_LINE_A12_SCALED(scr, bm, x0, x1);
            scr += player->paint.dst_bpr;
            DRAW_BITMAP_LINE_A12_SCALED(scr, bm, x0, x1);
          break;
          case ka_col_depth_ABGR15:
            DRAW_BITMAP_LINE_A15_SCALED(scr, bm, x0, x1);
            scr += player->paint.dst_bpr;
            DRAW_BITMAP_LINE_A15_SCALED(scr, bm, x0, x1);
          break;
          case ka_col_depth_ABGR16:
            DRAW_BITMAP_LINE_16_SCALED(scr, bm, x0, x1);
            scr += player->paint.dst_bpr;
            DRAW_BITMAP_LINE_16_SCALED(scr, bm, x0, x1);
          break;
          case ka_col_depth_ABGR32:
            DRAW_BITMAP_LINE_A32_SCALED(scr, bm, x0, x1);
            scr += player->paint.dst_bpr;
            DRAW_BITMAP_LINE_A32_SCALED(scr, bm, x0, x1);
          break;
          case ka_col_depth_ARGB12:
            DRAW_BITMAP_LINE_A12_R_SCALED(scr, bm, x0, x1);
            scr += player->paint.dst_bpr;
            DRAW_BITMAP_LINE_A12_R_SCALED(scr, bm, x0, x1);
          break;
          case ka_col_depth_ARGB15:
            DRAW_BITMAP_LINE_A15_R_SCALED(scr, bm, x0, x1);
            scr += player->paint.dst_bpr;
            DRAW_BITMAP_LINE_A15_R_SCALED(scr, bm, x0, x1);
          break;
          case ka_col_depth_ARGB16:
            DRAW_BITMAP_LINE_16_R_SCALED(scr, bm, x0, x1);
            scr += player->paint.dst_bpr;
            DRAW_BITMAP_LINE_16_R_SCALED(scr, bm, x0, x1);
          break;
          case ka_col_depth_ARGB32:
            DRAW_BITMAP_LINE_A32_R_SCALED(scr, bm, x0, x1);
            scr += player->paint.dst_bpr;
            DRAW_BITMAP_LINE_A32_R_SCALED(scr, bm, x0, x1);
          break;
        }
      }
      else
      {
        switch (player->screen_paint.col_depth)
        {
          case ka_col_depth_256:
            DRAW_BITMAP_LINE_8(scr, bm, x0, x1);
          break;
          case ka_col_depth_NV12:
          case ka_col_depth_NV21:
          case ka_col_depth_YV12:
          case ka_col_depth_YV16:
            // Y
            DRAW_BITMAP_LINE_Y(scr, bm, x0, x1);
          break;
          case ka_col_depth_UYVY:
            // Y
            DRAW_BITMAP_LINEXY(scr, bm, x0, x1, 1, 2, palette_y);
          break;
          case ka_col_depth_YUY2:
            // Y
            DRAW_BITMAP_LINEXY(scr, bm, x0, x1, 0, 2, palette_y);
          break;

          case ka_col_depth_TBGR12: DRAW_BITMAP_LINE_12(scr, bm, x0, x1);  break;
          case ka_col_depth_TBGR15: DRAW_BITMAP_LINE_15(scr, bm, x0, x1);  break;
          case ka_col_depth_TBGR16: DRAW_BITMAP_LINE_16(scr, bm, x0, x1);  break;
          case ka_col_depth_TBGR32: DRAW_BITMAP_LINE_32(scr, bm, x0, x1);  break;
          case ka_col_depth_TRGB12: DRAW_BITMAP_LINE_12_R(scr, bm, x0, x1);  break;
          case ka_col_depth_TRGB15: DRAW_BITMAP_LINE_15_R(scr, bm, x0, x1);  break;
          case ka_col_depth_TRGB16: DRAW_BITMAP_LINE_16_R(scr, bm, x0, x1);  break;
          case ka_col_depth_TRGB32: DRAW_BITMAP_LINE_32_R(scr, bm, x0, x1);  break;

          case ka_col_depth_ABGR12: DRAW_BITMAP_LINE_A12(scr, bm, x0, x1);  break;
          case ka_col_depth_ABGR15: DRAW_BITMAP_LINE_A15(scr, bm, x0, x1);  break;
          case ka_col_depth_ABGR16: DRAW_BITMAP_LINE_16(scr, bm, x0, x1);  break;
          case ka_col_depth_ABGR32: DRAW_BITMAP_LINE_A32(scr, bm, x0, x1);  break;
          case ka_col_depth_ARGB12: DRAW_BITMAP_LINE_A12_R(scr, bm, x0, x1);  break;
          case ka_col_depth_ARGB15: DRAW_BITMAP_LINE_A15_R(scr, bm, x0, x1);  break;
          case ka_col_depth_ARGB16: DRAW_BITMAP_LINE_16_R(scr, bm, x0, x1);  break;
          case ka_col_depth_ARGB32: DRAW_BITMAP_LINE_A32_R(scr, bm, x0, x1);  break;
        }
      }
    }
    else if (osd->zoom > 100)
    {
      scr += player->paint.dst_bpr;
      scr_u += player->paint.dst_u_bpr;
      scr_v += player->paint.dst_v_bpr;
    }

    scr += player->paint.dst_bpr;
    scr_u += player->paint.dst_u_bpr;
    scr_v += player->paint.dst_v_bpr;
    bm += osd->sizex;
  }
}

/**
 * Mark a rectangle in the OSD bitmap as update and in need to redraw.
 *
 * @param osd
 * @param x0     Horizontal start of the rectangle.
 * @param y0     Vertical start of the rectangle.
 * @param x1     Horizontal end the rectangle.
 * @param y1     Vertical end of the rectangle.
 */
static void add_changed_area(osd_t* osd, int x0, int y0, int x1, int y1)
{
  if (osd->changed[0] == CHANGED_AREA_EMPTY)
  {
    osd->changed[0] = x0;
    osd->changed[1] = y0;
    osd->changed[2] = x1;
    osd->changed[3] = y1;
  }
  else
  {
    if (x0 < osd->changed[0])    osd->changed[0] = x0;
    if (y0 < osd->changed[1])    osd->changed[1] = y0;
    if (x1 > osd->changed[2])    osd->changed[2] = x1;
    if (y1 > osd->changed[3])    osd->changed[3] = y1;
  }
}

/**
 * Fills a rectangle in the OSD bitmap.
 *
 * @param osd
 * @param x0     Horizontal starting position of the rectangle.
 * @param y0     Vertical starting position of the rectangle.
 * @param w      Width of the rectangle.
 * @param h      Height of the rectangle.
 * @param colour Colour to give to the rectangle.
 */
static void osd_drawRectangle(osd_t* osd, int x0, int y0, int w, int h, osd_colour colour)
{
  int y;

  y0 = osd->sizey - y0 - h;
  if (x0 < 0)              w += x0, x0 = 0;
  if (y0 < 0)              h += y0, y0 = 0;
  if (x0+w > osd->sizex)   w = osd->sizex - x0;
  if (y0+h > osd->sizey)   h = osd->sizey - y0;
  if (w <= 0 || h <= 0)    return;

  for (y = y0; y < y0+h; y++)
  {
    memset(osd->bitmap + y*osd->sizex + x0, colour, w);
    osd->emptyline[y] = 0;
  }
  add_changed_area(osd, x0, y0, x0+w, y0+h);
}

/*
static void osd_getStringSize(const char *text, int *w, int *h)
{
  unsigned int i, width;

  width = 0;
  for (i = 0; i < strlen(text); i++)
  {
    int min, max;
    get_char_min_max_x(text[i], &min, &max);
    width += max-min + 2;
  }
  *w = width;
  *h = FONTSIZE_Y;
}
*/

/**
 * Push a single character in the OSD bitmap.
 *
 * @param osd
 * @param c       Character to display.
 * @param x       Horizontal starting position in the bitmap.
 * @param y       Vertical starting position in the bitmap.
 * @param colour  Colour to give to the text.
 */
static void draw_char(osd_t* osd, unsigned char c, int x, int y, osd_colour colour)
{
  const unsigned short *glyph;
  int px, py;

  y = osd->sizey -y - FONTSIZE_Y;
  glyph = font12x24[c];
  for (py = 0; py < FONTSIZE_Y; py++)
  {
    int yy;

    yy = y + py;

    if (yy >= 0 && yy < osd->sizey)
    {
      unsigned short mask;
      char *bm;

      bm = osd->bitmap + yy*osd->sizex;
      osd->emptyline[yy] = 0;

      mask = glyph[py];
      for (px = 0; px < FONTSIZE_X; px++)
        if (x+px >= 0 && x+px < osd->sizex)
          if (mask & (1<<px))
            bm[x+px] = colour;
    }
  }
}

/**
 * Push some text in the OSD bitmap.
 *
 * @param osd
 * @param text    Text to display.
 * @param x       Horizontal starting position in the bitmap.
 * @param y       Vertical starting position in the bitmap.
 * @param colour  Colour to give to the text.
 */
static void osd_drawText(osd_t* osd, const char *text, int x, int y, osd_colour colour)
{
  int i;

  for (i = 0; text[i]; i++)
  {
    int min, max;
    get_char_min_max_x(text[i], &min, &max);
    add_changed_area(osd, x-min, y, x-min+(max-min), y+FONTSIZE_Y);
    draw_char(osd, text[i], x-min, y, colour);
    x += max-min+2;
  }
}

/*
static void osd_drawTextRightAligned(osd_t* osd, const char *text, int x, int y, osd_colour colour)
{
  int w, h;

  osd_getStringSize(text, &w, &h);
  osd_drawText(osd, text, x-w, y, colour);
}
*/

/**
 * Initialises the OSD to the player's parameters.
 *
 * @param osd
 * @param player
 */
static int osd_enable(osd_t* osd, player_t* player)
{
  int n = 0, i;

  if (osd->bitmap) return 0;
  if (!player->paint.dst_pix_width) return 1;

  // use dst sizes so osd is visible when the picture is larger than the screen
  osd->sizex = player->paint.dst_pix_width;
  osd->sizey = player->paint.dst_height;

  // to use the scaled display above 100% zoom
  if (osd->sizex >= 960)
  {
    osd->sizex >>= 1;
    osd->sizey >>= 1;
    osd->zoom = 200;
  }
  else osd->zoom = 100;

  osd->bitmap = ka_mem_alloc(osd->sizex*osd->sizey + osd->sizey);
  if (!osd->bitmap) return 1;
  osd->emptyline = osd->bitmap + osd->sizex*osd->sizey;

  memset(osd->bitmap, OSD_COLOUR_TRANSPARENT, osd->sizex*osd->sizey);
  for (i = 0; i < osd->sizey; i++)
    osd->emptyline[i] = 1;
  osd->changed[0] = 0;
  osd->changed[1] = 0;
  osd->changed[2] = osd->sizex;
  osd->changed[3] = osd->sizey;

  if (player->screen_paint.col_depth == ka_col_depth_256)
  {
    int i;
    for (i = 0; i < 8; i++)
    {
      _kernel_swi_regs regs;

      regs.r[0] = palette_32[i] << 8;
      _kernel_swi(ColourTrans_ReturnColourNumber, &regs, &regs);
      palette_8[i] = regs.r[0];
    }
  }
  osd->changed[0] = CHANGED_AREA_EMPTY;

  osd->osditems[n++] = &osditem_empty;
  osd->osditems[n++] = &osditem_brightness;
  osd->osditems[n++] = &osditem_contrast;
  if (!(config.video.modes[player->mode].cfg & cfg_video_mono))
    osd->osditems[n++] = &osditem_colour;
  if (config.audio.cfg & cfg_audio_play)
    osd->osditems[n++] = &osditem_volume;
  osd->osditems[n++] = NULL;
  if (osd->current >= (n - 1))
    osd->current = 0;

  return 0;
}

/**
 * Loose the OSD content and parameters.
 *
 * @param osd
 * @param player
 */
static void osd_disable(osd_t* osd)
{
  if (osd->bitmap)
  {
    ka_mem_free(osd->bitmap);
    osd->bitmap = NULL;
    osd->emptyline = NULL;
  }
}

#define CV           osd->osditems[osd->current]
#define WIDTH        width*(*CV->val)/(CV->max-CV->min)
#define DRAWMARKER   if (CV->nul >= CV->min)                                    \
                        osd_drawRectangle(osd, 9+width*(CV->nul)/(CV->max-CV->min), \
                                         18, 2, 12, OSD_COLOUR_RED)

/**
 * Updates parts of the OSD.
 */
static void osd_update(player_t* player, uint32_t update)
{
  int width, height;
  osd_t* osd = player->osd;

  if (!osd || !osd->bitmap) return;

  width = osd->sizex - 30;
  height = osd->sizey;

  // Property title and value
  if (update & OSD_STATE_NAV)
  {
    // Hide
    osd_drawRectangle(osd, 0, 18, osd->sizex, 34, OSD_COLOUR_TRANSPARENT);

    // Draw
    if (osd->current)
    {
      osd_drawText(osd, CV->title, 10, 32, OSD_COLOUR_RED);
      if (CV->diff)
      {
        osd_drawRectangle(osd, 10, 20, WIDTH, 8, OSD_COLOUR_RED);
        DRAWMARKER;
      }
      else if (CV->set)
        CV->set(player, (int)CV->nul);
    }
  }

  // Update property value
  if (update & OSD_UPDATE_VAL)
  {
    int w = WIDTH;
    osd_drawRectangle(osd, 10, 20, w, 8, OSD_COLOUR_RED);
    osd_drawRectangle(osd, 10+w, 20, width-w+10, 8, OSD_COLOUR_TRANSPARENT);
    DRAWMARKER;
  }

  // Update message
  if (update & OSD_STATE_MSG)
  {
    osd_drawRectangle(osd, 0, osd->sizey - FONTSIZE_Y, osd->sizex, FONTSIZE_Y, OSD_COLOUR_TRANSPARENT);

    if (osd->state & OSD_STATE_MSG)
      osd_drawText(osd, osd->msg, 10, osd->sizey - FONTSIZE_Y, OSD_COLOUR_RED);
  }
  // Update timecode
  else if (update & OSD_STATE_TC)
  {
    osd_drawRectangle(osd, 0, osd->sizey - FONTSIZE_Y, osd->sizex, FONTSIZE_Y, OSD_COLOUR_TRANSPARENT);

    if (osd->state & OSD_STATE_TC)
    {
      char tc[12];
      uint32_t rtc = timer_getrtc();

      snprintf(tc, sizeof(tc), "%02d:%02d:%02d", rtc/(timer_getrate()*60*60),
                                    (rtc/(timer_getrate()*60))%60,
                                    (rtc/timer_getrate())%60);
      osd_drawText(osd, tc, 10, osd->sizey - FONTSIZE_Y, OSD_COLOUR_GREEN);
      if (player->curr_frame != NULL)
      {
        snprintf(tc, sizeof(tc), "%05d", player->draw.picturenr);
        osd_drawText(osd, tc, 90, osd->sizey - FONTSIZE_Y, OSD_COLOUR_CYAN);
        static const char* stype[3] = {"I", "P", "B"};
        osd_drawText(osd, stype[player->draw.frame.type], 140, osd->sizey - FONTSIZE_Y, OSD_COLOUR_GREEN);
        snprintf(tc, sizeof(tc), "%02d Fps"
          , (player->time.fps_delta > 0)
            ? (timer_getrate() + (player->time.fps_delta >> 1))/player->time.fps_delta
            : 0);
        osd_drawText(osd, tc, osd->sizex-70, osd->sizey - FONTSIZE_Y, OSD_COLOUR_GREEN);
      }
    }
  }

}

/**
 * Creates a new OSD an attaches it to the player.
 *
 * @param player Player.
 */
void osd_attach(player_t* player)
{
  if (player->osd) return;

  player->osd = ka_mem_calloc(sizeof(*player->osd));
}

/**
 * Releases the OSD and all its memory.
 *
 * @param player Player.
 */
void osd_detach(player_t* player)
{
  if (player->osd)
  {
    osd_disable(player->osd);
    ka_mem_free(player->osd);
    player->osd = NULL;
  }
}

void osd_reset(player_t* player)
{
  osd_t* osd = player->osd;

  if (!osd) return;

  osd->state &= ~OSD_STATE_MSG;
  osd_update(player, OSD_STATE_MSG);
}

/**
 * Forces a redraw for time dependent info.
 */
void osd_refresh(player_t* player)
{
  osd_t* osd = player->osd;

  if (!osd) return;

  uint32_t time = timer_gettime();

  if ((osd->state & OSD_STATE_MSG)
  &&  (((int32_t)(osd->msg_timer - time)) < 0))
  {
    player->status_flags |= player_status_redrawframe;
  }

  if ((osd_getstate(player) & OSD_STATE_TC)
  &&  (((int32_t)(osd->draw_timer - time)) < 0))
  {
    player->status_flags |= player_status_redrawframe;
  }
}

/**
 * Plots the OSD bitmap to rgb screen or buffer.
 *
 * @param player Player.
 */
void osd_draw(player_t* player)
{
  osd_t* osd = player->osd;

  if (!osd) return;

  uint32_t time = timer_gettime();
  osd->draw_timer = time + timer_getrate();

  if ((osd->state & OSD_STATE_MSG)
  &&  (((int32_t)(osd->msg_timer - time)) < 0))
  {
    osd->state &= ~OSD_STATE_MSG;
    if (osd->state)
    {
      osd_enable(osd, player);
      if (!osd->bitmap) return;
      osd_update(player, OSD_STATE_MSG);
    }
    else
      osd_disable(osd);
  }

  if (osd_getstate(player) & OSD_STATE_TC)
    osd_update(player, OSD_STATE_TC);

  if (!osd->bitmap) return;

  draw_bitmap(osd, player, 0, 0, osd->sizex, osd->sizey);
}

/**
 * Returns the current state of the OSD.
 *
 * @param player Player.
 */
uint32_t osd_getstate(player_t* player)
{
  osd_t* osd = player->osd;

  if (!osd || !osd->bitmap) return 0;

  return osd->state;
}

/**
 * Handles OSD display and navigation commands.
 *
 * @param player Player.
 * @param nav    Navigation command.
 */
void osd_navigate(player_t* player, osd_nav nav)
{
  osd_t* osd = player->osd;
  uint32_t update = 0;

  if (!osd) return;

  // Updates that will force a full display refresh
  switch (nav)
  {
    case OSD_NAV_PAUSE_ON:
    {
      if (!(osd->state & OSD_STATE_NAV))
      {
        osd->state |= OSD_STATE_NAV;
        osd->current = 0;
        update = OSD_STATE_NAV;
      }
    }
    break;
    case OSD_NAV_PAUSE_OFF:
    {
      if (osd->state & OSD_STATE_NAV)
      {
        osd->state &= ~OSD_STATE_NAV;
        osd->current = 0;
        update = OSD_STATE_NAV;
      }
    }
    break;
    case OSD_NAV_TIME_ON:
    {
      if (!(osd->state & OSD_STATE_TC))
      {
        osd->state |= OSD_STATE_TC;
        update = OSD_STATE_TC;
      }
    }
    break;
    case OSD_NAV_TIME_OFF:
    {
      if (osd->state & OSD_STATE_TC)
      {
        osd->state &= ~OSD_STATE_TC;
        update = OSD_STATE_TC;
      }
    }
    break;
    case OSD_NAV_UPDATE:
    {
      if (!osd->state)
        return;

      // Disable current version which may be of incorrect size
      osd_disable(osd);
    }
    break;
    case OSD_NAV_UP:
    {
      if (osd->state & OSD_STATE_NAV)
      {
        osd->current++;
        if (!osd->osditems[osd->current]) osd->current = 0;
        update = OSD_STATE_NAV;
      }
    }
    break;
    case OSD_NAV_DOWN:
    {
      if (osd->state & OSD_STATE_NAV)
      {
        osd->current--;
        if (osd->current < 0)
        {
          osd->current = 0;
          while (osd->osditems[osd->current+1])
            osd->current++;
        }
        update = OSD_STATE_NAV;
      }
    }
    break;
    case OSD_NAV_LEFT:
    {
      if ((osd->state & OSD_STATE_NAV)
      &&  osd->current
      &&  CV->diff)
      {
        *CV->val = *CV->val - CV->diff;
        if (*CV->val < CV->min)    *CV->val = CV->min;
        if (*CV->val > CV->max)    *CV->val = CV->max;
        if (CV->set)   CV->set(player, *CV->val);

        update = OSD_UPDATE_VAL;
      }
    }
    break;
    case OSD_NAV_RIGHT:
    {
      if ((osd->state & OSD_STATE_NAV)
      &&  osd->current
      &&  CV->diff)
      {
        *CV->val = *CV->val + CV->diff;
        if (*CV->val < CV->min)    *CV->val = CV->min;
        if (*CV->val > CV->max)    *CV->val = CV->max;
        if (CV->set)   CV->set(player, *CV->val);

        update = OSD_UPDATE_VAL;
      }
    }
    break;
    case OSD_NAV_FRAME_STEP:
    {
      if (osd->state & OSD_STATE_NAV)
      {
        update = OSD_STATE_TC;
      }
    }
    break;
  }

  if (osd->state)
  {
    if (!osd->bitmap)
    {
      osd_enable(osd, player);
      if (!osd->bitmap) return;
      update = osd->state;
      if ((osd->state & OSD_STATE_NAV)
      &&  !osd->current)
        update &= ~OSD_STATE_NAV;
    }
  }
  else
  {
    if (osd->bitmap)
    {
      osd_disable(osd);
    }
  }

  if (osd->state) osd_update(player, update);
  if (update)
    player->status_flags |= player_status_redrawframe;
}

/**
 * Fills a message in a string.
 *
 * @param  player  Player.
 * @param  format  printf like format.
 * @param  ...     Additional parameters, format dependant.
 */
void osd_report(player_t* player, const char *format, ...)
{
  va_list arg;

  va_start(arg, format);
  osd_vreport(player, format, arg);
  va_end(arg);
}

void osd_vreport(player_t* player, const char *format, va_list arg)
{
  osd_t* osd = player->osd;

  if (!osd) return;

  if (format != NULL)
  {
    msg_lookup(format, -1, player->error_block.errmess, sizeof(player->error_block.errmess));
    vsnprintf(osd->msg, sizeof(osd->msg), player->error_block.errmess, arg);
    snprintf(player->error_block.errmess, sizeof(player->error_block.errmess), "%s", osd->msg);
  }
  else
  {
    snprintf(osd->msg, sizeof(osd->msg), "%s", player->error_block.errmess);
  }

  if (!osd->bitmap)
  {
    osd_enable(osd, player);
    if (!osd->bitmap) return;
  }

  // Show new value
  osd->msg_timer = timer_gettime() + 7*timer_getrate();
  osd->state |= OSD_STATE_MSG;
  osd_update(player, OSD_STATE_MSG);

  player->status_flags |= player_status_redrawframe;
}
