#include "ka_subtitles.h"

#include <string.h>
#include "ka_codecs.h"
#include "ka_mem.h"
#include "ka_scodec.h"
#include "ka_vars.h"
#include "ka_log.h"
#include "timer1.h"
#include "config.h"

typedef struct codec_info
{
  struct codec_info* next;
  uint32_t typ;
  ka_scodec_t* pcodec;
} codec_info;

struct ka_subtitles_s
{
  ka_error_t*  pErrorBlock;
  codec_info*  pcodecs;
  ka_sub_rect* first;
  ka_sub_rect* last;
  ka_vars_t*   vars;
};

ka_subtitles_t* ka_new_subtitles(ka_error_t* pErrorBlock, ka_vars_t* vars)
{
  ka_subtitles_t* subtitles = ka_mem_calloc(sizeof(*subtitles));

  if (!subtitles)
  {
    ka_error_fill(pErrorBlock, ka_error_nomem);
    return NULL;
  }

  subtitles->pErrorBlock = pErrorBlock;
  subtitles->vars = vars;

  return subtitles;
}

void ka_delete_subtitles(ka_subtitles_t** psubtitles)
{
  ka_subtitles_t* subtitles = *psubtitles;
  *psubtitles = NULL;

  if (!subtitles)
    return;

  ka_subtitles_reset(subtitles);
  ka_mem_free(subtitles);
}

void ka_subtitles_reset(ka_subtitles_t* subtitles)
{
  codec_info** ppinfo = &subtitles->pcodecs;
  codec_info* pinfo = *ppinfo;

  while (pinfo != NULL)
  {
    *ppinfo = pinfo->next;

    if (pinfo->pcodec)
      pinfo->pcodec->vptr->FNdelete(&pinfo->pcodec);

    ka_mem_free(pinfo);

    pinfo = *ppinfo;
  }

  while(subtitles->first)
  {
    ka_sub_rect* rect = subtitles->first;
    subtitles->first = rect->next;
    if (rect->data) ka_mem_free(rect->data);
    ka_mem_free(rect);
  }
  subtitles->last = NULL;
}

/**
 * returns 1 if consumed, 0 for retry later, -1 if memory error
 */
int ka_subtitles_decode(ka_subtitles_t* subtitles, ka_block_t* b)
{
  codec_info** ppinfo = &subtitles->pcodecs;
  codec_info* pinfo = *ppinfo;
  ka_sparams_t params;

  while (pinfo != NULL)
  {
    if (pinfo->typ == b->typ)
      break;

    ppinfo = &pinfo->next;
    pinfo = *ppinfo;
  }

  if (pinfo == NULL)
  {
    pinfo = ka_mem_calloc(sizeof(*pinfo));
    if (pinfo == NULL)
    {
      ka_error_fill(subtitles->pErrorBlock, ka_error_nomem);
      return -1;
    }

    params.typ = pinfo->typ = b->typ;
    params.pErrorBlock = subtitles->pErrorBlock;
    params.vars = subtitles->vars;
    const kav_scodec_t* vptr = ka_block_to_scodec(b->typ);

    if (vptr)
    {
      pinfo->pcodec = vptr->FNnew(&params);
      if (pinfo->pcodec == NULL)
      {
        ka_mem_free(pinfo);
        return -1;
      }
    }
    *ppinfo = pinfo;
  }

  if (pinfo->pcodec)
    return pinfo->pcodec->vptr->FNdecode(pinfo->pcodec, subtitles, b);
  else
    return 1; // unsupported, ignore
}

ka_sub_rect* ka_subtitles_getNewRect(ka_subtitles_t* subtitles)
{
  subtitles = subtitles; // unused

  return ka_mem_calloc(sizeof(ka_sub_rect));
}

void ka_subtitles_rectStatus(ka_subtitles_t* subtitles, ka_sub_rect* rect, int valid)
{
  if (valid)
  {
    if (!subtitles->first)
    {
      subtitles->first = subtitles->last = rect;
      rect->next = rect->prev = NULL;
    }
    else
    {
      ka_sub_rect* cur = subtitles->last;
      while ((cur != NULL) && (cur->typ != rect->typ))
        cur = cur->prev;

      // if previous subtitle has no duration set its end to start of new one
      if ((cur != NULL) && (cur->startDate == cur->endDate))
      {
        cur->endDate = rect->startDate - 1;
//        if (config.debug & cfg_printsubtitlestats)
//          ka_log(ka_log_subtitle
//               , "Typ %08x start %010d, fixed end %010d from Typ %08x"
//               , cur->typ, cur->startDate, cur->endDate, rect->typ);
      }

      rect->prev = subtitles->last;
      if (rect->prev)
        rect->prev->next = rect;
      rect->next = NULL;
      subtitles->last = rect;
    }

    if (config.debug & cfg_printsubtitlestats)
      ka_log(ka_log_subtitle
           , "Typ %08x forced %d start %010d, end %010d, rect (%d, %d) -> (%d, %d), %d colors"
           , rect->typ, rect->forceDisplay, rect->startDate, rect->endDate, rect->x0, rect->y0, rect->x1, rect->y1, rect->nrColors);
  }
  else
  {
    if (rect->data) ka_mem_free(rect->data);
    ka_mem_free(rect);
  }
}

const ka_sub_rect* ka_subtitles_getMatch(ka_subtitles_t* subtitles, uint32_t pts, const ka_sub_rect* current)
{
  ka_sub_rect* rect = current ? current->next : subtitles->first;

  while (rect)
  {
    // done ?
    if ((rect->startDate != rect->endDate) && (pts > rect->endDate))
    {
      // ignore timestamp rollover
      if (rect->displayed)
      {
        if (subtitles->first == rect)
          subtitles->first = rect->next;
        if (subtitles->last == rect)
          subtitles->last = rect->prev;
        if (rect->prev)
          rect->prev->next = rect->next;
        if (rect->next)
          rect->next->prev = rect->prev;

//        if (config.debug & cfg_printsubtitlestats)
//          ka_log(ka_log_subtitle
//               , "Typ %08x start %010d, end %010d, dropped."
//               , rect->typ, rect->startDate, rect->endDate);

        if (rect->data) ka_mem_free(rect->data);
        ka_mem_free(rect);
      }
    }
    else if (pts >= rect->startDate)
    {
      // mark as displayed even if not current type
      rect->displayed = 1;
      return rect;
    }
    rect = rect->next;
  }

  return NULL;
}

typedef struct
{
  uint8_t* dst;        // pointer to plane
  int dst_bpr;         // bytes per row in plane
  int sub_bpr;         // bytes per row in sub
  int w;
  int h;
} ka_render8_t;

static void renderY(const ka_render8_t* r, const ka_sub_rect* rect)
{
   uint8_t* dst = r->dst;
   uint8_t* sub = rect->data;

   for (int h = r->h; h > 0; h--)
   {
     for (int w = r->w; w > 0; w--)
     {
        int col = *sub++;
        int alpha = rect->palette[col].a;
        int y = rect->palette[col].y;
        if (alpha)
        {
          alpha++;
          y = (y * alpha) + (*dst * (256 - alpha));
          *dst++ = y >> 8;
        }
        else dst++;
     }
     dst += r->dst_bpr - r->w;
     sub += r->sub_bpr - r->w;
   }
}

static void renderU11(const ka_render8_t* r, const ka_sub_rect* rect)
{
   uint8_t* dst = r->dst;
   uint8_t* sub = rect->data;

   for (int h = r->h; h > 0; h--)
   {
     for (int w = r->w; w > 0; w--)
     {
        int col = *sub++;
        int alpha = rect->palette[col].a;
        int u = rect->palette[col].u;
        if (alpha)
        {
          alpha++;
          u = u * alpha + (*dst * (256 - alpha));
          *dst++ = u >> 8;
        }
        else dst++;
     }
     dst += r->dst_bpr - r->w;
     sub += r->sub_bpr - r->w;
   }
}

static void renderU12(const ka_render8_t* r, const ka_sub_rect* rect)
{
   uint8_t* dst = r->dst;
   uint8_t* sub1 = rect->data;
   uint8_t* sub2 = sub1 + r->sub_bpr;

   for (int h = r->h; h > 0; h--)
   {
     for (int w = r->w; w > 0; w--)
     {
        int col1 = *sub1++;
        int col2 = *sub2++;
        int alpha = rect->palette[col1].a;
        alpha += rect->palette[col2].a;
        alpha >>= 1;
        if (alpha)
        {
          alpha++;
          int u = rect->palette[col1].u;
          u += rect->palette[col2].u;
          u += 1;
          u >>= 1;
          u = (u * alpha) + (*dst * (256 - alpha));
          *dst++ = u >> 8;
        }
        else dst++;

     }
     dst += r->dst_bpr - r->w;
     sub1 = sub2 + (r->sub_bpr - r->w);
     sub2 = sub1 + r->sub_bpr;
   }
}

static void renderU22(const ka_render8_t* r, const ka_sub_rect* rect)
{
   uint8_t* dst = r->dst;
   uint8_t* sub1 = rect->data;
   uint8_t* sub2 = sub1 + r->sub_bpr;

   for (int h = r->h; h > 0; h--)
   {
     for (int w = r->w; w > 0; w--)
     {
        int col1 = *sub1++;
        int col2 = *sub1++;
        int col3 = *sub2++;
        int col4 = *sub2++;
        int alpha = rect->palette[col1].a;
        alpha += rect->palette[col2].a;
        alpha += rect->palette[col3].a;
        alpha += rect->palette[col4].a;
        alpha >>= 2;
        if (alpha)
        {
          alpha++;
          int u = rect->palette[col1].u;
          u += rect->palette[col2].u;
          u += rect->palette[col3].u;
          u += rect->palette[col4].u;
          u += 2;
          u >>= 2;
          u = (u * alpha) + (*dst * (256 - alpha));
          *dst++ = u >> 8;
        }
        else dst++;

     }
     dst += r->dst_bpr - r->w;
     sub1 = sub2 + (r->sub_bpr - 2*r->w);
     sub2 = sub1 + r->sub_bpr;
   }
}

static void renderV11(const ka_render8_t* r, const ka_sub_rect* rect)
{
   uint8_t* dst = r->dst;
   uint8_t* sub = rect->data;

   for (int h = r->h; h > 0; h--)
   {
     for (int w = r->w; w > 0; w--)
     {
        int col = *sub++;
        int alpha = rect->palette[col].a;
        int v = rect->palette[col].v;
        if (alpha)
        {
          alpha++;
          v = (v * alpha) + (*dst * (256 - alpha));
          *dst++ = v >> 8;
        }
        else dst++;
     }
     dst += r->dst_bpr - r->w;
     sub += r->sub_bpr - r->w;
   }
}

static void renderV12(const ka_render8_t* r, const ka_sub_rect* rect)
{
   uint8_t* dst = r->dst;
   uint8_t* sub1 = rect->data;
   uint8_t* sub2 = sub1 + r->sub_bpr;

   for (int h = r->h; h > 0; h--)
   {
     for (int w = r->w; w > 0; w--)
     {
        int col1 = *sub1++;
        int col2 = *sub2++;
        int alpha = rect->palette[col1].a;
        alpha += rect->palette[col2].a;
        alpha >>= 1;
        if (alpha)
        {
          alpha++;
          int v = rect->palette[col1].v;
          v += rect->palette[col2].v;
          v += 1;
          v >>= 1;
          v = (v * alpha) + (*dst * (256 - alpha));
          *dst++ = v >> 8;
        }
        else dst++;

     }
     dst += r->dst_bpr - r->w;
     sub1 = sub2 + (r->sub_bpr - r->w);
     sub2 = sub1 + r->sub_bpr;
   }
}

static void renderV22(const ka_render8_t* r, const ka_sub_rect* rect)
{
   uint8_t* dst = r->dst;
   uint8_t* sub1 = rect->data;
   uint8_t* sub2 = sub1 + r->sub_bpr;

   for (int h = r->h; h > 0; h--)
   {
     for (int w = r->w; w > 0; w--)
     {
        int col1 = *sub1++;
        int col2 = *sub1++;
        int col3 = *sub2++;
        int col4 = *sub2++;
        int alpha = rect->palette[col1].a;
        alpha += rect->palette[col2].a;
        alpha += rect->palette[col3].a;
        alpha += rect->palette[col4].a;
        alpha >>= 2;
        if (alpha)
        {
          alpha++;
          int v = rect->palette[col1].v;
          v += rect->palette[col2].v;
          v += rect->palette[col3].v;
          v += rect->palette[col4].v;
          v += 2;
          v >>= 2;
          v = (v * alpha) + (*dst * (256 - alpha));
          *dst++ = v >> 8;
        }
        else dst++;

     }
     dst += r->dst_bpr - r->w;
     sub1 = sub2 + (r->sub_bpr - 2*r->w);
     sub2 = sub1 + r->sub_bpr;
   }
}

void ka_subtitles_render(const ka_vinfo_t* info, const ka_vframe_t* pframe, const ka_sub_rect* rect)
{
  switch(info->chroma.type)
  {
    case ka_chroma_YUV420:
    {
      ka_render8_t r;
      int x, y;

      r.dst_bpr = info->luminance.width;
      r.sub_bpr = rect->bpr;

      r.w = rect->x1 - rect->x0;
      if (rect->x1 > info->luminance.width)
      {
        x = rect->x0 - (rect->x1 - info->luminance.width);
        if (x + r.w > info->luminance.width)
          r.w = info->luminance.width - x;
      }
      else
        x = rect->x0;

      r.h = rect->y1 - rect->y0;
      if (rect->y1 > info->luminance.height)
      {
        y = rect->y0 - (rect->y1 - info->luminance.height);
        if (y + r.h > info->luminance.height)
          r.h = info->luminance.height - y;
      }
      else
        y = rect->y0;

      if ((r.w > 0) && (r.h > 0))
      {
        r.dst = pframe->planes.y + x + (y * r.dst_bpr);
        renderY(&r, rect);
        r.dst_bpr >>= 1;
        r.w >>= 1;
        r.h >>= 1;
        x >>= 1;
        y >>= 1;
        r.dst = pframe->planes.cb + x + (y * r.dst_bpr);
        renderU22(&r, rect);
        r.dst = pframe->planes.cr + x + (y * r.dst_bpr);
        renderV22(&r, rect);
      }
    }
    break;
    case ka_chroma_YUV422:
    {
      ka_render8_t r;
      int x, y;

      r.dst_bpr = info->luminance.width;
      r.sub_bpr = rect->bpr;

      r.w = rect->x1 - rect->x0;
      if (rect->x1 > info->luminance.width)
      {
        x = rect->x0 - (rect->x1 - info->luminance.width);
        if (x + r.w > info->luminance.width)
          r.w = info->luminance.width - x;
      }
      else
        x = rect->x0;

      r.h = rect->y1 - rect->y0;
      if (rect->y1 > info->luminance.height)
      {
        y = rect->y0 - (rect->y1 - info->luminance.height);
        if (y + r.h > info->luminance.height)
          r.h = info->luminance.height - y;
      }
      else
        y = rect->y0;

      if ((r.w > 0) && (r.h > 0))
      {
        r.dst = pframe->planes.y + x + (y * r.dst_bpr);
        renderY(&r, rect);
        r.h >>= 1;
        y >>= 1;
        r.dst = pframe->planes.cb + x + (y * r.dst_bpr);
        renderU12(&r, rect);
        r.dst = pframe->planes.cr + x + (y * r.dst_bpr);
        renderV12(&r, rect);
      }
    }
    break;
    case ka_chroma_YUV444:
    {
      ka_render8_t r;
      int x, y;

      r.dst_bpr = info->luminance.width;
      r.sub_bpr = rect->bpr;

      r.w = rect->x1 - rect->x0;
      if (rect->x1 > info->luminance.width)
      {
        x = rect->x0 - (rect->x1 - info->luminance.width);
        if (x + r.w > info->luminance.width)
          r.w = info->luminance.width - x;
      }
      else
        x = rect->x0;

      r.h = rect->y1 - rect->y0;
      if (rect->y1 > info->luminance.height)
      {
        y = rect->y0 - (rect->y1 - info->luminance.height);
        if (y + r.h > info->luminance.height)
          r.h = info->luminance.height - y;
      }
      else
        y = rect->y0;

      if ((r.w > 0) && (r.h > 0))
      {
        r.dst = pframe->planes.y + x + (y * r.dst_bpr);
        renderY(&r, rect);
        r.dst = pframe->planes.cb + x + (y * r.dst_bpr);
        renderU11(&r, rect);
        r.dst = pframe->planes.cr + x + (y * r.dst_bpr);
        renderV11(&r, rect);
      }
    }
    break;
  }
}
