#include "ka_vcodec.h"

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

#include "libmpeg2/mpeg2.h"

typedef struct
{
  ka_vcodec_t hdr;
  mpeg2dec_t* mpeg2dec;
  uint32_t    section;
  char videotype[7];
} data_t;

static ka_vcodec_t* vmpeg_new(ka_error_t* pErrorBlock, const ka_vparams_t* params, uint32_t hardware)
{
  data_t* data = ka_mem_alloc(sizeof(*data));

  params = params; // unused

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

  data->hdr.vptr = &kav_vcodec_mpeg;
  data->hdr.pErrorBlock = pErrorBlock;

  data->mpeg2dec = mpeg2_init(pErrorBlock, hardware);
  if (!data->mpeg2dec)
  {
    ka_error_fill(pErrorBlock, ka_error_nomem);
    ka_mem_free(data);
    return NULL;
  }

  return &data->hdr;
}

static void vmpeg_delete(ka_vcodec_t** ppcodec)
{
  data_t* data = (data_t*) *ppcodec;
  *ppcodec = NULL;

  if (!data)
    return;

  mpeg2_close(data->mpeg2dec);
  ka_mem_free(data);
}

static ka_vcodec_event vmpeg_decode(ka_vcodec_t* pcodec, ka_block_t* b, uint32_t exit_time)
{
  data_t* data = (data_t*) pcodec;
  int action;
  int old_action = ka_vcodec_event_buffering;

  do
  {
    if (b)
    {
      // normal buffer processing
      data->section = b->section;
      action = mpeg2_decode_videoblock(data->mpeg2dec, data->hdr.pErrorBlock, &b->sod, b->eod, b->pts);
    }
    else
    {
      // may still be a last frame to complete
      action = mpeg2_decode_complete(data->mpeg2dec, data->hdr.pErrorBlock);
    }

    switch(action)
    {
      case MPEG2_DECODE_NEW_SEQUENCE:
      {
        continue;
      }
      break;
      case MPEG2_DECODE_WAIT_FREEFRAME:
      {
        // returned as soon as frame is required
        // must come back here a second time for mpeg2dec to really
        // having no free frame
        if (old_action == action)
          return ka_vcodec_event_buffer_full;
      }
      break;
      case MPEG2_DECODE_START_FIRST_PICTURE:
      {
        return ka_vcodec_event_decode_frame;
      }
      break;
      case MPEG2_DECODE_END_FRAME:
      {
        return ka_vcodec_event_end_frame;
      }
      break;
      case MPEG2_DECODE_BLOCKEND:
      {
        return ka_vcodec_event_buffering;
      }
      break;
      case MPEG2_DECODE_ERROR:
      {
        return ka_vcodec_event_inerror;
      }
      break;
    }
    old_action = action;
    if (b && ((int32_t)(timer_gettime() - exit_time) > 0))
    {
      return ka_vcodec_event_timeout;
    }
  } while(1);
}

static void vmpeg_action(ka_vcodec_t* pcodec, ka_vcodec_action action)
{
  data_t* data = (data_t*) pcodec;

  switch(action)
  {
    case ka_vcodec_action_reset:
    {
      mpeg2_decode_reset(data->mpeg2dec);
    }
    break;
    case ka_vcodec_action_skipframe:
    {
      mpeg2_frame_t* mframe = mpeg2_currentframe(data->mpeg2dec);
      mframe->valid = 0;
    }
    break;
    case ka_vcodec_action_monochrome:
    {
      mpeg2_setoptions(data->mpeg2dec, MPEG2_OPT_MONOCHROME, MPEG2_OPT_MONOCHROME);
    }
    break;
    case ka_vcodec_action_colours:
    {
      mpeg2_setoptions(data->mpeg2dec, MPEG2_OPT_MONOCHROME, 0);
    }
    break;
  }
}

static void vmpeg_info(ka_vcodec_t* pcodec, ka_vinfo_t* info)
{
  data_t* data = (data_t*) pcodec;
  const mpeg2_sequence_t* seq = &mpeg2_info(data->mpeg2dec)->sequence;

  info->videotype = data->videotype;
  sprintf((char*) info->videotype, "MPEG %d", (seq->flags & SEQ_FLAG_MPEG2) ? 2 : 1);
  info->luminance.width  = seq->width;
  info->luminance.height = seq->height;
  info->chroma.wshift = seq->chroma_wshift;
  info->chroma.hshift = seq->chroma_hshift;
  switch(seq->chroma_type)
  {
    case SEQ_CHROMA_420: info->chroma.type = ka_chroma_YUV420; break;
    case SEQ_CHROMA_422: info->chroma.type = ka_chroma_YUV422; break;
    case SEQ_CHROMA_444: info->chroma.type = ka_chroma_YUV444; break;
  }
  info->picture.width = seq->picture_width;
  info->picture.height = seq->picture_height;
  info->frame_period = seq->frame_period;
  info->aspect_ratio.width = seq->pixel_width;
  info->aspect_ratio.height = seq->pixel_height;
  info->bitrate = (seq->byte_rate*400)/1000;
  info->progressive = ((seq->flags & SEQ_FLAG_PROGRESSIVE_SEQUENCE) != 0);
}

static int vmpeg_convertFrame(ka_vframe_t* frame, const mpeg2_frame_t* mframe)
{
  if (mframe == NULL)
    return 0;

  frame->planes.y  = mframe->base.y;
  frame->planes.cr = mframe->base.cr;
  frame->planes.cb = mframe->base.cb;

  switch(mframe->coding_type)
  {
    case PIC_FLAG_CODING_TYPE_B: frame->type = ka_vframe_type_B; break;
    case PIC_FLAG_CODING_TYPE_P: frame->type = ka_vframe_type_P; break;
    default: frame->type = ka_vframe_type_I; break;
  }

  frame->pts = mframe->pts;
  frame->duration = mframe->duration;
  frame->display_type = mframe->display_type;
  frame->valid = mframe->valid;
  frame->temporal_reference = mframe->temporal_reference;
  frame->reference = (uint32_t) mframe;
  frame->sequence_ref = mframe->sequence_ref;
  frame->section = mframe->section;

  return 1;
}

static int vmpeg_getDecodeFrame(ka_vcodec_t* pcodec, ka_vframe_t* frame)
{
  data_t* data = (data_t*) pcodec;
  mpeg2_frame_t* mframe = mpeg2_currentframe(data->mpeg2dec);
  mframe->section = data->section;

  return vmpeg_convertFrame(frame, mframe);
}

static int vmpeg_getDisplayFrame(ka_vcodec_t* pcodec, ka_vframe_t* frame)
{
  data_t* data = (data_t*) pcodec;

  return vmpeg_convertFrame(frame, mpeg2_displayframe(data->mpeg2dec));
}

static void vmpeg_releaseFrame(ka_vcodec_t* pcodec, ka_vframe_t* frame)
{
  data_t* data = (data_t*) pcodec;

  mpeg2_releaseframe(data->mpeg2dec, (mpeg2_frame_t*) frame->reference);
}

const kav_vcodec_t kav_vcodec_mpeg =
{
  .name = "MPEG"
, .FNnew             = vmpeg_new
, .FNdelete          = vmpeg_delete
, .FNdecode          = vmpeg_decode
, .FNact             = vmpeg_action
, .FNgetInfo         = vmpeg_info
, .FNgetDecodeFrame  = vmpeg_getDecodeFrame
, .FNgetDisplayFrame = vmpeg_getDisplayFrame
, .FNreleaseFrame    = vmpeg_releaseFrame
};
