/*
 * Copyright (C) 2000-2022 the xine project
 *
 * This file is part of xine, a unix video player.
 *
 * xine is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * xine is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA
 *
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

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

#include "lang.h"

#include "common.h"
#include "osd.h"
#include "videowin.h"
#include "actions.h"

#define OVL_PALETTE_SIZE 256

#ifdef	__GNUC__
#define CLUT_Y_CR_CB_INIT(_y,_cr,_cb)	{{ .y = (_y), .cr = (_cr), .cb = (_cb)}}
#else
#define CLUT_Y_CR_CB_INIT(_y,_cr,_cb)	{{ (_cb), (_cr), (_y) }}
#endif

static const union {         /* CLUT == Color LookUp Table */
  struct {
    uint8_t cb;
    uint8_t cr;
    uint8_t y;
    uint8_t foo;
  } u8;
  uint32_t u32;
} textpalettes_color[OVL_PALETTE_SIZE] = {
  /* white, no border, translucid */
    CLUT_Y_CR_CB_INIT(0x00, 0x00, 0x00), //0
    CLUT_Y_CR_CB_INIT(0x60, 0x80, 0x80), //1
    CLUT_Y_CR_CB_INIT(0x70, 0x80, 0x80), //2
    CLUT_Y_CR_CB_INIT(0x80, 0x80, 0x80), //3
    CLUT_Y_CR_CB_INIT(0x80, 0x80, 0x80), //4
    CLUT_Y_CR_CB_INIT(0x80, 0x80, 0x80), //5
    CLUT_Y_CR_CB_INIT(0x80, 0x80, 0x80), //6
    CLUT_Y_CR_CB_INIT(0xa0, 0x80, 0x80), //7
    CLUT_Y_CR_CB_INIT(0xc0, 0x80, 0x80), //8
    CLUT_Y_CR_CB_INIT(0xe0, 0x80, 0x80), //9
    CLUT_Y_CR_CB_INIT(0xff, 0x80, 0x80), //10
  /* yellow, black border, transparent */
    CLUT_Y_CR_CB_INIT(0x00, 0x00, 0x00), //0
    CLUT_Y_CR_CB_INIT(0x80, 0x80, 0xe0), //1
    CLUT_Y_CR_CB_INIT(0x80, 0x80, 0xc0), //2
    CLUT_Y_CR_CB_INIT(0x60, 0x80, 0xa0), //3
    CLUT_Y_CR_CB_INIT(0x40, 0x80, 0x80), //4
    CLUT_Y_CR_CB_INIT(0x20, 0x80, 0x80), //5
    CLUT_Y_CR_CB_INIT(0x00, 0x80, 0x80), //6
    CLUT_Y_CR_CB_INIT(0x40, 0x84, 0x60), //7
    CLUT_Y_CR_CB_INIT(0xd0, 0x88, 0x40), //8
    CLUT_Y_CR_CB_INIT(0xe0, 0x8a, 0x00), //9
    CLUT_Y_CR_CB_INIT(0xff, 0x90, 0x00), //10
};

#ifdef DEBUG
/* check textpalettes_color is properly packed */
typedef char _xitk_static_assertion_testpalettes_size[2*(!!(sizeof(textpalettes_color) == sizeof(uint32_t)*OVL_PALETTE_SIZE))-1];
#endif

static const uint8_t textpalettes_trans[OVL_PALETTE_SIZE] = {
  /* white, no border, translucid */
  0, 8, 9, 10, 11, 12, 13, 14, 15, 15, 15,
  /* yellow, black border, transparent */
  0, 8, 9, 10, 11, 12, 13, 14, 15, 15, 15,
};

#define BAR_WIDTH 336
#define BAR_HEIGHT 25

#define MINIMUM_WIN_WIDTH  300
#define FONT_SIZE          20
#define UNSCALED_FONT_SIZE 24

static void _xine_osd_show (gGui_t *gui, xine_osd_t *osd, int64_t vpts) {
  if (gui->osd.use_unscaled && gui->osd.unscaled_available)
    xine_osd_show_unscaled(osd, vpts);
  else
    xine_osd_show(osd, vpts);
}

static void _osd_obj_hide_all (gGui_t *gui) {
  while (1) {
    struct xui_osd_obj_s *obj = (struct xui_osd_obj_s *)gui->osd.pending.head.next;

    if (!obj->node.next)
      break;
    xitk_dnode_remove (&obj->node);
    xine_osd_hide (obj->obj.osd[0], 0);
    if (obj->obj.osd[1] && obj->obj.have_text)
      xine_osd_hide (obj->obj.osd[1], 0);
    obj->obj.visible = 0;
  }
}

/** chain objects pending for timeout hide by relative time,
 *  + 1 to make sure obj.visible is always > 0. */
static void _osd_obj_tick (gGui_t *gui) {
  int tick = -1;

  while (1) {
    struct xui_osd_obj_s *obj = (struct xui_osd_obj_s *)gui->osd.pending.head.next;

    if (!obj->node.next)
      break;
    if ((obj->obj.visible += tick) > 1)
      break;
    tick = 0;
    xitk_dnode_remove (&obj->node);
    xine_osd_hide (obj->obj.osd[0], 0);
    if (obj->obj.osd[1] && obj->obj.have_text)
      xine_osd_hide (obj->obj.osd[1], 0);
    obj->obj.visible = 0;
  }
}

static void _osd_obj_set (gGui_t *gui, struct xui_osd_obj_s *obj, int timeout) {
  struct xui_osd_obj_s *o1;

  if (obj->node.next) {
    o1 = (struct xui_osd_obj_s *)obj->node.next;
    if (o1->node.next)
      o1->obj.visible += obj->obj.visible - 1;
    xitk_dnode_remove (&obj->node);
    xine_osd_hide (obj->obj.osd[0], 0);
    if (obj->obj.osd[1] && obj->obj.have_text)
      xine_osd_hide (obj->obj.osd[1], 0);
    obj->obj.visible = 0;
  }

  if (timeout > 0) {
    for (o1 = (struct xui_osd_obj_s *)gui->osd.pending.head.next; o1->node.next; o1 = (struct xui_osd_obj_s *)o1->node.next) {
      int tt = timeout;
      timeout -= o1->obj.visible - 1;
      if (timeout <= 0) {
        o1->obj.visible -= tt;
        timeout = tt;
        break;
      }
    }
    o1 = (struct xui_osd_obj_s *)o1->node.prev;
    obj->obj.visible = timeout + 1;
    xitk_dnode_insert_after (&o1->node, &obj->node);
    _xine_osd_show (gui, obj->obj.osd[0], 0);
    if (obj->obj.osd[1] && obj->obj.have_text)
      _xine_osd_show (gui, obj->obj.osd[1], 0);
  }
}

static void _osd_get_output_size (gGui_t *gui, int *w, int *h) {
  if (gui->osd.use_unscaled && gui->osd.unscaled_available)
    video_window_get_output_size (gui->vwin, w, h);
  else
    video_window_get_frame_size (gui->vwin, w, h);
}

static void _osd_get_speed_sym (char **q, int speed) {
  if (speed < XINE_FINE_SPEED_NORMAL * 71 / 100) {
    if (speed <= 0) {
      memcpy (*q, "<", 2); *q += 1; /* XINE_SPEED_PAUSE */
    } else if (speed < XINE_FINE_SPEED_NORMAL * 35 / 100) {
      memcpy (*q, "@@>", 4); *q += 3; /* XINE_SPEED_SLOW_4 */
    } else {
      memcpy (*q, "@>", 3); *q += 2; /* XINE_SPEED_SLOW_2 */
    }
  } else {
    if (speed < XINE_FINE_SPEED_NORMAL * 141 / 100) {
      memcpy (*q, ">", 2); *q += 1; /* XINE_SPEED_NORMAL */
    } else if (speed < XINE_FINE_SPEED_NORMAL * 282 / 100) {
      memcpy (*q, ">$", 3); *q += 2; /* XINE_SPEED_FAST_2 */
    } else {
      memcpy (*q, ">$$", 4), *q += 3; /* XINE_SPEED_FAST_4 */
    }
  }
}

static void _osd_get_status_sym (char **q, int status) {
  if (status == XINE_STATUS_STOP) {
    memcpy (*q, "}", 2); *q += 1;
  }
  /*
  else if (status == XINE_STATUS_PLAY) {
    memcpy (*q, ">", 2); *q += 1;
  }
  */
  else if (status == XINE_STATUS_QUIT) {
    memcpy (*q, "{", 2); *q += 1;
  }
};

void osd_init (gGui_t *gui) {
  int fonth = FONT_SIZE;

  xitk_dlist_init (&gui->osd.pending);

  gui->osd.sinfo.node.next = gui->osd.sinfo.node.prev = NULL;
  gui->osd.sinfo.obj.osd[0] = xine_osd_new (gui->stream, 0, 0, 900, (fonth * 6) + (5 * 3));
  xine_osd_set_font (gui->osd.sinfo.obj.osd[0], "sans", fonth);
  xine_osd_set_text_palette (gui->osd.sinfo.obj.osd[0], XINE_TEXTPALETTE_WHITE_BLACK_TRANSPARENT, XINE_OSD_TEXT1);
  gui->osd.sinfo.obj.osd[1] = NULL;

  gui->osd.bar.node.next = gui->osd.bar.node.prev = NULL;
  gui->osd.bar.obj.osd[0] = xine_osd_new (gui->stream, 0, 0, BAR_WIDTH + 1, BAR_HEIGHT + 1);
  xine_osd_set_palette (gui->osd.bar.obj.osd[0], &textpalettes_color[0].u32, textpalettes_trans);
  gui->osd.bar.obj.osd[1] = xine_osd_new (gui->stream, 0, 0, BAR_WIDTH + 1, BAR_HEIGHT + 1);
  xine_osd_set_font (gui->osd.bar.obj.osd[1], "sans", fonth);
  xine_osd_set_text_palette (gui->osd.bar.obj.osd[1], XINE_TEXTPALETTE_WHITE_BLACK_TRANSPARENT, XINE_OSD_TEXT1);

  gui->osd.status.node.next = gui->osd.status.node.prev = NULL;
  gui->osd.status.obj.osd[0] = xine_osd_new (gui->stream, 0, 0, 300, 2 * fonth);
  xine_osd_set_text_palette (gui->osd.status.obj.osd[0], XINE_TEXTPALETTE_WHITE_BLACK_TRANSPARENT, XINE_OSD_TEXT1);
  gui->osd.status.obj.osd[1] = NULL;

  gui->osd.info.node.next = gui->osd.info.node.prev = NULL;
  gui->osd.info.obj.osd[0] = xine_osd_new (gui->stream, 0, 0, 2048, fonth + (fonth >> 1));
  xine_osd_set_font (gui->osd.info.obj.osd[0], "sans", fonth);
  xine_osd_set_text_palette (gui->osd.info.obj.osd[0], XINE_TEXTPALETTE_WHITE_BLACK_TRANSPARENT, XINE_OSD_TEXT1);
  gui->osd.info.obj.osd[1] = NULL;

  gui->osd.unscaled_available = xine_osd_get_capabilities (gui->osd.status.obj.osd[0]) & XINE_OSD_CAP_UNSCALED;

  pthread_mutex_init (&gui->osd.mutex, NULL);
}

void osd_hide_sinfo (gGui_t *gui) {
  pthread_mutex_lock (&gui->osd.mutex);
  _osd_obj_set (gui, &gui->osd.sinfo, 0);
  pthread_mutex_unlock (&gui->osd.mutex);
}

void osd_hide_bar (gGui_t *gui) {
  pthread_mutex_lock (&gui->osd.mutex);
  _osd_obj_set (gui, &gui->osd.bar, 0);
  pthread_mutex_unlock (&gui->osd.mutex);
}

void osd_hide_status (gGui_t *gui) {
  pthread_mutex_lock (&gui->osd.mutex);
  _osd_obj_set (gui, &gui->osd.status, 0);
  pthread_mutex_unlock (&gui->osd.mutex);
}

void osd_hide_info (gGui_t *gui) {
  pthread_mutex_lock (&gui->osd.mutex);
  _osd_obj_set (gui, &gui->osd.info, 0);
  pthread_mutex_unlock (&gui->osd.mutex);
}

void osd_hide (gGui_t *gui) {
  pthread_mutex_lock (&gui->osd.mutex);
  _osd_obj_hide_all (gui);
  pthread_mutex_unlock (&gui->osd.mutex);
}

void osd_deinit (gGui_t *gui) {
  osd_hide (gui);

  xine_osd_free (gui->osd.sinfo.obj.osd[0]);
  xine_osd_free (gui->osd.bar.obj.osd[0]);
  xine_osd_free (gui->osd.bar.obj.osd[1]);
  xine_osd_free (gui->osd.status.obj.osd[0]);
  xine_osd_free (gui->osd.info.obj.osd[0]);

  pthread_mutex_destroy (&gui->osd.mutex);
}

void osd_update (gGui_t *gui) {
  pthread_mutex_lock (&gui->osd.mutex);
  _osd_obj_tick (gui);
  pthread_mutex_unlock (&gui->osd.mutex);
}

void osd_stream_infos (gGui_t *gui) {
  if (gui->osd.enabled) {
    int         vwidth, vheight, asrate;
    int         wwidth, wheight;
    const char *vcodec, *acodec;
    char        buffer[512], *p, *e = buffer + sizeof (buffer);
    int         x, y;
    int         w, h, osdw;
    int         playedtime, playeddays, totaltime, pos;
    int         audiochannel, spuchannel;
#ifdef XINE_QUERY_STREAM_INFO
    char sbuf[512];
    int strings[3] = {XINE_META_INFO_VIDEOCODEC, XINE_META_INFO_AUDIOCODEC, -1};
    int ints[4] = { XINE_STREAM_INFO_VIDEO_WIDTH, XINE_STREAM_INFO_VIDEO_HEIGHT,
      XINE_STREAM_INFO_AUDIO_SAMPLERATE, -1};

    xine_query_stream_info (gui->stream, sbuf, sizeof (sbuf), strings, ints);
    vcodec = strings[0] ? sbuf + strings[0] : NULL;
    acodec = strings[1] ? sbuf + strings[1] : NULL;
    vwidth  = ints[0];
    vheight = ints[1];
    asrate  = ints[2];
#else
    vcodec       = xine_get_meta_info (gui->stream, XINE_META_INFO_VIDEOCODEC);
    acodec       = xine_get_meta_info (gui->stream, XINE_META_INFO_AUDIOCODEC);
    vwidth       = xine_get_stream_info (gui->stream, XINE_STREAM_INFO_VIDEO_WIDTH);
    vheight      = xine_get_stream_info (gui->stream, XINE_STREAM_INFO_VIDEO_HEIGHT);
    asrate       = xine_get_stream_info (gui->stream, XINE_STREAM_INFO_AUDIO_SAMPLERATE);
#endif
    audiochannel = xine_get_param (gui->stream, XINE_PARAM_AUDIO_CHANNEL_LOGICAL);
    spuchannel   = xine_get_param (gui->stream, XINE_PARAM_SPU_CHANNEL);

    if (!gui_xine_get_pos_length (gui, gui->stream, &pos, &playedtime, &totaltime))
      return;

    playedtime /= 1000;
    totaltime  /= 1000;

    xine_osd_clear (gui->osd.sinfo.obj.osd[0]);

    /* We're in visual animation mode */
    if((vwidth == 0) && (vheight == 0)) {
      if (gui->visual_anim.running) {
        if (gui->visual_anim.enabled == 1) {
          video_window_get_frame_size (gui->vwin, &vwidth, &vheight);
	  vcodec = _("post animation");
	}
        else if (gui->visual_anim.enabled == 2) {
          vcodec  = xine_get_meta_info (gui->visual_anim.stream, XINE_META_INFO_VIDEOCODEC);
          vwidth  = xine_get_stream_info (gui->visual_anim.stream, XINE_STREAM_INFO_VIDEO_WIDTH);
          vheight = xine_get_stream_info (gui->visual_anim.stream, XINE_STREAM_INFO_VIDEO_HEIGHT);
	}
      }
      else {
        video_window_get_frame_size (gui->vwin, &vwidth, &vheight);
	vcodec = _("unknown");
      }
    }

    _osd_get_output_size (gui, &wwidth, &wheight);

    y = x = 0;

    gui_playlist_lock (gui);
    strlcpy (buffer, (gui->is_display_mrl) ? gui->mmk.mrl : gui->mmk.ident, sizeof (buffer));
    gui_playlist_unlock (gui);
    xine_osd_get_text_size (gui->osd.sinfo.obj.osd[0], buffer, &osdw, &h);
    p = buffer;
    while(osdw > (wwidth - 40)) {
      *(p++) = '\0';
      *(p)   = '.';
      *(p+1) = '.';
      *(p+2) = '.';
      xine_osd_get_text_size (gui->osd.sinfo.obj.osd[0], p, &osdw, &h);
    }
    xine_osd_draw_text (gui->osd.sinfo.obj.osd[0], x, y, p, XINE_OSD_TEXT1);

    y += h;

    if(vcodec && vwidth && vheight) {
      snprintf(buffer, sizeof(buffer), "%s: %dX%d", vcodec, vwidth, vheight);
      xine_osd_draw_text (gui->osd.sinfo.obj.osd[0], x, y, buffer, XINE_OSD_TEXT1);
      xine_osd_get_text_size (gui->osd.sinfo.obj.osd[0], buffer, &w, &h);
      if(w > osdw)
	osdw = w;
      y += h;
    }

    if(acodec && asrate) {
      snprintf(buffer, sizeof(buffer), "%s: %d%s", acodec, asrate, "Hz");
      xine_osd_draw_text (gui->osd.sinfo.obj.osd[0], x, y, buffer, XINE_OSD_TEXT1);
      xine_osd_get_text_size (gui->osd.sinfo.obj.osd[0], buffer, &w, &h);
      if(w > osdw)
	osdw = w;
      y += h;
    }

    p = buffer;
    p += strlcpy (p, _("Audio: "), e - p);
    if (p > e)
      p = e;
    switch (audiochannel) {
      case -2:
        p += strlcpy (p, "off", e - p);
        break;
      case -1:
        if (xine_get_audio_lang (gui->stream, audiochannel, p))
          p += strlen (p);
        else
          p += strlcpy (p, "auto", e - p);
        break;
      default:
        if (xine_get_audio_lang (gui->stream, audiochannel, p))
          p += strlen (p);
        else
          p += snprintf (p, e - p, "%3d", audiochannel);
    }
    if (p > e)
      p = e;

    p += strlcpy (p, ", Spu: ", e - p);
    if (p > e)
      p = e;
    switch (spuchannel) {
      case -2:
        p += strlcpy (p, "off", e - p);
        break;
      case -1:
        if (xine_get_spu_lang (gui->stream, spuchannel, p))
          p += strlen (p);
        else
          p += strlcpy (p, "auto", e - p);
        break;
      default:
        if (xine_get_spu_lang (gui->stream, spuchannel, p))
          p += strlen (p);
        else
          p += snprintf (p, e - p, "%3d", spuchannel);
    }
    if (p > e)
      p = e;
    /* p += */ strlcpy (p, ".", e - p);
    xine_osd_draw_text (gui->osd.sinfo.obj.osd[0], x, y, buffer, XINE_OSD_TEXT1);
    xine_osd_get_text_size (gui->osd.sinfo.obj.osd[0], buffer, &w, &h);
    if(w > osdw)
      osdw = w;

    y += (h);

    playeddays = playedtime / (3600 * 24);

    if(playeddays > 0)
      sprintf(buffer, "%d::%02d ", playeddays, playedtime / 3600);
    else
      sprintf(buffer, "%d:%02d:%02d ", playedtime / 3600, (playedtime % 3600) / 60, playedtime % 60);

    if(totaltime > 0) {
      int totaldays;

      totaldays  = totaltime / (3600 * 24);
      sprintf(buffer+strlen(buffer), "(%.0f%%) %s ", ((float)playedtime / (float)totaltime) * 100, _("of"));

      if(totaldays > 0)
	sprintf(buffer+strlen(buffer), "%d::%02d", totaldays, totaltime / 3600);
      else
	sprintf(buffer+strlen(buffer), "%d:%02d:%02d", totaltime / 3600, (totaltime % 3600) / 60, totaltime % 60);
    }

    xine_osd_draw_text (gui->osd.sinfo.obj.osd[0], x, y, buffer, XINE_OSD_TEXT1);
    xine_osd_get_text_size (gui->osd.sinfo.obj.osd[0], buffer, &w, &h);
    if(w > osdw)
      osdw = w;

    osd_stream_position (gui, pos);

    x = (wwidth - osdw) - 40;
    xine_osd_set_position (gui->osd.sinfo.obj.osd[0], (x >= 0) ? x : 0, 15);
    gui->osd.sinfo.obj.x = (x >= 0) ? x : 0;
    gui->osd.sinfo.obj.y = 15;
    gui->osd.sinfo.obj.w = osdw;

    pthread_mutex_lock (&gui->osd.mutex);
    _osd_obj_set (gui, &gui->osd.sinfo, gui->osd.timeout);
    pthread_mutex_unlock (&gui->osd.mutex);
  }
}

void osd_draw_bar (gGui_t *gui, const char *title, int min, int max, int val, int type) {
  if (gui->osd.enabled) {
    int      wwidth, wheight;
    int      bar_color[40];
    int      i, x;
    float    _val = (int) val;
    float    _min = (int) min;
    float    _max = (int) max;
    int      pos;

    if(max <= min)
      _max = (int) (min + 1);
    if(min >= max)
      _min = (int) (max - 1);
    if(val > max)
      _val = (int) max;
    if(val < min)
      _val = (int) min;

    pos = (int) (_val + -_min) / ((_max + -_min) / 40);

    _osd_get_output_size (gui, &wwidth, &wheight);

    xine_osd_clear (gui->osd.bar.obj.osd[0]);
    xine_osd_clear (gui->osd.bar.obj.osd[1]);

    memset(&bar_color, (XINE_OSD_TEXT1 + 7), sizeof(int) * 40);

    switch(type) {
    case OSD_BAR_PROGRESS:
    case OSD_BAR_STEPPER:
      if(pos)
	memset(bar_color, (XINE_OSD_TEXT1 + 21), sizeof(int) * pos);
      break;
    case OSD_BAR_POS:
    case OSD_BAR_POS2:
      if(pos)
	bar_color[pos - 1] = (XINE_OSD_TEXT1 + 21);
      break;
    }

    if((type == OSD_BAR_PROGRESS) || (type == OSD_BAR_POS)) {
      x = 3;
      xine_osd_draw_rect (gui->osd.bar.obj.osd[0], x, 2, x + 3, BAR_HEIGHT - 2, XINE_OSD_TEXT1 + 9, 1);
      x += 8;

      for(i = 0; i < 40; i++, x += 8) {
        xine_osd_draw_rect (gui->osd.bar.obj.osd[0],
			   x, 6, x + 3, BAR_HEIGHT - 2, bar_color[i], 1);
      }

      xine_osd_draw_rect (gui->osd.bar.obj.osd[0],
			 x, 2, x + 3, BAR_HEIGHT - 2, XINE_OSD_TEXT1 + 9, 1);
    }
    else if(type == OSD_BAR_POS2) {
      x = 3;
      xine_osd_draw_rect (gui->osd.bar.obj.osd[0], x, 2, x + 3, BAR_HEIGHT - 2, XINE_OSD_TEXT1 + 9, 1);
      x += 8;

      for(i = 0; i < 40; i++, x += 8) {
	if(i == (pos - 1))
          xine_osd_draw_rect (gui->osd.bar.obj.osd[0],
			     x, 2, x + 3, BAR_HEIGHT - 2, bar_color[i], 1);
	else
          xine_osd_draw_rect (gui->osd.bar.obj.osd[0],
			     x, 6, x + 3, BAR_HEIGHT - 6, bar_color[i], 1);
      }

      xine_osd_draw_rect (gui->osd.bar.obj.osd[0],
			 x, 2, x + 3, BAR_HEIGHT - 2, XINE_OSD_TEXT1 + 9, 1);
    }
    else if(type == OSD_BAR_STEPPER) {
      int y = BAR_HEIGHT - 4;
      int step = y / 20;

      x = 11;

      for(i = 0; i < 40; i++, x += 8) {
        xine_osd_draw_rect (gui->osd.bar.obj.osd[0],
			   x, y, x + 3, BAR_HEIGHT - 2, bar_color[i], 1);

	if(!(i % 2))
	  y -= step;

      }
    }

    if(title) {
      int  tw, th;

      gui->osd.bar.obj.have_text = 1;

      xine_osd_get_text_size (gui->osd.bar.obj.osd[1], title, &tw, &th);
      xine_osd_draw_text (gui->osd.bar.obj.osd[1], (BAR_WIDTH - tw) >> 1, 0, title, XINE_OSD_TEXT1);
    }
    else
      gui->osd.bar.obj.have_text = 0;

    x = (wwidth - BAR_WIDTH) >> 1;
    xine_osd_set_position (gui->osd.bar.obj.osd[0], (x >= 0) ? x : 0, (wheight - BAR_HEIGHT) - 40);
    xine_osd_set_position (gui->osd.bar.obj.osd[1], (x >= 0) ? x : 0, (wheight - (BAR_HEIGHT * 2)) - 40);

    /* don't even bother drawing osd over those small streams.
     * it would look pretty bad.
     */
    if( wwidth > MINIMUM_WIN_WIDTH ) {
      pthread_mutex_lock (&gui->osd.mutex);
      _osd_obj_set (gui, &gui->osd.bar, gui->osd.timeout);
      pthread_mutex_unlock (&gui->osd.mutex);
    }
  }
}

void osd_stream_position (gGui_t *gui, int pos) {
  osd_draw_bar (gui, _("Position in Stream"), 0, 65535, pos, OSD_BAR_POS2);
}

void osd_display_info (gGui_t *gui, const char *info, ...) {
  char *buf;
  const char *text;

  if (!gui->osd.enabled || gui->on_quit)
    return;

  if (info) {
    if (!strchr (info, '%')) {
      buf = NULL;
      text = info;
    } else {
      va_list args;
      va_start (args, info);
      if (!strcmp (info, "%s")) {
        buf = NULL;
        text = va_arg (args, const char *);
      } else {
        text = buf = xitk_vasprintf (info, args);
      }
      va_end (args);
    }
  } else {
    text = buf = NULL;
  }
  if (!text)
    return;

  xine_osd_clear (gui->osd.info.obj.osd[0]);

  xine_osd_draw_text (gui->osd.info.obj.osd[0], 0, 0, text, XINE_OSD_TEXT1);
  xine_osd_set_position (gui->osd.info.obj.osd[0], 20, 10 + 30);

  pthread_mutex_lock (&gui->osd.mutex);
  _osd_obj_set (gui, &gui->osd.info, gui->osd.timeout);
  pthread_mutex_unlock (&gui->osd.mutex);

  free (buf);
}

void osd_update_status (gGui_t *gui) {
  if (gui->osd.enabled) {
    int  status;
    char buffer[256];
    int wwidth, wheight;

    status = xine_get_status (gui->stream);

    /*
      { : eject
      [ : previous
      | : | (thin)
      @ : | (large)
      ] : next
      } : stop
      $ : > (large)
      > : play
      < : pause
    */

    buffer[0] = 0;

    switch (status) {

      case XINE_STATUS_IDLE:
      case XINE_STATUS_STOP:
        {
          char *q = buffer;
          _osd_get_status_sym (&q, status);
        }
        break;

      case XINE_STATUS_PLAY:
        {
          int speed = xine_get_param (gui->stream, XINE_PARAM_FINE_SPEED), ms = 0;
          char *q = buffer;

          _osd_get_speed_sym (&q, speed);

          if (gui_xine_get_pos_length (gui, gui->stream, NULL, &ms, NULL)) {
            uint32_t u = (ms < 0) ? -ms : ms;

            if (ms < 0)
              *q++ = '-';
            if (speed < XINE_FINE_SPEED_NORMAL * 4 / 5) {
              if (!u)
                break;
              q[13] = 0;
              q[12] = u % 10u + '0';
              u /= 10u;
              q[11] = u % 10 + '0';
              u /= 10u;
              q[10] = u % 10u + '0';
              u /= 10u;
              q[9] = '.';
            } else {
              u /= 1000u;
              if (!u)
                break;
              q[9] = 0;
            }
            q[8] = u % 10u + '0';
            u /= 10u;
            q[7] = u % 6u + '0';
            u /= 6u;
            q[6] = ':';
            q[5] = u % 10u + '0';
            u /= 10u;
            q[4] = u % 6u + '0';
            u /= 6u;
            q[3] = ':';
            q[2] = u % 10u + '0';
            u /= 10u;
            q[1] = u % 10u + '0';
            q[0] = ' ';
          }
        }
        break;

      default: ;
    }

    _osd_get_output_size (gui, &wwidth, &wheight);

    pthread_mutex_lock (&gui->osd.mutex);

    xine_osd_clear (gui->osd.status.obj.osd[0]);

    if (gui->osd.use_unscaled && gui->osd.unscaled_available)
      xine_osd_set_font (gui->osd.status.obj.osd[0], "cetus", UNSCALED_FONT_SIZE);
    else
      xine_osd_set_font (gui->osd.status.obj.osd[0], "cetus", FONT_SIZE);

    /* set latin1 encoding (NULL) for status text with special characters,
     * then switch back to locale encoding ("")
     */
    xine_osd_set_encoding (gui->osd.status.obj.osd[0], NULL);
    xine_osd_draw_text (gui->osd.status.obj.osd[0], 0, 0, buffer, XINE_OSD_TEXT1);
    xine_osd_set_encoding (gui->osd.status.obj.osd[0], "");
    xine_osd_set_position (gui->osd.status.obj.osd[0], 20, 10);

    /* don't even bother drawing osd over those small streams.
     * it would look pretty bad.
     */
    if (wwidth > MINIMUM_WIN_WIDTH)
      _osd_obj_set (gui, &gui->osd.status, gui->osd.timeout);

    pthread_mutex_unlock (&gui->osd.mutex);
  }
}

void osd_display_spu_lang (gGui_t *gui) {
  char   buffer[XINE_LANG_MAX+128];
  char   lang_buffer[XINE_LANG_MAX];
  int    channel;
  const char *lang = NULL;

  channel = xine_get_param (gui->stream, XINE_PARAM_SPU_CHANNEL);

  switch(channel) {
  case -2:
    lang = "off";
    break;
  case -1:
    if(!xine_get_spu_lang (gui->stream, channel, &lang_buffer[0]))
      lang = "auto";
    else
      lang = lang_buffer;
    break;
  default:
    if(!xine_get_spu_lang (gui->stream, channel, &lang_buffer[0]))
      snprintf(lang_buffer, sizeof(lang_buffer), "%3d", channel);
    lang = lang_buffer;
    break;
  }

  snprintf(buffer, sizeof(buffer), "%s%s", _("Subtitles: "), get_language_from_iso639_1(lang));
  osd_display_info (gui, "%s", buffer);
}

void osd_display_audio_lang (gGui_t *gui) {
  char   buffer[XINE_LANG_MAX+128];
  char   lang_buffer[XINE_LANG_MAX];
  int    channel;
  const char *lang = NULL;

  channel = xine_get_param (gui->stream, XINE_PARAM_AUDIO_CHANNEL_LOGICAL);

  switch(channel) {
  case -2:
    lang = "off";
    break;
  case -1:
    if(!xine_get_audio_lang (gui->stream, channel, &lang_buffer[0]))
      lang = "auto";
    else
      lang = lang_buffer;
    break;
  default:
    if(!xine_get_audio_lang (gui->stream, channel, &lang_buffer[0]))
      snprintf(lang_buffer, sizeof(lang_buffer), "%3d", channel);
    lang = lang_buffer;
    break;
  }

  snprintf(buffer, sizeof(buffer), "%s%s", _("Audio Channel: "), get_language_from_iso639_1(lang));
  osd_display_info (gui, "%s", buffer);
}

void osd_update_osd (gGui_t *gui) {
  int vwidth, vheight, wwidth, wheight;
  int x;

  if (!gui->osd.sinfo.obj.visible && !gui->osd.bar.obj.visible)
    return;

  vwidth  = xine_get_stream_info (gui->stream, XINE_STREAM_INFO_VIDEO_WIDTH);
  vheight = xine_get_stream_info (gui->stream, XINE_STREAM_INFO_VIDEO_HEIGHT);

  if((vwidth == 0) && (vheight == 0)) {
    if (gui->visual_anim.running) {
      if (gui->visual_anim.enabled == 1)
        video_window_get_frame_size (gui->vwin, &vwidth, &vheight);
      else if (gui->visual_anim.enabled == 2)
	vwidth  = xine_get_stream_info (gui->visual_anim.stream, XINE_STREAM_INFO_VIDEO_WIDTH);
    }
    else
      video_window_get_frame_size (gui->vwin, &vwidth, &vheight);

  }

  _osd_get_output_size (gui, &wwidth, &wheight);

  pthread_mutex_lock (&gui->osd.mutex);

  if (gui->osd.sinfo.obj.visible) {
    xine_osd_hide (gui->osd.sinfo.obj.osd[0], 0);

    x = (wwidth - gui->osd.sinfo.obj.w) - 40;
    xine_osd_set_position (gui->osd.sinfo.obj.osd[0], (x >= 0) ? x : 0,  gui->osd.sinfo.obj.y);
    _xine_osd_show (gui, gui->osd.sinfo.obj.osd[0], 0);
  }

  if (gui->osd.bar.obj.visible) {
    xine_osd_hide (gui->osd.bar.obj.osd[0], 0);
    xine_osd_hide (gui->osd.bar.obj.osd[1], 0);

    x = (wwidth - BAR_WIDTH) >> 1;
    xine_osd_set_position (gui->osd.bar.obj.osd[0], (x >= 0) ? x : 0, (wheight - BAR_HEIGHT) - 40);
    xine_osd_set_position (gui->osd.bar.obj.osd[1], (x >= 0) ? x : 0, (wheight - (BAR_HEIGHT * 2)) - 40);

    /* don't even bother drawing osd over those small streams.
     * it would look pretty bad.
     */
    if (wwidth > MINIMUM_WIN_WIDTH) {
      _xine_osd_show (gui, gui->osd.bar.obj.osd[0], 0);

      if (gui->osd.bar.obj.have_text)
        _xine_osd_show (gui, gui->osd.bar.obj.osd[1], 0);
    }

  }

  pthread_mutex_unlock (&gui->osd.mutex);
}
