/*
 * Copyright (C) 2000-2020 the xine project
 *
 * Copyright (C) 2009-2020 Petri Hintukainen <phintuka@users.sourceforge.net>
 *
 * 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
 *
 * Decoder for HDMV/BluRay bitmap subtitles
 */

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

#include <inttypes.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

#define LOG_MODULE "spu_hdmv"
/*
#define LOG
*/

#include <xine/xine_internal.h>
#include <xine/buffer.h>
#include <xine/xineutils.h>
#include <xine/video_out.h>
#include <xine/video_overlay.h>

#define XINE_HDMV_TRACE lprintf

#ifndef MAX
#  define MAX(a,b) (a>b)?(a):(b)
#endif

enum {
  SEGTYPE_PALETTE              = 0x14,
  SEGTYPE_OBJECT               = 0x15,
  SEGTYPE_PRESENTATION_SEGMENT = 0x16,
  SEGTYPE_WINDOW_DEFINITION    = 0x17,
  SEGTYPE_INTERACTIVE          = 0x18,
  SEGTYPE_END_OF_DISPLAY       = 0x80,
} eSegmentType;

/*
 * cached palette (xine-lib format)
 */
typedef struct subtitle_clut_s subtitle_clut_t;
struct subtitle_clut_s {
  uint8_t          id;
  uint32_t         color[256];
  uint8_t          trans[256];
  subtitle_clut_t *next;

  int shown;
};

/*
 * cached RLE image (xine-lib format)
 */
typedef struct subtitle_object_s subtitle_object_t;
struct subtitle_object_s {
  uint16_t    id;
  uint16_t    xpos, ypos;
  uint16_t    width, height;

  /* xine format */
  rle_elem_t *rle;
  unsigned int num_rle;
  size_t      data_size;

  /* HDMV format (used when object does not fit to single segment) */
  uint32_t    data_len;      /* size of complete object */
  uint8_t    *raw_data;      /* partial RLE data in HDMV format */
  size_t      raw_data_len;  /* bytes buffered */
  size_t      raw_data_size; /* allocated size */

  subtitle_object_t *next;

  int shown;
};

/*
 * Window definition
 */
typedef struct window_def_s window_def_t;
struct window_def_s {
  uint8_t   id;
  uint16_t  xpos, ypos;
  uint16_t  width, height;

  window_def_t *next;

  int shown;
};


/*
 * decoded SPU
 */
typedef struct composition_object_s composition_object_t;
struct composition_object_s {
  uint8_t     window_id_ref;
  uint16_t    object_id_ref;

  uint16_t    xpos, ypos;

  uint8_t     forced_flag;
  uint8_t     cropped_flag;
  uint16_t    crop_horiz_pos, crop_vert_pos;
  uint16_t    crop_width, crop_height;

  composition_object_t *next;

  int shown;
};

typedef struct composition_descriptor_s composition_descriptor_t;
struct composition_descriptor_s {
  uint16_t number;
  uint8_t  state;
};

typedef struct presentation_segment_s presentation_segment_t;
struct presentation_segment_s {
  composition_descriptor_t comp_descr;

  uint8_t palette_update_flag;
  uint8_t palette_id_ref;
  uint8_t object_number;

  composition_object_t *comp_objs;

  presentation_segment_t *next;

  int64_t pts;
  int     shown;
};

/*
 * list handling
 */

#define LIST_REPLACE(list, obj, FREE_FUNC)      \
  do {                                          \
    unsigned int id = obj->id;                  \
                                                \
    /* insert to list */                        \
    obj->next = list;                           \
    list = obj;                                 \
                                                \
    /* remove old */                            \
    while (obj->next && obj->next->id != id)    \
      obj = obj->next;                          \
    if (obj->next) {                            \
      void *tmp = (void*)obj->next;             \
      obj->next = obj->next->next;              \
      FREE_FUNC(tmp);                           \
    }                                           \
  } while (0);

#define LIST_DESTROY(list, FREE_FUNC) \
  while (list) {               \
    void *tmp = (void*)list;   \
    list = list->next;         \
    FREE_FUNC(tmp);            \
  }

static void free_subtitle_object(void *ptr)
{
  if (ptr) {
    _x_freep(&((subtitle_object_t*)ptr)->rle);
    _x_freep(&((subtitle_object_t*)ptr)->raw_data);
    free(ptr);
  }
}
static void free_presentation_segment(void *ptr)
{
  if (ptr) {
    presentation_segment_t *seg = (presentation_segment_t*)ptr;
    LIST_DESTROY(seg->comp_objs, free);
    free(ptr);
  }
}


/*
 * segment_buffer_t
 *
 * assemble and decode segments
 */

typedef struct {
  /* current segment */
  int      segment_len;  /* length of current segment (without 3-byte header) */
  uint8_t  segment_type; /* current segment type */
  uint8_t *segment_data; /* pointer to current segment payload */
  uint8_t *segment_end;  /* pointer to last byte + 1 of current segment */
  uint8_t  error;        /* boolean: buffer overflow etc. */

  /* accumulated data */
  uint8_t *buf;          /* */
  size_t   len;          /* count of unprocessed bytes */
  size_t   data_size;    /* allocated buffer size */
} segment_buffer_t;

/*
 * mgmt
 */

static segment_buffer_t *segbuf_init(void)
{
  segment_buffer_t *buf = calloc(1, sizeof(segment_buffer_t));
  return buf;
}

static void segbuf_dispose(segment_buffer_t *buf)
{
  _x_freep (&buf->buf);
  free (buf);
}

static void segbuf_reset(segment_buffer_t *buf)
{
  buf->segment_end = buf->segment_data = buf->buf;
  buf->len          = 0;
  buf->segment_len  = -1;
  buf->segment_type = 0;
  buf->error        = 0;
}

/*
 * assemble, parse
 */

static void segbuf_parse_segment_header(segment_buffer_t *buf)
{
  if (buf->len > 2) {
    buf->segment_type = buf->buf[0];
    buf->segment_len  = (buf->buf[1] << 8) | buf->buf[2];
    buf->segment_data = buf->buf + 3;
    buf->segment_end  = buf->segment_data + buf->segment_len;
    buf->error        = 0;

    if ( buf->segment_type < 0x14 ||
         ( buf->segment_type > 0x18 &&
           buf->segment_type != 0x80)) {
      XINE_HDMV_TRACE("unknown segment type 0x%02x, resetting\n", buf->segment_type);
      segbuf_reset(buf);
    }
  } else {
    buf->segment_len = -1;
    buf->error       = 1;
  }
}

static void segbuf_fill(segment_buffer_t *buf, uint8_t *data, size_t len)
{
  if (buf->len + len > buf->data_size) {
    buf->data_size = buf->len + len;
    buf->buf = realloc(buf->buf, buf->data_size);
  }

  memcpy(buf->buf + buf->len, data, len);
  buf->len += len;

  segbuf_parse_segment_header(buf);
}

static int segbuf_segment_complete(segment_buffer_t *buf)
{
  return (buf->segment_len >= 0) && (buf->len >= (unsigned)buf->segment_len + 3);
}

static void segbuf_skip_segment(segment_buffer_t *buf)
{
  _x_assert (segbuf_segment_complete (buf));

    buf->len -= buf->segment_len + 3;
    if (buf->len > 0)
      memmove(buf->buf, buf->buf + buf->segment_len + 3, buf->len);

    segbuf_parse_segment_header(buf);

    XINE_HDMV_TRACE("  skip_segment: %zd bytes left\n", buf->len);
}

/*
 * access segment data
 */

static uint8_t segbuf_segment_type(segment_buffer_t *buf)
{
  return buf->segment_type;
}

static size_t segbuf_data_length(segment_buffer_t *buf)
{
  ssize_t val = buf->segment_end - buf->segment_data;
  if (val < 0) val = 0;
  return (size_t)val;
}

static uint8_t segbuf_get_u8(segment_buffer_t *buf)
{
  if (!(buf->error = ++buf->segment_data > buf->segment_end))
    return buf->segment_data[-1];
  XINE_HDMV_TRACE("segbuf_get_u8: read failed (end of segment reached) !\n");
  return 0;
}

static uint16_t segbuf_get_u16(segment_buffer_t *buf)
{
  return (segbuf_get_u8(buf) << 8) | segbuf_get_u8(buf);
}

static uint32_t segbuf_get_u24(segment_buffer_t *buf)
{
  return (segbuf_get_u8(buf) << 16) | (segbuf_get_u8(buf) << 8) | segbuf_get_u8(buf);
}

/*
 * decode segments
 */

static subtitle_clut_t *segbuf_decode_palette(segment_buffer_t *buf)
{
  subtitle_clut_t *clut;
  uint8_t palette_id             = segbuf_get_u8 (buf);
  uint8_t palette_version_number = segbuf_get_u8 (buf);

  size_t  len     = segbuf_data_length(buf);
  size_t  entries = len / 5;
  size_t  i;

  if (buf->error)
    return NULL;

  if (len % 5) {
    XINE_HDMV_TRACE("  decode_palette: segment size error (%zd ; expected %zd for %zd entries)\n",
                    len, (5 * entries), entries);
    return NULL;
  }

  (void)palette_version_number;
  XINE_HDMV_TRACE("decode_palette: %zd items (id %d, version %d)\n",
                  entries, palette_id, palette_version_number);

  /* convert to xine-lib clut */

  clut = calloc(1, sizeof(subtitle_clut_t));
  if (!clut)
    return NULL;

  clut->id = palette_id;

  for (i = 0; i < entries; i++) {
    uint8_t index = segbuf_get_u8 (buf);
    uint8_t Y     = segbuf_get_u8 (buf);
    uint8_t Cr    = segbuf_get_u8 (buf);
    uint8_t Cb    = segbuf_get_u8 (buf);
    uint8_t alpha = segbuf_get_u8 (buf);
#ifdef WORDS_BIGENDIAN
    clut->color[index] = (Y << 8) | (Cr << 16) | (Cb << 24);
#else
    clut->color[index] = (Y << 16) | (Cr << 8) | Cb;
#endif
    clut->trans[index] = alpha >> 4;
  }

  /* HDMV subs always use same color matrix as main video. */
  _X_SET_CLUT_CM (clut->color, 4);

  return clut;
}

static int segbuf_decode_rle(segment_buffer_t *buf, subtitle_object_t *obj)
{
  int x = 0, y = 0;
  int rle_size = sizeof(rle_elem_t) * obj->width / 16 * obj->height + 1;
  rle_elem_t *rlep = malloc(rle_size);

  free (obj->rle);
  obj->rle       = rlep;
  obj->data_size = rle_size;
  obj->num_rle   = 0;

  /* convert to xine-lib rle format */
  while (y < obj->height && !buf->error) {

    /* decode RLE element */
    uint8_t byte = segbuf_get_u8 (buf);
    if (byte != 0) {
      rlep->color = byte;
      rlep->len   = 1;
    } else {
      byte = segbuf_get_u8 (buf);
      if (!(byte & 0x80)) {
        rlep->color = 0;
        if (!(byte & 0x40))
          rlep->len   = byte & 0x3f;
        else
          rlep->len   = ((byte & 0x3f) << 8) | segbuf_get_u8 (buf);
      } else {
        if (!(byte & 0x40))
          rlep->len   = byte & 0x3f;
        else
          rlep->len   = ((byte & 0x3f) << 8) | segbuf_get_u8 (buf);
        rlep->color = segbuf_get_u8 (buf);
      }
    }

    /* move to next element */
    if (rlep->len > 0) {
      x += rlep->len;
      rlep++;
      obj->num_rle ++;
    } else {
      /* end of line marker (00 00) */
      if (x < obj->width) {
        rlep->len = obj->width - x;
        rlep->color = 0xff;
        rlep++;
        obj->num_rle ++;
      }
      x = 0;
      y++;
    }

    /* grow allocated RLE data size ? */
    if (obj->data_size <= (obj->num_rle + 1) * sizeof(rle_elem_t)) {
      obj->data_size *= 2;
      obj->rle = realloc(obj->rle, obj->data_size);
      rlep = obj->rle + obj->num_rle;
    }
  }

  return buf->error;
}

static subtitle_object_t *segbuf_decode_object(segment_buffer_t *buf, subtitle_object_t *objects)
{
  uint16_t object_id = segbuf_get_u16(buf);
  uint8_t  version   = segbuf_get_u8 (buf);
  uint8_t  seq_desc  = segbuf_get_u8 (buf);

  (void)version;
  XINE_HDMV_TRACE("  decode_object: object_id %d, version %d, seq 0x%x\n",
                  object_id, version, seq_desc);

  if (seq_desc & 0x80) {
    subtitle_object_t *obj;

    /* new object (first-in-sequence flag set) */

    obj = calloc(1, sizeof(subtitle_object_t));
    if (!obj)
      return NULL;

    obj->id       = object_id;
    obj->data_len = segbuf_get_u24(buf);
    obj->width    = segbuf_get_u16(buf);
    obj->height   = segbuf_get_u16(buf);

    if (buf->error) {
      XINE_HDMV_TRACE("    decode error at object header\n");
      free_subtitle_object(obj);
      return NULL;
    }

    obj->data_len -= 4; /* width, height parsed */

    XINE_HDMV_TRACE("    object length %d bytes, size %dx%d\n", obj->data_len, obj->width, obj->height);

    if (obj->data_len > segbuf_data_length(buf)) {
      XINE_HDMV_TRACE("    object length %d bytes, have only %zd bytes -> missing %d bytes\n",
                      obj->data_len, segbuf_data_length(buf), obj->data_len - (int)segbuf_data_length(buf));

      _x_freep(&obj->raw_data);

      /* store partial RLE data in HDMV format */
      obj->raw_data_len  = segbuf_data_length(buf);
      obj->raw_data_size = MAX(obj->data_len, obj->raw_data_len);
      obj->raw_data      = malloc(obj->raw_data_size);
      memcpy(obj->raw_data, buf->segment_data, obj->raw_data_len);

      return obj;
    }

    segbuf_decode_rle (buf, obj);

    if (buf->error) {
      XINE_HDMV_TRACE("    decode error at RLE data\n");
      free_subtitle_object(obj);
      return NULL;
    }

    return obj;
  }

  /* not first-of-sequence --> append data to already existing objct */

  /* search for object */
  while (objects && objects->id != object_id)
    objects = objects->next;

  if (!objects) {
    XINE_HDMV_TRACE("    object not found from list, discarding segment\n");
    return NULL;
  }

  /* store partial RLE data in HDMV format */
  if (objects->raw_data_size < objects->raw_data_len + segbuf_data_length(buf)) {
    XINE_HDMV_TRACE("object larger than object size !\n");
    return NULL;
  }
  memcpy(objects->raw_data + objects->raw_data_len, buf->segment_data, segbuf_data_length(buf));
  objects->raw_data_len  += segbuf_data_length(buf);

  /* if complete, decode RLE data */
  if (objects->raw_data_len >= objects->data_len) {
    /* create dummy buffer for segbuf_decode_rle */
    segment_buffer_t tmpbuf = {
      .segment_data = objects->raw_data,
      .segment_end  = objects->raw_data + objects->raw_data_len,
    };

    /* decode RLE data */
    segbuf_decode_rle (&tmpbuf, objects);

    if (tmpbuf.error) {
      XINE_HDMV_TRACE("    error decoding multi-segment object\n");
    }

    /* free decode buffer */
    _x_freep(&objects->raw_data);
    objects->raw_data_len  = 0;
    objects->raw_data_size = 0;
  }

  return NULL;
}

static window_def_t *segbuf_decode_window_definition(segment_buffer_t *buf)
{
  window_def_t *wnd;
  uint8_t a;

  wnd = calloc(1, sizeof(window_def_t));
  if (!wnd)
    return NULL;

  a           = segbuf_get_u8 (buf);
  wnd->id     = segbuf_get_u8 (buf);
  wnd->xpos   = segbuf_get_u16 (buf);
  wnd->ypos   = segbuf_get_u16 (buf);
  wnd->width  = segbuf_get_u16 (buf);
  wnd->height = segbuf_get_u16 (buf);

  (void)a;
  XINE_HDMV_TRACE("  window: [%02x %d]  %d,%d %dx%d\n", a,
                  wnd->id, wnd->xpos, wnd->ypos, wnd->width, wnd->height);

  if (buf->error) {
    free(wnd);
    return NULL;
  }

  return wnd;
}

static int segbuf_decode_video_descriptor(segment_buffer_t *buf)
{
  uint16_t width      = segbuf_get_u16(buf);
  uint16_t height     = segbuf_get_u16(buf);
  uint8_t  frame_rate = segbuf_get_u8 (buf);

  (void)width;
  (void)height;
  (void)frame_rate;
  XINE_HDMV_TRACE("  video_descriptor: %dx%d fps %d\n", width, height, frame_rate);

  return buf->error;
}

static int segbuf_decode_composition_descriptor(segment_buffer_t *buf, composition_descriptor_t *descr)
{
  descr->number = segbuf_get_u16(buf);
  descr->state  = segbuf_get_u8 (buf) & 0xc0;

  XINE_HDMV_TRACE("  composition_descriptor: number %d, state %d\n", descr->number, descr->state);
  return buf->error;
}

static composition_object_t *segbuf_decode_composition_object(segment_buffer_t *buf)
{
  composition_object_t *cobj;
  uint8_t tmp;

  cobj = calloc(1, sizeof(composition_object_t));
  if (!cobj)
    return NULL;

  cobj->object_id_ref    = segbuf_get_u16 (buf);
  cobj->window_id_ref    = segbuf_get_u8 (buf);
  tmp                    = segbuf_get_u8 (buf);
  cobj->cropped_flag     = !!(tmp & 0x80);
  cobj->forced_flag      = !!(tmp & 0x40);
  cobj->xpos             = segbuf_get_u16 (buf);
  cobj->ypos             = segbuf_get_u16 (buf);
  if (cobj->cropped_flag) {
    /* x,y where to take the image from */
    cobj->crop_horiz_pos = segbuf_get_u8 (buf);
    cobj->crop_vert_pos  = segbuf_get_u8 (buf);
    /* size of the cropped image */
    cobj->crop_width     = segbuf_get_u8 (buf);
    cobj->crop_height    = segbuf_get_u8 (buf);
  }

  if (buf->error) {
    free(cobj);
    return NULL;
  }

  XINE_HDMV_TRACE("  composition_object: id: %d, win: %d, position %d,%d crop %d forced %d\n",
                  cobj->object_id_ref, cobj->window_id_ref, cobj->xpos, cobj->ypos,
                  cobj->cropped_flag, cobj->forced_flag);

  return cobj;
}

static presentation_segment_t *segbuf_decode_presentation_segment(segment_buffer_t *buf)
{
  presentation_segment_t *seg;
  int                     index;

  seg = calloc(1, sizeof(presentation_segment_t));
  if (!seg)
    return NULL;

  segbuf_decode_video_descriptor (buf);
  segbuf_decode_composition_descriptor (buf, &seg->comp_descr);

  seg->palette_update_flag  = !!((segbuf_get_u8(buf)) & 0x80);
  seg->palette_id_ref       = segbuf_get_u8 (buf);
  seg->object_number        = segbuf_get_u8 (buf);

  XINE_HDMV_TRACE("  presentation_segment: object_number %d, palette %d\n",
                  seg->object_number, seg->palette_id_ref);

  for (index = 0; index < seg->object_number; index++) {
    composition_object_t *cobj = segbuf_decode_composition_object (buf);
    if (cobj) {
      cobj->next = seg->comp_objs;
      seg->comp_objs = cobj;
    }
  }

  if (buf->error) {
    free_presentation_segment(seg);
    return NULL;
  }

  return seg;
}

static rle_elem_t *copy_crop_rle(subtitle_object_t *obj, composition_object_t *cobj)
{
  /* TODO: cropping (w,h sized image from pos x,y) */
  (void)cobj;

  rle_elem_t *rle = calloc (obj->num_rle, sizeof(rle_elem_t));
  if (rle) {
    memcpy (rle, obj->rle, obj->num_rle * sizeof(rle_elem_t));
  }
  return rle;
}


/*
 * xine plugin
 */

typedef struct spuhdmv_decoder_s {
  spu_decoder_t    spu_decoder;

  xine_stream_t    *stream;

  segment_buffer_t *buf;

  subtitle_clut_t        *cluts;
  subtitle_object_t      *objects;
  window_def_t           *windows;
  presentation_segment_t *segments;

  int overlay_handles[MAX_OBJECTS];

  uint32_t              current_spu_channel;
  int64_t               pts;

} spuhdmv_decoder_t;

static void free_objs(spuhdmv_decoder_t *this)
{
  LIST_DESTROY (this->cluts,    free);
  LIST_DESTROY (this->objects,  free_subtitle_object);
  LIST_DESTROY (this->windows,  free);
  LIST_DESTROY (this->segments, free_presentation_segment);
}

static int decode_palette(spuhdmv_decoder_t *this)
{
  /* decode */
  subtitle_clut_t *clut = segbuf_decode_palette(this->buf);
  if (!clut)
    return 1;

  LIST_REPLACE (this->cluts, clut, free);

  return 0;
}

static int decode_object(spuhdmv_decoder_t *this)
{
  /* decode */
  subtitle_object_t *obj = segbuf_decode_object(this->buf, this->objects);
  if (!obj)
    return 1;

  LIST_REPLACE (this->objects, obj, free_subtitle_object);

  return 0;
}

static int decode_window_definition(spuhdmv_decoder_t *this)
{
  /* decode */
  window_def_t *wnd = segbuf_decode_window_definition (this->buf);
  if (!wnd)
    return 1;

  LIST_REPLACE (this->windows, wnd, free);

  return 0;
}

static int decode_presentation_segment(spuhdmv_decoder_t *this)
{
  /* decode */
  presentation_segment_t *seg = segbuf_decode_presentation_segment(this->buf);
  if (!seg)
    return 1;

  seg->pts = this->pts;

  /* epoch start or acquistion point -> drop cached objects */
  if (seg->comp_descr.state) {
    free_objs(this);
  }

  /* replace */
  if (this->segments)
    LIST_DESTROY(this->segments, free_presentation_segment);
  this->segments = seg;

  return 0;
}

static int show_overlay(spuhdmv_decoder_t *this, composition_object_t *cobj, unsigned int palette_id_ref,
                        int overlay_index, int64_t pts, int force_update)
{
  video_overlay_manager_t *ovl_manager = this->stream->video_out->get_overlay_manager(this->stream->video_out);
  metronom_t              *metronom    = this->stream->metronom;
  video_overlay_event_t    event       = {.vpts = 0};
  vo_overlay_t             overlay     = {.x = 0};

  /* find palette */
  subtitle_clut_t *clut = this->cluts;
  while (clut && clut->id != palette_id_ref)
    clut = clut->next;
  if (!clut) {
    XINE_HDMV_TRACE("  show_overlay: clut %d not found !\n", palette_id_ref);
    return -1;
  }

  /* find RLE image */
  subtitle_object_t *obj = this->objects;
  while (obj && obj->id != cobj->object_id_ref)
    obj = obj->next;
  if (!obj) {
    XINE_HDMV_TRACE("  show_overlay: object %d not found !\n", cobj->object_id_ref);
    return -1;
  }
  if (!obj->rle) {
    XINE_HDMV_TRACE("  show_overlay: object %d RLE data not decoded !\n", cobj->object_id_ref);
    return -1;
  }

  /* find window */
  window_def_t *wnd = this->windows;
  while (wnd && wnd->id != cobj->window_id_ref)
    wnd = wnd->next;
  if (!wnd) {
    XINE_HDMV_TRACE("  show_overlay: window %d not found !\n", cobj->window_id_ref);
    return -1;
  }

  /* do not show again if all elements are unchanged */
  if (!force_update && clut->shown && obj->shown && wnd->shown && cobj->shown)
    return 0;
  clut->shown = obj->shown = wnd->shown = cobj->shown = 1;

  /* copy palette to xine overlay */
  overlay.rgb_clut = 0;
  memcpy(overlay.color, clut->color, sizeof(uint32_t) * 256);
  memcpy(overlay.trans, clut->trans, sizeof(uint8_t)  * 256);

  /* copy and crop RLE image to xine overlay */
  overlay.width     = obj->width;
  overlay.height    = obj->height;

  overlay.num_rle   = obj->num_rle;
  overlay.data_size = obj->num_rle * sizeof(rle_elem_t);

  overlay.rle       = copy_crop_rle (obj, cobj);
  if (!overlay.rle)
    return -1;

  /* */

  overlay.x = /*wnd->xpos +*/ cobj->xpos;
  overlay.y = /*wnd->ypos +*/ cobj->ypos;

  overlay.unscaled    = 0;
  overlay.hili_top    = -1;
  overlay.hili_bottom = -1;
  overlay.hili_left   = -1;
  overlay.hili_right  = -1;

  XINE_HDMV_TRACE("    -> overlay: %d,%d %dx%d\n",
                  overlay.x, overlay.y, overlay.width, overlay.height);


  /* set timings */

  if (pts > 0)
    event.vpts = metronom->got_spu_packet (metronom, pts);
  else
    event.vpts = 0;


  /* generate SHOW event */

  this->stream->video_out->enable_ovl(this->stream->video_out, 1);

  if (this->overlay_handles[overlay_index] < 0)
    this->overlay_handles[overlay_index] = ovl_manager->get_handle(ovl_manager, 0);

  event.event_type     = OVERLAY_EVENT_SHOW;
  event.object.handle  = this->overlay_handles[overlay_index];
  event.object.overlay = &overlay;
  event.object.object_type = 0; /* subtitle */

  ovl_manager->add_event (ovl_manager, (void *)&event);

  return 0;
}

static void hide_overlays(spuhdmv_decoder_t *this, int64_t pts)
{
  video_overlay_event_t event = {.vpts = 0};
  int i = 0;

  while (this->overlay_handles[i] >= 0) {
    XINE_HDMV_TRACE("    -> HIDE %d\n", i);

    video_overlay_manager_t *ovl_manager = this->stream->video_out->get_overlay_manager(this->stream->video_out);
    metronom_t              *metronom    = this->stream->metronom;

    event.object.handle = this->overlay_handles[i];
    event.vpts = metronom->got_spu_packet (metronom, pts);
    event.event_type = OVERLAY_EVENT_HIDE;
    event.object.overlay = NULL;
    ovl_manager->add_event (ovl_manager, (void *)&event);

    //this->overlay_handles[i] = -1;
    i++;
  }
}

static void update_overlays(spuhdmv_decoder_t *this)
{
  presentation_segment_t *pseg = this->segments;

  while (pseg) {

    if (!pseg->object_number) {

      /* HIDE */
      if (!pseg->shown)
        hide_overlays (this, pseg->pts);

    } else {

      /* SHOW */
      composition_object_t *cobj = pseg->comp_objs;
      int i;

      for (i = 0; i < pseg->object_number; i++) {
        if (!cobj) {
          xprintf (this->stream->xine, XINE_VERBOSITY_LOG, LOG_MODULE ": "
                   "Error displaying overlay %d: missing composition object\n", i);
        } else {
          show_overlay(this, cobj, pseg->palette_id_ref, i, pseg->pts, !pseg->shown);
          cobj = cobj->next;
        }
      }
    }

    pseg->shown = 1;

    pseg = pseg->next;
  }
}

static void decode_segment(spuhdmv_decoder_t *this)
{
  int err = 0;

  XINE_HDMV_TRACE("*** new segment, pts %010"PRId64": 0x%02x (%8d bytes)\n",
                  this->pts, this->buf->segment_type, this->buf->segment_len);

  switch (segbuf_segment_type(this->buf)) {
  case SEGTYPE_PALETTE:
    XINE_HDMV_TRACE("  segment: PALETTE\n");
    err = decode_palette(this);
    break;
  case SEGTYPE_OBJECT:
    XINE_HDMV_TRACE("  segment: OBJECT\n");
    err = decode_object(this);
    break;
  case SEGTYPE_PRESENTATION_SEGMENT:
    XINE_HDMV_TRACE("  segment: PRESENTATION SEGMENT\n");
    err = decode_presentation_segment(this);
    break;
  case SEGTYPE_WINDOW_DEFINITION:
    XINE_HDMV_TRACE("  segment: WINDOW DEFINITION\n");
    err = decode_window_definition(this);
    break;
  case SEGTYPE_INTERACTIVE:
    XINE_HDMV_TRACE("  segment: INTERACTIVE\n");
    break;
  case SEGTYPE_END_OF_DISPLAY:
    XINE_HDMV_TRACE("  segment: END OF DISPLAY\n");
#if 0
    /* drop all cached objects */
    free_objs(this);
#endif
    break;
  default:
    xprintf (this->stream->xine, XINE_VERBOSITY_LOG, LOG_MODULE ": "
             "Unknown segment type 0x%x, skipping\n", segbuf_segment_type(this->buf));
    break;
  }
  if (err || this->buf->error) {
    xprintf (this->stream->xine, XINE_VERBOSITY_LOG, LOG_MODULE ": "
             "Error decoding segment 0x%x\n", segbuf_segment_type(this->buf));
  }

  update_overlays (this);
}

static void close_osd(spuhdmv_decoder_t *this)
{
  video_overlay_manager_t *ovl_manager = this->stream->video_out->get_overlay_manager (this->stream->video_out);

  int i = 0;
  while (this->overlay_handles[i] >= 0) {
    ovl_manager->free_handle(ovl_manager, this->overlay_handles[i]);
    this->overlay_handles[i] = -1;
    i++;
  }
}

static void spudec_decode_data (spu_decoder_t * this_gen, buf_element_t * buf)
{
  spuhdmv_decoder_t *this = (spuhdmv_decoder_t *) this_gen;

  if ((buf->type & 0xffff0000) != BUF_SPU_HDMV)
    return;

  if ((this->stream->spu_channel & 0x1f) != (buf->type & 0x1f))
    return;

  if (buf->size < 1)
    return;

  if (this->current_spu_channel != buf->type) {
    xprintf (this->stream->xine, XINE_VERBOSITY_DEBUG, "spuhdmv: reset (spu channel changed)\n");
    this_gen->reset(this_gen);
    this->current_spu_channel = buf->type;
  }

  if (buf->pts)
    this->pts = buf->pts;

#ifdef DUMP_SPU_DATA
  int i;
  for(i = 0; i < buf->size; i++)
    printf(" %02x", buf->content[i]);
  printf("\n");
#endif

  segbuf_fill(this->buf, buf->content, buf->size);

  while (segbuf_segment_complete(this->buf)) {
    decode_segment(this);
    segbuf_skip_segment(this->buf);
  }
}

static void spudec_reset (spu_decoder_t * this_gen)
{
  spuhdmv_decoder_t *this = (spuhdmv_decoder_t *) this_gen;

  if (this->buf)
    segbuf_reset(this->buf);

  free_objs(this);

  close_osd(this);
}

static void spudec_discontinuity (spu_decoder_t *this_gen)
{
  spuhdmv_decoder_t *this = (spuhdmv_decoder_t *) this_gen;

  close_osd(this);
}

static void spudec_dispose (spu_decoder_t *this_gen)
{
  spuhdmv_decoder_t  *this = (spuhdmv_decoder_t *) this_gen;

  close_osd (this);
  segbuf_dispose (this->buf);

  free_objs(this);

  free (this);
}

static spu_decoder_t *open_plugin (spu_decoder_class_t *class_gen, xine_stream_t *stream)
{
  spuhdmv_decoder_t *this;

  (void)class_gen;

  this = (spuhdmv_decoder_t *) calloc(1, sizeof (spuhdmv_decoder_t));
  if (!this)
    return NULL;

  this->buf = segbuf_init();
  if (!this->buf) {
    free(this);
    return NULL;
  }

  this->spu_decoder.decode_data         = spudec_decode_data;
  this->spu_decoder.reset               = spudec_reset;
  this->spu_decoder.discontinuity       = spudec_discontinuity;
  this->spu_decoder.dispose             = spudec_dispose;
  this->spu_decoder.get_interact_info   = NULL;
  this->spu_decoder.set_button          = NULL;
  this->stream                          = stream;

  memset(this->overlay_handles, 0xff, sizeof(this->overlay_handles)); /* --> -1 */

  return &this->spu_decoder;
}

static void *init_plugin (xine_t *xine, const void *data)
{
  (void)xine;
  (void)data;

  static const spu_decoder_class_t decode_spu_hdmv_class = {
    .open_plugin = open_plugin,
    .identifier  = "spuhdmv",
    .description = "HDMV/BluRay bitmap SPU decoder plugin",
    .dispose     = NULL,
  };
  return (void *)&decode_spu_hdmv_class;
}

/* plugin catalog information */
static const uint32_t supported_types[] = { BUF_SPU_HDMV, 0 };

static const decoder_info_t dec_info_data = {
  .supported_types = supported_types,
  .priority        = 5,
};

const plugin_info_t xine_plugin_info[] EXPORTED = {
  /* type, API, "name", version, special_info, init_function */
  { PLUGIN_SPU_DECODER, 17, "spuhdmv", XINE_VERSION_CODE, &dec_info_data, &init_plugin },
  { PLUGIN_NONE, 0, NULL, 0, NULL, NULL }
};
