#include "ka_stack.h"

#include <string.h>

#include "config.h"
#include "ka_codecs.h"
#include "ka_mem.h"
#include "ka_log.h"
#include "ka_error.h"

/**
 * Creates new data blocks stack.
 *
 * @returns New stack.
 */
ka_stack_t* ka_new_stack(ka_error_t* pErrorBlock)
{
  ka_stack_t* stack = ka_mem_calloc(sizeof(*stack));
  if (!stack)
  {
    ka_error_fill(pErrorBlock, ka_error_nomem);
    return NULL;
  }

  ka_stack_reset(stack);
  return stack;
}

/**
 * Deletes a stack.
 *
 * @param  stack  Stack handle.
 */
void ka_delete_stack(ka_stack_t** pstack)
{
  ka_stack_t* stack = *pstack;
  *pstack = NULL;
  int i, j, k;

  if (!stack)
    return;

  for (i = 0; i < KA_MAX_PROGRAMS; i++)
  {
    ka_program_t* program = &stack->programs[i];

    for (j = 0; j < KA_PROGRAM_MAX_TITLES; j++)
    {
      if (program->titles[j].info.name)
        ka_mem_free(program->titles[j].info.name);

      for (k = 0; k < KA_PROGRAM_MAX_CHAPTERS; k++)
      {
        if (program->titles[j].chapters[k].name)
          ka_mem_free(program->titles[j].chapters[k].name);
      }
    }
  }

  ka_mem_free(stack);
}

/**
 * Clears the contents of the fifo.
 *
 * @param  fifo  Fifo handle.
 */
static void ka_fifo_clear(ka_fifo_t* fifo, uint32_t size, uint32_t clear)
{
  fifo->size = size;
  fifo->fstart = fifo->fread = fifo->fwrite = &fifo->fifo[0];
  fifo->fend = fifo->fstart + fifo->size;
  fifo->status &= ~clear;

}

/**
 * Checks if any more data blocks can be inserted in the fifo.
 *
 * @param  fifo  Fifo handle.
 *
 * @returns 1 it fifo is full, 0 otherwise.
 */
static int ka_fifo_isFull(const ka_fifo_t* fifo)
{
  int count = fifo->fread - fifo->fwrite;
  if (count <= 0) count += fifo->size;
  return (count <= 1);
}

/**
 * Counts the number of blocks stacked..
 *
 * @param  fifo  Fifo handle.
 *
 * @returns 1 it fifo is full, 0 otherwise.
 */
uint32_t ka_fifo_count(const ka_fifo_t* fifo)
{
  int count = fifo->fwrite - fifo->fread;
  if (count < 0) count += fifo->size;

  return count;
}

/**
 * Push data on the fifo.
 *
 * @param  typ  block type
 * @param  src  time stamp, source
 * @param  dts  time stamp, decode
 * @param  pts  time stamp, presentation
 * @param  sod  start of block data
 * @param  eod  end of block data
 *
 * @returns 1 if data could be inserted, 0 otherwise.
 */
static int ka_fifo_push(ka_fifo_t* fifo
                , uint32_t typ
                , uint32_t section
                , uint32_t scr
                , uint32_t dts
                , uint32_t pts
                , const uint8_t* sod
                , const uint8_t* eod)
{
  int count = fifo->fread - fifo->fwrite;

  if (count <= 0) count += fifo->size;

  if (count <= 1)
    return 0;

  fifo->fwrite->typ = typ;
  fifo->fwrite->section = section;
  fifo->fwrite->scr = scr;
  fifo->fwrite->pts = pts;
  fifo->fwrite->dts = dts;
  fifo->fwrite->sod = sod;
  fifo->fwrite->eod = eod;
  if (++fifo->fwrite == fifo->fend) fifo->fwrite = fifo->fstart;

  return 1;
}

/**
 * Clears the contents of the stack and stream lists.
 *
 * @param  stack  Stack handle.
 */
void ka_stack_reset(ka_stack_t* stack)
{
  memset(stack, 0, sizeof(*stack));

  ka_fifo_clear(&stack->video, sizeof(stack->video_blocks) / sizeof(stack->video_blocks[0]), 0);
  ka_fifo_clear(&stack->audio, sizeof(stack->audio_blocks) / sizeof(stack->audio_blocks[0]), 0);
  ka_fifo_clear(&stack->subtitle, sizeof(stack->subtitle_blocks) / sizeof(stack->subtitle_blocks[0]), 0);
  ka_fifo_clear(&stack->navigation, sizeof(stack->navigation_blocks) / sizeof(stack->navigation_blocks[0]), 0);
}

/**
 * Clears the contents of the stack.
 *
 * @param  stack  Stack handle.
 */
void ka_stack_clear(ka_stack_t* stack)
{
  uint32_t clear = ka_fifo_status_used | ka_fifo_status_pulldone;
  ka_fifo_clear(&stack->video, sizeof(stack->video_blocks) / sizeof(stack->video_blocks[0]), clear);
  ka_fifo_clear(&stack->audio, sizeof(stack->audio_blocks) / sizeof(stack->audio_blocks[0]), clear);
  ka_fifo_clear(&stack->subtitle, sizeof(stack->subtitle_blocks) / sizeof(stack->subtitle_blocks[0]), clear);
  ka_fifo_clear(&stack->navigation, sizeof(stack->navigation_blocks) / sizeof(stack->navigation_blocks[0]), clear);
}

/**
 * Sets the input stream's properties,
 * these will be used for synchronisation.
 *
 * @param  stack     Stack handle.
 * @param  datatype  Properties to set.
 */
void ka_stack_setProperties(ka_stack_t* stack, uint32_t datatype)
{
  stack->properties = datatype;
}

/**
 * Checks if any more data blocks can be inserted in the stack.
 *
 * @param  stack  Stack handle.
 *
 * @returns 1 it stack is full, 0 otherwise.
 */
int ka_stack_isFull(const ka_stack_t* stack)
{
  return ka_fifo_isFull(&stack->video)
       | ka_fifo_isFull(&stack->audio)
       | ka_fifo_isFull(&stack->subtitle);
}

/**
 * Push data on the stack.
 * Signals kid is used.
 *
 * @param  kid  block type
 * @param  src  time stamp, source
 * @param  dts  time stamp, decode
 * @param  pts  time stamp, presentation
 * @param  sod  start of block data
 * @param  eod  end of block data
 *
 * @returns 1 if data could be inserted, 0 otherwise.
 */
int ka_stack_push(ka_stack_t* stack
                , ka_block_id kid
                , uint32_t section
                , uint32_t scr
                , uint32_t dts
                , uint32_t pts
                , const uint8_t* sod
                , const uint8_t* eod)
{
  int count = 0;

  ka_idinfo_t* info = &stack->infos[0];
  int i;

  for (i = 0; i < stack->nr_infos; i++, info++)
  {
    if (info->kid == kid)
      break;
  }

  if (i >= stack->nr_infos)
    return 0;

  // If uses timestamps, ignore leading data without timestamp.
  if (!info->count
  &&  !(stack->properties & ps_stack_prop_notimestamp)
  &&  !pts)
    return 0;

  switch(kid & KA_BLOCK_TYPEMASK)
  {
    case KA_BLOCK_TYPE_AUDIO:
    {
      ka_fifo_t* fifo = &stack->audio;
      count = ka_fifo_push(fifo, kid, section, scr, dts, pts, sod, eod);

      if (count)
      {
        fifo->status |= ka_fifo_status_used;
        if (config.debug & cfg_printaudiopush)
          ka_log(ka_log_audio, "=> Push %8x, pts %10u, len %u", kid, pts, eod-sod);
      }
    }
    break;
    case KA_BLOCK_TYPE_VIDEO:
    {
      ka_fifo_t* fifo = &stack->video;
      count = ka_fifo_push(fifo, kid, section, scr, dts, pts, sod, eod);

      if (count)
      {
        fifo->status |= ka_fifo_status_used;
        if (config.debug & cfg_printvideopush)
           ka_log(ka_log_video, "=> Push %8x, pts %10u, len %u", kid, pts, eod-sod);
      }
    }
    break;
    case KA_BLOCK_TYPE_SUBTITLE:
    {
      ka_fifo_t* fifo = &stack->subtitle;
      count = ka_fifo_push(fifo, kid, section, scr, dts, pts, sod, eod);

      if (count)
      {
        fifo->status |= ka_fifo_status_used;
        if (config.debug & cfg_printsubtitlepush)
           ka_log(ka_log_subtitle, "=> Push %8x, pts %10u, len %u", kid, pts, eod-sod);
      }
    }
    break;
    case KA_BLOCK_TYPE_NAV:
    {
      ka_fifo_t* fifo = &stack->navigation;
      count = ka_fifo_push(fifo, kid, section, scr, dts, pts, sod, eod);

      if (count)
      {
        fifo->status |= ka_fifo_status_used;
        if (config.debug & cfg_printdemuxstats)
           ka_log(ka_log_demux, "=> Push %8x, pts %10u, len %u", kid, pts, eod-sod);
      }
    }
    break;
    default:
      if (config.debug & cfg_printbufferstats)
         ka_log(ka_log_error | ka_log_demux, "=> NO Push %8x, pts %10u, len %u", kid, pts, eod-sod);
  }

  if (count)
    info->count++;

  return count;
}

/**
 * Checks if we received some packets for a given type.
 */
uint32_t ka_stack_getCountForKid(ka_stack_t* stack, ka_block_id kid)
{
  ka_idinfo_t* info = &stack->infos[0];
  int i;

  for (i = 0; i < stack->nr_infos; i++, info++)
  {
    if (info->kid == kid)
      return info->count;
  }

  return 0;
}

static char* fill_vtext(const char* msg_format, va_list arg)
{
  char text[64];
  report_vstring(text, sizeof(text), msg_format, arg);
  return ka_mem_allocstring(text);
}

/**
 * Find/adds a program to the list.
 */
ka_idinfo_t* ka_stack_programInfo(ka_stack_t* stack, uint32_t prog_id)
{
  int i;

  for (i = 0; i < stack->nr_programs; i++)
  {
    if (stack->programs[i].info.kid == prog_id)
      return &stack->programs[i].info;
  }

  if (stack->nr_programs < KA_MAX_PROGRAMS)
  {
    ka_program_t* program = &stack->programs[i];

    program->info.kid = prog_id;
    program->info.data = NULL;
    program->nr_videos = 0;
    program->nr_audios = 0;
    program->nr_subtitles = 0;

    stack->nr_programs++;
    return &program->info;
  }

  return NULL;
}

/**
 * Find/add a stream id to the list or NULL if no more space.
 */
ka_idinfo_t* ka_stack_idInfo(ka_stack_t* stack, uint32_t pid, ka_block_id kid)
{
  ka_idinfo_t* info = &stack->infos[0];
  int i;

  for (i = 0; i < stack->nr_infos; i++, info++)
  {
    if (info->pid == pid)
    {
      if (kid)
        info->kid = kid;
      return info;
    }
  }

  if (i < KA_MAX_TYP_INFOS)
  {
     info->pid = pid;
     info->kid = kid;
     info->count = 0;
     info->data = NULL;
     stack->nr_infos++;
     return info;
  }

  return NULL;
}

/**
 * Adds a title to the list.
 */
int ka_stack_addTitle(ka_stack_t* stack, uint32_t pg, uint32_t id, const char* format, ...)
{
  ka_program_t* program = &stack->programs[pg];
  int t = -1;

  va_list arg;
  va_start(arg, format);

  if (pg >= stack->nr_programs)
    goto exit;

  if (program->nr_titles >= KA_PROGRAM_MAX_TITLES)
    goto exit;

  t = program->nr_titles;

  {
    ka_title_t* title = &program->titles[t];

    title->nr_chapters = 0;
    title->info.blockid = id;
    title->info.name = fill_vtext(format, arg);
  }

  program->nr_titles++;

exit:
  va_end(arg);

  return t;
}

/**
 * Adds a chapter to the list.
 */
int ka_stack_addChapter(ka_stack_t* stack, uint32_t pg, uint32_t t, uint32_t id, const char* format, ...)
{
  ka_program_t* program = &stack->programs[pg];
  int c = -1;

  va_list arg;
  va_start(arg, format);

  if (pg >= stack->nr_programs)
    goto exit;

  if (t >= program->nr_titles)
    goto exit;

  {
    ka_title_t* title = &program->titles[t];

    if (title->nr_chapters >= KA_PROGRAM_MAX_CHAPTERS)
      goto exit;

    c = title->nr_chapters;

    ka_block_info_t* info = &title->chapters[c];

    info->blockid = id;
    info->name = fill_vtext(format, arg);

    title->nr_chapters++;
  }

exit:
  va_end(arg);

  return c;
}

/**
 * Adds a stream id to a program.
 */
void ka_stack_programMapId(ka_stack_t* stack, uint32_t prog_id, ka_block_id kid)
{
  ka_program_t* program = NULL;
  int i;

  for (i = 0; i < stack->nr_programs; i++)
  {
    if (stack->programs[i].info.kid == prog_id)
    {
      program = &stack->programs[i];
      break;
    }
  }

  if (program == NULL)
    return;

  switch(kid & KA_BLOCK_TYPEMASK)
  {
    case KA_BLOCK_TYPE_VIDEO:
    {
      for (i = 0; i < program->nr_videos; i++)
      {
        if (program->videos[i] == kid)
          return;
      }

      if (program->nr_videos >= KA_PROGRAM_MAX_VIDEOS)
        return;

      while ((i > 0) && (program->videos[i-1] > kid))
      {
        program->videos[i] = program->videos[i-1];
        i--;
      }

      program->videos[i] = kid;

      program->nr_videos++;
    }
    break;
    case KA_BLOCK_TYPE_AUDIO:
    {
      for (i = 0; i < program->nr_audios; i++)
      {
        if (program->audios[i] == kid)
          return;
      }

      if (program->nr_audios >= KA_PROGRAM_MAX_AUDIOS)
        return;

      while ((i > 0) && (program->audios[i-1] > kid))
      {
        program->audios[i] = program->audios[i-1];
        i--;
      }

      program->audios[i] = kid;

      program->nr_audios++;
    }
    break;
    case KA_BLOCK_TYPE_SUBTITLE:
    {
      for (i = 0; i < program->nr_subtitles; i++)
      {
        if (program->subtitles[i] == kid)
          return;
      }

      if (program->nr_subtitles >= KA_PROGRAM_MAX_SUBTITLES)
        return;

      while ((i > 0) && (program->subtitles[i-1] > kid))
      {
        program->subtitles[i] = program->subtitles[i-1];
        i--;
      }

      program->subtitles[i] = kid;

      program->nr_subtitles++;
    }
    break;
  }
}

int ka_stack_idBelongsToProgram(const ka_program_t* program, ka_block_id kid)
{
  int i;

  if (program == NULL)
    return -1;

  switch(kid & KA_BLOCK_TYPEMASK)
  {
    case KA_BLOCK_TYPE_VIDEO:
    {
      for (i = 0; i < program->nr_videos; i++)
      {
        if (program->videos[i] == kid)
          return i;
      }

      return -1;
    }
    break;
    case KA_BLOCK_TYPE_AUDIO:
    {
      for (i = 0; i < program->nr_audios; i++)
      {
        if (program->audios[i] == kid)
          return i;
      }

      return -1;
    }
    break;
    case KA_BLOCK_TYPE_SUBTITLE:
    {
      for (i = 0; i < program->nr_subtitles; i++)
      {
        if (program->subtitles[i] == kid)
          return i;
      }

      return -1;
    }
    break;
  }

  return -1;
}

/**
 * Creates a default program using available ids.
 */
void ka_stack_createFallbackProgram(ka_stack_t* stack)
{
  if (stack->nr_programs) return;
  stack->nr_programs++;

  ka_program_t* program = &stack->programs[0];
  ka_idinfo_t* info = &stack->infos[0];
  int i;

  for (i = 0; i < stack->nr_infos; i++, info++)
  {
    if (info->kid)
      ka_stack_programMapId(stack, program->info.kid, info->kid);
  }
}
