#include "../ka_scodec.h"

#include <stdint.h>
#include <string.h>
#include "ka_mem.h"
#include "ka_log.h"
#include "config.h"
#include "ka_error.h"

#define BUF_SIZE (320*1024)

typedef struct region_id_t
{
  struct region_id_t* pnext;
  int page_id;
  int id;
  int x, y;
} region_id_t;

typedef struct page_composition_t
{
  struct page_composition_t* pnext;
  int page_id;
  int timeout;
  int version;
  int state;
  region_id_t* pregion_id;
  uint8_t    pixel_buffer[BUF_SIZE];
} page_composition_t;

typedef struct object_id_t
{
  struct object_id_t* pnext;
  int id;
  int type;
  int flag;
  int x;
  int y;
  int fg;
  int bg;
} object_id_t;

typedef struct region_composition_t
{
  struct region_composition_t* pnext;
  int page_id;
  int reg_id;
  int version;
  int flags;
  int width;
  int height;
  int compatibility;
  int depth;
  int clut_id;
  int pixel_code;
  object_id_t* pobjectid;
  uint8_t* buf;     // assigned from part of page pixel_buffer
  uint8_t* buf_end;
} region_composition_t;

typedef struct clut_t
{
  struct clut_t* pnext;
  int page_id;
  int clut_id;
  int version;
  int component_type;
  int bit_depth;
  int dynamic_range;
  ka_subcolor palette2[4];
  ka_subcolor palette4[16];
  ka_subcolor palette8[256];
} clut_t;

typedef struct object_t
{
  struct object_t* pnext;
  int page_id;
  int object_id;
  int version;
  int coding_method;
  int non_modifying_color_flag;
  struct
  {
    int topsize;
    int bottomsize;
  } bitmap;
  struct
  {
    int nr_chars;
    char* data;
  } text;
  struct
  {
    int w;
    int h;
    int zlen;
  } zlib;
} object_t;

typedef struct display_definition_t
{
  struct display_definition_t* pnext;
  int page_id;
  int version;
  int window_flag;
  int width;
  int height;
  int x0, y0, x1, y1;
} display_definition_t;

typedef struct
{
  int hpos;
  int width;
  int shift_len;
  int shift_integer_part;
  int shift_fract_part;
  int interval_duration;
  int division_count;
  int interval_count[256];
  int shift_update_integer_part[256];
} sub_region_t;

typedef struct disparity_signaling_t
{
  struct disparity_signaling_t* pnext;
  int page_id;
  int version;
  int shift_flag;
  int shift;
  int shift_len;
  int interval_duration;
  int division_count;
  int interval_count[256];
  int shift_update_integer_part[256];
  sub_region_t subregion[256];
} disparity_signaling_t;

typedef struct
{
  ka_scodec_t hdr;
  int      filled;
  uint32_t last_pts;
  int      parsed;
  uint32_t section;
  int      nrcolors;
  uint32_t palette[256];
  region_id_t* pregion_ids;
  page_composition_t* ppage_compositions;
  region_composition_t* pregion_compositions;
  clut_t* pcluts;
  clut_t* palternative_cluts;
  object_t* pobjects;
  display_definition_t* pdisplay_definitions;
  disparity_signaling_t* pdisparity_signalings;
  uint8_t  buf[BUF_SIZE];
} data_t;

typedef struct
{
  const uint8_t* start;
  const uint8_t* end;
  uint32_t bits;
  uint32_t buf;
} bitbuffer;

static void initbits(bitbuffer* gb, const uint8_t* start, const uint8_t* end)
{
  gb->start = start + 4;
  gb->end = end;
  gb->bits = 32;
  gb->buf = (start[0] << 24) + (start[1] << 16) + (start[2] << 8) + start[3];;
}

static uint32_t getbits(bitbuffer* gb, int count)
{
  uint32_t val;

  if ((gb->bits < 24) && (gb->start < gb->end))
  {
    gb->buf |= (*gb->start++) << (24 - gb->bits);
    gb->bits += 8;
  }

  if (gb->bits >= count)
  {
    val = gb->buf >> (32 - count);
    gb->buf <<= count;
    gb->bits -= count;
  }
  else val = 0;

  return val;
}

static ka_scodec_t* subs_dvb_new(const ka_sparams_t* params)
{
  data_t* data = ka_mem_calloc(sizeof(*data));

  if (!data)
  {
    ka_error_fill(params->pErrorBlock, ka_error_nomem);
    return NULL;
  }

  data->hdr.vptr = &kav_scodec_dvb;
  data->hdr.vars = params->vars;
  data->hdr.pErrorBlock = params->pErrorBlock;

  data->filled = 0;

  return &data->hdr;
}

static void subs_dvb_delete(ka_scodec_t** ppcodec)
{
  data_t* data = (data_t*) *ppcodec;
  *ppcodec = NULL;

  if (!data)
    return;

  ka_mem_free(data);
}

static int decode_run_2bit(bitbuffer* gb, int* color)
{
  unsigned int v;
  int len;

  v = getbits(gb, 2);
  if (v)
  {
    *color = v;
    len = 1;
  }
  else
  {
    v = getbits(gb, 1);
    if (v)
    {
      len = getbits(gb, 3) + 3;
      *color = getbits(gb, 2);
    }
    else
    {
      v = getbits(gb, 1);
      if (v)
      {
        len = 1;
        *color = 0;
      }
      else
      {
        v = getbits(gb, 2);
        len = 0;
        switch(v)
        {
          case 0: *color = 0; len = -1; break;
          case 1: *color = 0; len = 2; break;
          case 2:
          {
            len = getbits(gb, 4) + 12;
            *color = getbits(gb, 2);
          }
          break;
          case 3:
          {
            len = getbits(gb, 8) + 29;
            *color = getbits(gb, 2);
          }
          break;
        }
      }
    }
  }

  return len;
}

static int decode_run_4bit(bitbuffer* gb, int* color)
{
  unsigned int v;
  int len;

  v = getbits(gb, 4);
  if (v)
  {
    *color = v;
    len = 1;
  }
  else
  {
    v = getbits(gb, 1);
    if (!v)
    {
      len = getbits(gb, 3);
      if (len)
        len += 2;
      else
        len = -1;
      *color = 0;
    }
    else
    {
      v = getbits(gb, 1);
      if (!v)
      {
        len = getbits(gb, 2) + 4;
        *color = getbits(gb, 4);
      }
      else
      {
        v = getbits(gb, 2);
        len = 0;
        switch(v)
        {
          case 0: *color = 0; len = 1; break;
          case 1: *color = 0; len = 2; break;
          case 2:
          {
            len = getbits(gb, 4) + 9;
            *color = getbits(gb, 4);
          }
          break;
          case 3:
          {
            len = getbits(gb, 8) + 25;
            *color = getbits(gb, 4);
          }
          break;
        }
      }
    }
  }

  return len;
}

static int decode_run_8bit(bitbuffer* gb, int* color)
{
  unsigned int v;
  int len;

  v = getbits(gb, 8);
  if (v)
  {
    *color = v;
    len = 1;
  }
  else
  {
    v = getbits(gb, 1);
    if (!v)
    {
      len = getbits(gb, 7);
      if (!len) len = -1;
      *color = 0;
    }
    else
    {
      len = getbits(gb, 7);
      *color = getbits(gb, 8);
    }
  }

  return len;
}

static int decode_rle(int depth, uint8_t* p, int stride, int w, int h
               , const uint8_t* start, const uint8_t* end, int non_modifying)
{
  bitbuffer gb;
  int x, y, len, color;
  uint8_t map2to4[] = {  0x0,  0x7,  0x8,  0xf};
  uint8_t map2to8[] = { 0x00, 0x77, 0x88, 0xff};
  uint8_t map4to8[] = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77
                      , 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff
                      };

  if (start >= end)
    return -1;

  if (w <= 0 || h <= 0)
    return -1;

  initbits(&gb, start, end);

  x = 0;
  y = 0;
  while(y < h)
  {
    int type = getbits(&gb, 8);
    len = 0;
    switch(type)
    {
      case 0x10:
        len = decode_run_2bit(&gb, &color);
        // byte align
        if (gb.bits & 7)
          getbits(&gb, gb.bits & 7);

        if (depth == 8)
          color = map2to8[color];
        else if (depth == 4)
          color = map2to4[color];
      break;
      case 0x11:
        len = decode_run_4bit(&gb, &color);
        // byte align
        if (gb.bits & 7)
          getbits(&gb, gb.bits & 7);

        if (depth == 8)
          color = map4to8[color];
        else if (depth == 2)
          return -1; // 4-bit for bit depth 2
      break;
      case 0x12:
        len = decode_run_8bit(&gb, &color);
        if (depth != 8)
          return -1; // 8-bit for bit depth  < 8
      break;
      case 0x20: // 2 to 4-bit map table
        map2to4[0] = getbits(&gb, 4);
        map2to4[1] = getbits(&gb, 4);
        map2to4[2] = getbits(&gb, 4);
        map2to4[3] = getbits(&gb, 4);
      break;
      case 0x21: // 2 to 8-bit map table
        map2to8[0] = getbits(&gb, 8);
        map2to8[1] = getbits(&gb, 8);
        map2to8[2] = getbits(&gb, 8);
        map2to8[3] = getbits(&gb, 8);
      break;
      case 0x22: // 4 to 8-bit map table
        for (int i = 0; i < 16; i++)
        map4to8[i] = getbits(&gb, 8);
      break;
      case 0xf0: // end of line
        len = -1;
      break;
    }

    if (len == -1)
    {
      y++;
      p += stride;
      x = 0;
    }
    else if (len && (y < h))
    {
      if (len > w - x)
        len = w - x;
      if (!non_modifying || (color != 1))
        memset(p + x, color, len);
      x += len;
    }
  }

  return 0;
}

static inline uint32_t read_offset(const uint8_t* buf, int bigoffsets)
{
  uint32_t a = *buf;
  a = buf[1] + (a << 8);
  if (bigoffsets)
  {
    a = buf[2] + (a << 8);
    a = buf[3] + (a << 8);
  }

  return a;
}

static page_composition_t* get_page(data_t* data, uint32_t page_id, uint32_t version)
{
  page_composition_t** pppage = &data->ppage_compositions;
  page_composition_t* ppage;

  while (*pppage != NULL)
  {
    ppage = *pppage;
    if ((ppage->page_id == page_id) && (ppage->version == version))
      return ppage;

    pppage = &ppage->pnext;
  }

  *pppage = ppage = ka_mem_calloc(sizeof(*ppage));
  if (ppage == NULL)
    return NULL;

  ppage->page_id = page_id;
  ppage->version = version;

  return ppage;
}

static region_composition_t* get_region(data_t* data, uint32_t page_id, uint32_t region_id, uint32_t version)
{
  region_composition_t** ppregion = &data->pregion_compositions;
  region_composition_t* pregion;

  while (*ppregion != NULL)
  {
    pregion = *ppregion;
    if ((pregion->page_id == page_id)
    &&  (pregion->reg_id == region_id)
    &&  (pregion->version == version))
      return pregion;

    ppregion = &pregion->pnext;
  }

  *ppregion = pregion = ka_mem_calloc(sizeof(*pregion));
  if (pregion == NULL)
    return NULL;

  pregion->page_id = page_id;
  pregion->reg_id = region_id;
  pregion->version = version;

  return pregion;
}

static clut_t* get_clut(data_t* data, uint32_t page_id, uint32_t clut_id, uint32_t version)
{
  clut_t** ppclut = &data->pcluts;
  clut_t* pclut;

  while (*ppclut != NULL)
  {
    pclut = *ppclut;
    if ((pclut->page_id == page_id)
    &&  (pclut->clut_id == clut_id)
    &&  (pclut->version == version))
      return pclut;

    ppclut = &pclut->pnext;
  }

  *ppclut = pclut = ka_mem_calloc(sizeof(*pclut));
  if (pclut == NULL)
    return NULL;

  pclut->page_id = page_id;
  pclut->clut_id = clut_id;
  pclut->version = version;

  return pclut;
}

static clut_t* get_alternative_clut(data_t* data, uint32_t page_id, uint32_t clut_id, uint32_t version)
{
  clut_t** ppclut = &data->palternative_cluts;
  clut_t* pclut;

  while (*ppclut != NULL)
  {
    pclut = *ppclut;
    if ((pclut->page_id == page_id)
    &&  (pclut->clut_id == clut_id)
    &&  (pclut->version == version))
      return pclut;

    ppclut = &pclut->pnext;
  }

  *ppclut = pclut = ka_mem_calloc(sizeof(*pclut));
  if (pclut == NULL)
    return NULL;

  pclut->page_id = page_id;
  pclut->clut_id = clut_id;
  pclut->version = version;

  return pclut;
}

static object_t* get_object(data_t* data, uint32_t page_id, uint32_t object_id, uint32_t version)
{
  object_t** ppobject = &data->pobjects;
  object_t* pobject;

  while (*ppobject != NULL)
  {
    pobject = *ppobject;
    if ((pobject->page_id == page_id)
    &&  (pobject->object_id == object_id)
    &&  (pobject->version == version))
      return pobject;

    ppobject = &pobject->pnext;
  }

  *ppobject = pobject = ka_mem_calloc(sizeof(*pobject));
  if (pobject == NULL)
    return NULL;

  pobject->page_id = page_id;
  pobject->object_id = object_id;
  pobject->version = version;

  return pobject;
}

static display_definition_t* get_display_definition(data_t* data, uint32_t page_id, uint32_t version)
{
  display_definition_t** ppdef = &data->pdisplay_definitions;
  display_definition_t* pdef;

  while (*ppdef != NULL)
  {
    pdef = *ppdef;
    if ((pdef->page_id == page_id)
    &&  (pdef->version == version))
      return pdef;

    ppdef = &pdef->pnext;
  }

  *ppdef = pdef = ka_mem_calloc(sizeof(*pdef));
  if (pdef == NULL)
    return NULL;

  pdef->page_id = page_id;
  pdef->version = version;
  pdef->width = pdef->x1 = 720;
  pdef->height = pdef->y1 = 576;

  return pdef;
}

static disparity_signaling_t* get_disparity_signaling(data_t* data, uint32_t page_id, uint32_t version)
{
  disparity_signaling_t** ppsig = &data->pdisparity_signalings;
  disparity_signaling_t* psig;

  while (*ppsig != NULL)
  {
    psig = *ppsig;
    if ((psig->page_id == page_id)
    &&  (psig->version == version))
      return psig;

    ppsig = &psig->pnext;
  }

  *ppsig = psig = ka_mem_calloc(sizeof(*psig));
  if (psig == NULL)
    return NULL;

  psig->page_id = page_id;
  psig->version = version;

  return psig;
}

static int subs_dvb_decode(ka_scodec_t* pcodec, ka_subtitles_t* subs, ka_block_t* b)
{
  data_t* data = (data_t*) pcodec;
  int size = b->eod - b->sod;

  // if pts, start of new data
  if (b->pts)
  {
    if ((data->parsed == 0) && (data->filled > 0))
    {
      ka_log(ka_log_error | ka_log_subtitle, "DVB subtitle, discarded as incomplete");
    }

    if (config.debug & cfg_printsubtitlestats)
      ka_log(ka_log_subtitle, "DVB subtile, new packet");
    data->filled = 0;
    data->last_pts = b->pts;
    data->parsed = 0;
  }

  // merge in buffer
  if (BUF_SIZE < data->filled + size)
  {
    ka_log(ka_log_error | ka_log_subtitle, "DVB subtile too large for buffer");
    // extend size as if read
    data->filled = BUF_SIZE;
    return 1; // consumed
  }

  memcpy(data->buf + data->filled, b->sod, size);
//ka_log_dump(b->sod, size);
  data->filled += size;

  // decode
  if ((data->filled < 10) || data->parsed)
    return 1;

  const uint8_t* buf;

  // complete ?
  uint32_t segment_type = 0;
  buf = data->buf + 2; // 0x20 0x00

  while ((buf + 6 < data->buf + data->filled) && (buf[0] == 0x0f))
  {
    segment_type = buf[1];
    uint32_t segment_size = (buf[4] << 8) + buf[5];
    buf += 6;
//    if (segment_type == 0x80)
//      break;
    buf += segment_size;

    // incomplete or corrupted
    if (buf > data->buf + data->filled)
    {
      return 1;
    }
  }

  if ((buf >= data->buf + data->filled) || (buf[0] != 0xff))
  {
    ka_log(ka_log_error | ka_log_subtitle, "DVB subtitle, corrupted");
    return 1;
  }

  if (segment_type != 0x80)
  {
    ka_log(ka_log_error | ka_log_subtitle, "DVB subtile without 'end of display set'");
    return 1;
  }

  segment_type = 0;
  buf = data->buf + 2; // 0x20 0x00
  while ((buf + 6 < data->buf + data->filled) && (buf[0] == 0x0f))
  {
    segment_type = buf[1];
    uint32_t page_id = (buf[2] << 8) + buf[3];
    uint32_t segment_size = (buf[4] << 8) + buf[5];
    uint32_t version;
    uint32_t segment_offset = buf - data->buf;
    buf += 6;
    const uint8_t* segment_end = buf + segment_size;

    if (segment_end > data->buf + data->filled)
    {
      ka_log(ka_log_error | ka_log_subtitle
           , "DVB subtile segment at %d, type %02x, page_id %04x, size %d exceeds total size %d."
           , segment_offset, segment_type, page_id, segment_end - buf, data->filled
           );
      goto fail;
    }

    if (config.debug & cfg_printsubtitlestats)
       ka_log( ka_log_subtitle
             , "DVB subtitle segment  at %d, type %02x, page_id %04x. length %d."
             , segment_offset, segment_type, page_id, 6 + (segment_end - buf)
             );

    // segment type
    switch(segment_type)
    {
      case 0x10: // page composition
      {
        if (buf + 2 > segment_end)
        {
          ka_log(ka_log_error | ka_log_subtitle
               , "DVB subtile segment at %d, type %02x, page_id %04x. Page composition does not fit."
               , segment_offset, segment_type, page_id
               );
          goto fail;
        }

        page_composition_t* ppage = get_page(data, page_id, buf[1] >> 4);
        if (ppage == NULL)
        {
          ka_log(ka_log_error | ka_log_subtitle, "Out of memory");
          goto fail;
        }
        ppage->timeout = buf[0];
        ppage->state = buf[1] & 0xf;
        ppage->pregion_id = NULL;
        buf += 2;

        region_id_t** ppregid = &ppage->pregion_id;
        while (buf + 6 <= segment_end)
        {
          region_id_t* pregid = ka_mem_calloc(sizeof(*pregid));
          if (pregid == NULL)
          {
            ka_log(ka_log_error | ka_log_subtitle, "Out of memory");
            goto fail;
          }
          *ppregid = pregid;
          ppregid = &pregid->pnext;
          pregid->page_id = page_id;
          pregid->id = buf[0];
          pregid->x = (buf[1] << 8) + buf[2];
          pregid->y = (buf[2] << 8) + buf[3];
          buf += 6;
        }
      }
      break;
      case 0x11: // region composition
      {
        if (buf + 10 > segment_end)
        {
          ka_log(ka_log_error | ka_log_subtitle
               , "DVB subtitle segment at %d, type %02x, page_id %04x. Region composition does not fit."
               , segment_offset, segment_type, page_id
               );
          goto fail;
        }

        region_composition_t* preg = get_region(data, page_id, buf[0], buf[1] >> 4);
        if (preg == NULL)
        {
          ka_log(ka_log_error | ka_log_subtitle, "Out of memory");
          goto fail;
        }
        preg->flags = (buf[1] & 0xf) & 8;
        preg->width = 1 + (buf[2] << 8) + buf[3];
        preg->height = 1 + (buf[4] << 8) + buf[5];
        preg->compatibility = buf[6] >> 5;
        preg->depth = (buf[6] & 0x1c) >> 2;
        preg->clut_id = buf[7];
        preg->pixel_code = (buf[8] << 8) + buf[9];

        object_id_t** ppobjid = &preg->pobjectid;
        while (buf + 6 <= segment_end)
        {
          object_id_t* pobjid = ka_mem_calloc(sizeof(*pobjid));
          if (pobjid == NULL)
          {
            ka_log(ka_log_error | ka_log_subtitle, "Out of memory");
            goto fail;
          }
          *ppobjid = pobjid;
          ppobjid = &pobjid->pnext;
          pobjid->id =(buf[0] << 8) + buf[1];
          pobjid->type = (buf[2] & 0x30) >> 4;
          pobjid->x = ((buf[2] & 0xf) << 8) + buf[3];
          pobjid->y = ((buf[4] & 0xf) << 8) + buf[5];
          buf += 6;
          if ((pobjid->type == 1) || (pobjid->type == 2))
          {
            if (buf + 2 > segment_end)
            {
              ka_log(ka_log_error | ka_log_subtitle
                   , "DVB subtitle segment at %d, type %02x, page_id %04x. Object at %d does not fit."
                   , segment_offset, segment_type, page_id, buf - data->buf
                   );
              goto fail;
            }

            pobjid->fg = buf[0];
            pobjid->bg = buf[1];
            buf += 2;
          }
        }
      }
      break;
      case 0x12: // CLUT definition
      {
        if (buf + 2 > segment_end)
        {
          ka_log(ka_log_error | ka_log_subtitle
               , "DVB subtitle segment  at %d, type %02x, page_id %04x. CLUT does not fit."
               , segment_offset, segment_type, page_id
               );
          goto fail;
        }

        clut_t* pclut = get_clut(data, page_id, buf[0], buf[1] >> 4);
        if (pclut == NULL)
        {
          ka_log(ka_log_error | ka_log_subtitle, "Out of memory");
          goto fail;
        }
        buf += 2;
        while (buf + 4 <= segment_end)
        {
          int i = buf[0];
          int flags = buf[1];
          ka_subcolor val;

          if (flags & 0x01)
          {
            if (buf + 6 > segment_end)
            {
              ka_log(ka_log_error | ka_log_subtitle
                   , "DVB subtitle segment  at %d, type %02x, page_id %04x. CLUT does not fit. Flags %02x."
                   , segment_offset, segment_type, page_id, flags
                   );
              goto fail;
            }
            val.y = buf[2];
            val.u = buf[3];
            val.v = buf[4];
            if (val.y)
              val.a = 255 - buf[5];
            else
              val.a = 0;
            buf += 6;
          }
          else
          {
            val.y = buf[2] & 0xfc;
            val.u = ((buf[2] & 0x3) << 6) + ((buf[3] & 0xc0) << 4);
            val.v = (buf[3] & 0x3c) << 2;
            if (val.y)
              val.a = 255 - ((buf[3] & 0x3) << 6);
            else
              val.a = 0;
            buf += 4;
          }

          if (flags & 0x80)
            pclut->palette2[i & 3] = val;
          if (flags & 0x40)
            pclut->palette4[i & 0xf] = val;
          if (flags & 0x20)
            pclut->palette8[i] = val;
        }
      }
      break;
      case 0x13: // object data
      {
        if (buf + 3 > segment_end)
        {
          ka_log(ka_log_error | ka_log_subtitle
               , "DVB subtitle segment at %d, type %02x, page_id %04x. Object data does not fit."
               , segment_offset, segment_type, page_id
               );
          goto fail;
        }

        object_t* pobj = get_object(data, page_id, (buf[0] << 8) + buf[1], buf[2] >> 4);
        if (pobj == NULL)
        {
          ka_log(ka_log_error | ka_log_subtitle, "Out of memory");
          goto fail;
        }
        pobj->coding_method = (buf[2] >> 2) & 0x3;
        pobj->non_modifying_color_flag = (buf[2] & 0x2) >> 1;
        buf += 3;

        switch(pobj->coding_method)
        {
          case 0:
          {
            if (buf + 4 > segment_end)
            {
              ka_log(ka_log_error | ka_log_subtitle
                   , "DVB subtitle segment at %d, type %02x, page_id %04x. Object bitmap does not fit."
                   , segment_offset, segment_type, page_id
                   );
              goto fail;
            }
            pobj->bitmap.topsize = (buf[0] << 8) + buf[1];
            pobj->bitmap.bottomsize = (buf[2] << 8) + buf[3];
            int size = pobj->bitmap.topsize;

            buf += 4;
            if (buf + pobj->bitmap.topsize + pobj->bitmap.bottomsize > segment_end)
            {
              ka_log(ka_log_error | ka_log_subtitle
                   , "DVB subtitle segment at %d, type %02x, page_id %04x. Object bitmap does not fit."
                   , segment_offset, segment_type, page_id
                   );
              goto fail;
            }

//            decode_rle(, p, w * 2, w, (h + 1) / 2, buf, buf + size);
            if (pobj->bitmap.bottomsize)
            {
              buf += pobj->bitmap.topsize;
              size = pobj->bitmap.bottomsize;
            }

//            decode_rle(, p + w, w * 2, w, h / 2, buf, buf + size);
          }
          break;
          case 1:
          {
            if (buf + 1 > segment_end)
            {
              ka_log(ka_log_error | ka_log_subtitle
                   , "DVB subtitle segment at %d, type %02x, page_id %04x. Object text does not fit."
                   , segment_offset, segment_type, page_id
                   );
              goto fail;
            }
            pobj->text.nr_chars = buf[0];
            buf++;
            if (buf + 2*pobj->text.nr_chars > segment_end)
            {
              ka_log(ka_log_error | ka_log_subtitle
                   , "DVB subtitle segment at %d, type %02x, page_id %04x. Object text does not fit."
                   , segment_offset, segment_type, page_id
                   );
              goto fail;
            }
            pobj->text.data = ka_mem_alloc(2*pobj->text.nr_chars);
            if (pobj->text.data == NULL)
            {
              ka_log(ka_log_error | ka_log_subtitle, "Out of memory");
              goto fail;
            }
            memcpy(pobj->text.data, buf, 2*pobj->text.nr_chars);
            buf += 2*pobj->text.nr_chars;
          }
          break;
          case 2:
          {
            if (buf + 6 > segment_end)
            {
              ka_log(ka_log_error | ka_log_subtitle
                   , "DVB subtitle segment at %d, type %02x, page_id %04x. Object PNG does not fit."
                   , segment_offset, segment_type, page_id
                   );
              goto fail;
            }
            pobj->zlib.w = (buf[0] << 8) + buf[1];
            pobj->zlib.h = (buf[2] << 8) + buf[3];
            pobj->zlib.zlen = (buf[4] << 8) + buf[5];
            buf += 6;
            if (buf + pobj->zlib.zlen > segment_end)
            {
              ka_log(ka_log_error | ka_log_subtitle
                   , "DVB subtitle segment at %d, type %02x, page_id %04x. Object PNG does not fit."
                   , segment_offset, segment_type, page_id
                   );
              goto fail;
            }
            // zlib deflated data
            // ...
          }
          break;
        }
      }
      break;
      case 0x14: // display definition
      {
        if (buf + 5 > segment_end)
        {
          ka_log(ka_log_error | ka_log_subtitle
               , "DVB subtitle segment at %d, type %02x, page_id %04x. Display definition does not fit."
               , segment_offset, segment_type, page_id
               );
          goto fail;
        }

        display_definition_t* pdef = get_display_definition(data, page_id, buf[0] >> 4);
        if (pdef == NULL)
        {
          ka_log(ka_log_error | ka_log_subtitle, "Out of memory");
          goto fail;
        }
        pdef->window_flag = (buf[0] & 0xf) & 8;
        pdef->width = 1 + (buf[1] << 8) + buf[2];
        pdef->height = 1 + (buf[3] << 8) + buf[4];
        buf += 5;
        if (pdef->window_flag)
        {
          if (buf + 8 > segment_end)
          {
            ka_log(ka_log_error | ka_log_subtitle
                 , "DVB subtitle segment at %d, type %02x, page_id %04x. Display definition does not fit."
                 , segment_offset, segment_type, page_id
                 );
             goto fail;
          }

          pdef->x0 = (buf[0] << 8) + buf[1];
          pdef->x1 = 1 + (buf[2] << 8) + buf[3];
          pdef->y0 = (buf[4] << 8) + buf[5];
          pdef->y1 = 1 + (buf[6] << 8) + buf[7];
          buf += 8;
        }
      }
      break;
      case 0x15: // disparity signaling
      {
        if (buf + 2 > segment_end)
        {
          ka_log(ka_log_error | ka_log_subtitle
               , "DVB subtitle segment at offset %d, type %02x, page_id %04x. Disparity signaling does not fit."
               , segment_offset, segment_type, page_id
               );
          goto fail;
        }

        disparity_signaling_t* pdisp = get_disparity_signaling(data, page_id, buf[0] >> 4);
        if (pdisp == NULL)
        {
          ka_log(ka_log_error | ka_log_subtitle, "Out of memory");
          goto fail;
        }
        pdisp->shift_flag = (buf[0] >> 3) & 0x1;
        pdisp->shift = buf[1];
        buf += 6;
        if (pdisp->shift_flag)
        {
          if (buf + 5 > segment_end)
          {
            ka_log(ka_log_error | ka_log_subtitle
                 , "DVB subtitle segment at offset %d, type %02x, page_id %04x. Disparity signaling does not fit."
                 , segment_offset, segment_type, page_id
                 );
            goto fail;
          }
          pdisp->shift_len = buf[0];
          pdisp->interval_duration = (buf[1] << 16) + (buf[2] << 8) + buf[3];
          pdisp->division_count = buf[4];
          buf += 5;
          if (buf + (2 * pdisp->shift_len) > segment_end)
          {
            ka_log(ka_log_error | ka_log_subtitle
                 , "DVB subtitle segment at offset %d, type %02x, page_id %04x. Disparity signaling does not fit."
                 , segment_offset, segment_type, page_id
                 );
            goto fail;
          }

          for (int i = pdisp->shift_len; i > 0; i--)
          {
            pdisp->interval_count[i] = *buf++;
            pdisp->shift_update_integer_part[i] = *buf++;
          }
        }

        while (buf + 2 <= segment_end)
        {
          int regid = buf[0];
          int shift_flag = buf[1] >> 7;
          int nr_subregions = 1 + (buf[1] & 3);
          buf += 2;
          for (int i = 0; i < nr_subregions; i++)
          {
            sub_region_t* psub = &pdisp->subregion[i];
            if (nr_subregions > 1)
            {
              if (buf + 4 > segment_end)
              {
                ka_log(ka_log_error | ka_log_subtitle
                     , "DVB subtitle segment at offset %d, type %02x, page_id %04x. Subregion at %d does not fit."
                     , segment_offset, segment_type, page_id, buf - data->buf
                     );
                goto fail;
              }
              psub->hpos = (buf[0] << 8) + buf[1];
              psub->width = (buf[2] << 8) + buf[3];
              buf += 4;
            }
            else
            {
              psub->hpos = 0;
              psub->width = 0;
            }

            if (buf + 2 > segment_end)
            {
              ka_log(ka_log_error | ka_log_subtitle
                   , "DVB subtitle segment at offset %d, type %02x, page_id %04x. Subregion at %d does not fit."
                   , segment_offset, segment_type, page_id, buf - data->buf
                   );
              goto fail;
            }
            psub->shift_integer_part = *buf++;
            psub->shift_fract_part = (*buf++) >> 4;
            if (shift_flag)
            {
              if (buf + 5 > segment_end)
              {
                ka_log(ka_log_error | ka_log_subtitle
                     , "DVB subtitle segment at offset %d, type %02x, page_id %04x. Subregion at %d does not fit."
                     , segment_offset, segment_type, page_id, buf - data->buf
                     );
                goto fail;
              }
              psub->shift_len = buf[0];
              psub->interval_duration = (buf[1] << 16) + (buf[2] << 8) + buf[3];
              psub->division_count = buf[4];
              buf += 5;
              if (buf + (2 * psub->shift_len) > segment_end)
              {
                ka_log(ka_log_error | ka_log_subtitle
                     , "DVB subtitle segment at offset %d, type %02x, page_id %04x. Subregion at %d does not fit."
                     , segment_offset, segment_type, page_id, buf - data->buf
                     );
                goto fail;
              }
              for (int i = psub->shift_len; i > 0; i--)
              {
                 psub->interval_count[i] = *buf++;
                 psub->shift_update_integer_part[i] = *buf++;
              }
            }
          }
        }
      }
      break;
      case 0x16: // alternative CLUT
      {
        if (buf + 4 > segment_end)
        {
          ka_log(ka_log_error | ka_log_subtitle
               , "DVB subtitle segment at %d, type %02x, page_id %04x. Alternative CLUT does not fit."
               , segment_offset, segment_type, page_id
               );
          goto fail;
        }

        clut_t* pclut = get_alternative_clut(data, page_id, buf[0], buf[1] >> 4);
        if (pclut == NULL)
        {
          ka_log(ka_log_error | ka_log_subtitle, "Out of memory");
          goto fail;
        }
        pclut->component_type = (buf[2] >> 4) & 2;
        if (pclut->component_type > 1)
        {
          ka_log(ka_log_error | ka_log_subtitle
               , "DVB subtitle segment at %d, type %02x, page_id %04x. Alternative CLUT invalid type %02x."
               , segment_offset, segment_type, page_id, pclut->component_type
               );
          goto fail;
        }
        pclut->bit_depth = (buf[2] >> 1) & 3;
        if (pclut->bit_depth > 1)
        {
          ka_log(ka_log_error | ka_log_subtitle
               , "DVB subtitle segment at %d, type %02x, page_id %04x. Alternative CLUT invalid bit depth %d."
               , segment_offset, segment_type, page_id, pclut->bit_depth
               );
          goto fail;
        }
        pclut->dynamic_range = buf[3];
        buf += 4;
        while (buf + 4 <= segment_end)
        {
          int i = buf[0];
          int flags = buf[1];
          ka_subcolor val;

          if (pclut->component_type)
          {
            if (pclut->bit_depth)
            {
              val.y = buf[0];
              val.v = ((buf[1] & 0x3f) << 2) + (buf[2] >> 6);
              val.u = ((buf[2] & 0x0f) << 4) + (buf[3] >> 4);
              if (val.y)
                val.a = 255 - (((buf[3] & 0x3) << 6) + (buf[4] >> 2));
              else
                val.a = 0;

              buf += 5;
            }
            else
            {
              val.y = buf[0];
              val.v = buf[1];
              val.u = buf[2];
              if (val.y)
                val.a = 255 - buf[3];
              else
                val.a = 0;

              buf += 4;
            }
          }
          else
          {
            if (pclut->bit_depth)
            {
              val.y = buf[0];
              val.u = ((buf[1] & 0x3f) << 2) + (buf[2] >> 6);
              val.v = ((buf[2] & 0x0f) << 4) + (buf[3] >> 4);
              if (val.y)
                val.a = 255 - (((buf[3] & 0x3) << 6) + (buf[4] >> 2));
              else
                val.a = 0;
              buf += 5;
            }
            else
            {
              val.y = buf[0];
              val.u = buf[1];
              val.v = buf[2];
              if (val.y)
                val.a = 255 - buf[3];
              else
                val.a = 0;
              buf += 4;
            }
          }
          pclut->palette8[i] = val;
        }
      }
      break;
      case 0x80: // end of display set
      case 0xff: // stuffing
      break;
      default: // 0x81-EF, private data, 0x17-7F reserved
        ;
    }

    buf = segment_end;
  }

  if (segment_type != 0x80)
  {
    ka_log(ka_log_error | ka_log_subtitle, "Expected end of data field marker");
  }

  data->parsed = 1;

  return 1;

fail:

  data->parsed = 1;

  return 1; // consumed
}

const kav_scodec_t kav_scodec_dvb =
{
  .name = "DVB"
, .FNnew             = subs_dvb_new
, .FNdelete          = subs_dvb_delete
, .FNdecode          = subs_dvb_decode
};
