/**************************************************************************
 *
 * Copyright 2013 Advanced Micro Devices, Inc.
 * All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sub license, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice (including the
 * next paragraph) shall be included in all copies or substantial portions
 * of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
 * IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR
 * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 **************************************************************************/

/*
 * Authors:
 *      Christian König <christian.koenig@amd.com>
 *
 */

#include "pipe/p_video_codec.h"
#include "vl/vl_vlc.h"
#include "vl/vl_zscan.h"

#include "vid_dec.h"

static uint8_t default_intra_matrix[64] = {
    8, 16, 19, 22, 26, 27, 29, 34,
   16, 16, 19, 22, 22, 22, 22, 26,
   26, 27, 22, 26, 26, 27, 29, 24,
   27, 27, 29, 32, 27, 29, 29, 32,
   35, 29, 34, 34, 35, 40, 34, 34,
   37, 40, 48, 37, 38, 40, 48, 58,
   26, 27, 29, 34, 38, 46, 56, 69,
   27, 29, 35, 38, 46, 56, 69, 83
};

static uint8_t default_non_intra_matrix[64] = {
   16, 16, 16, 16, 16, 16, 16, 16,
   16, 16, 16, 16, 16, 16, 16, 16,
   16, 16, 16, 16, 16, 16, 16, 16,
   16, 16, 16, 16, 16, 16, 16, 16,
   16, 16, 16, 16, 16, 16, 16, 16,
   16, 16, 16, 16, 16, 16, 16, 16,
   16, 16, 16, 16, 16, 16, 16, 16,
   16, 16, 16, 16, 16, 16, 16, 16
};

static void vid_dec_mpeg12_Decode(vid_dec_PrivateType *priv, struct vl_vlc *vlc, unsigned min_bits_left);
static void vid_dec_mpeg12_EndFrame(vid_dec_PrivateType *priv);
static struct pipe_video_buffer *vid_dec_mpeg12_Flush(vid_dec_PrivateType *priv, OMX_TICKS *timestamp);

void vid_dec_mpeg12_Init(vid_dec_PrivateType *priv)
{
   struct pipe_video_codec templat = {};
   omx_base_video_PortType *port;

   port = (omx_base_video_PortType *)priv->ports[OMX_BASE_FILTER_INPUTPORT_INDEX];
   templat.profile = priv->profile;
   templat.entrypoint = PIPE_VIDEO_ENTRYPOINT_BITSTREAM;
   templat.chroma_format = PIPE_VIDEO_CHROMA_FORMAT_420;
   templat.max_references = 2;
   templat.expect_chunked_decode = true;
   templat.width = port->sPortParam.format.video.nFrameWidth;
   templat.height = port->sPortParam.format.video.nFrameHeight;

   priv->codec = priv->pipe->create_video_codec(priv->pipe, &templat);

   priv->picture.base.profile = PIPE_VIDEO_PROFILE_MPEG2_MAIN;
   priv->picture.mpeg12.intra_matrix = default_intra_matrix;
   priv->picture.mpeg12.non_intra_matrix = default_non_intra_matrix;

   priv->Decode = vid_dec_mpeg12_Decode;
   priv->EndFrame = vid_dec_mpeg12_EndFrame;
   priv->Flush = vid_dec_mpeg12_Flush;
}

static void BeginFrame(vid_dec_PrivateType *priv)
{
   if (priv->picture.mpeg12.picture_coding_type != PIPE_MPEG12_PICTURE_CODING_TYPE_B) {
      priv->picture.mpeg12.ref[0] = priv->picture.mpeg12.ref[1];
      priv->picture.mpeg12.ref[1] = NULL;
   }

   if (priv->target == priv->picture.mpeg12.ref[0]) {
      struct pipe_video_buffer *tmp = priv->target;
      priv->target = priv->shadow;
      priv->shadow = tmp;
   }

   vid_dec_NeedTarget(priv);

   priv->codec->begin_frame(priv->codec, priv->target, &priv->picture.base);
   priv->frame_started = true;
}

static void vid_dec_mpeg12_EndFrame(vid_dec_PrivateType *priv)
{
   struct pipe_video_buffer *done;

   priv->codec->end_frame(priv->codec, priv->target, &priv->picture.base);
   priv->frame_started = false;

   if (priv->picture.mpeg12.picture_coding_type != PIPE_MPEG12_PICTURE_CODING_TYPE_B) {

      priv->picture.mpeg12.ref[1] = priv->target;
      done = priv->picture.mpeg12.ref[0];
      if (!done) {
         priv->target = NULL;
         return;
      }

   } else
      done = priv->target;

   priv->frame_finished = true;
   priv->target = priv->in_buffers[0]->pInputPortPrivate;
   priv->in_buffers[0]->pInputPortPrivate = done;
}

static struct pipe_video_buffer *vid_dec_mpeg12_Flush(vid_dec_PrivateType *priv, OMX_TICKS *timestamp)
{
   struct pipe_video_buffer *result = priv->picture.mpeg12.ref[1];
   priv->picture.mpeg12.ref[1] = NULL;
   if (timestamp)
      *timestamp = OMX_VID_DEC_TIMESTAMP_INVALID;
   return result;
}

static void vid_dec_mpeg12_Decode(vid_dec_PrivateType *priv, struct vl_vlc *vlc, unsigned min_bits_left)
{
   uint8_t code;
   unsigned i;

   if (!vl_vlc_search_byte(vlc, vl_vlc_bits_left(vlc) - min_bits_left, 0x00))
      return;

   if (vl_vlc_peekbits(vlc, 24) != 0x000001) {
      vl_vlc_eatbits(vlc, 8);
      return;
   }

   if (priv->slice) {
      unsigned bytes = priv->bytes_left - (vl_vlc_bits_left(vlc) / 8);
      priv->codec->decode_bitstream(priv->codec, priv->target, &priv->picture.base,
                                    1, &priv->slice, &bytes);
      priv->slice = NULL;
   }

   vl_vlc_eatbits(vlc, 24);
   code = vl_vlc_get_uimsbf(vlc, 8);

   if (priv->frame_started && (code == 0x00 || code > 0xAF))
      vid_dec_mpeg12_EndFrame(priv);

   if (code == 0xB3) {
      /* sequence header code */
      vl_vlc_fillbits(vlc);

      /* horizontal_size_value */
      vl_vlc_get_uimsbf(vlc, 12);

      /* vertical_size_value */
      vl_vlc_get_uimsbf(vlc, 12);

      /* aspect_ratio_information */
      vl_vlc_get_uimsbf(vlc, 4);

      /* frame_rate_code */
      vl_vlc_get_uimsbf(vlc, 4);

      vl_vlc_fillbits(vlc);

      /* bit_rate_value */
      vl_vlc_get_uimsbf(vlc, 18);

      /* marker_bit */
      vl_vlc_get_uimsbf(vlc, 1);

      /* vbv_buffer_size_value */
      vl_vlc_get_uimsbf(vlc, 10);

      /* constrained_parameters_flag */
      vl_vlc_get_uimsbf(vlc, 1);

      vl_vlc_fillbits(vlc);

      /* load_intra_quantiser_matrix */
      if (vl_vlc_get_uimsbf(vlc, 1)) {
         /* intra_quantiser_matrix */
         priv->picture.mpeg12.intra_matrix = priv->codec_data.mpeg12.intra_matrix;
         for (i = 0; i < 64; ++i) {
            priv->codec_data.mpeg12.intra_matrix[vl_zscan_normal[i]] = vl_vlc_get_uimsbf(vlc, 8);
            vl_vlc_fillbits(vlc);
         }
      } else
         priv->picture.mpeg12.intra_matrix = default_intra_matrix;

      /* load_non_intra_quantiser_matrix */
      if (vl_vlc_get_uimsbf(vlc, 1)) {
         /* non_intra_quantiser_matrix */
         priv->picture.mpeg12.non_intra_matrix = priv->codec_data.mpeg12.non_intra_matrix;
         for (i = 0; i < 64; ++i) {
            priv->codec_data.mpeg12.non_intra_matrix[i] = vl_vlc_get_uimsbf(vlc, 8);
            vl_vlc_fillbits(vlc);
         }
      } else
         priv->picture.mpeg12.non_intra_matrix = default_non_intra_matrix;

   } else if (code == 0x00) {
      /* picture start code */
      vl_vlc_fillbits(vlc);

      /* temporal_reference */
      vl_vlc_get_uimsbf(vlc, 10);

      priv->picture.mpeg12.picture_coding_type = vl_vlc_get_uimsbf(vlc, 3);

      /* vbv_delay */
      vl_vlc_get_uimsbf(vlc, 16);

      vl_vlc_fillbits(vlc);
      if (priv->picture.mpeg12.picture_coding_type == 2 ||
          priv->picture.mpeg12.picture_coding_type == 3) {
         priv->picture.mpeg12.full_pel_forward_vector = vl_vlc_get_uimsbf(vlc, 1);
         /* forward_f_code */
         priv->picture.mpeg12.f_code[0][0] = vl_vlc_get_uimsbf(vlc, 3) - 1;
         priv->picture.mpeg12.f_code[0][1] = priv->picture.mpeg12.f_code[0][0];
      } else {
         priv->picture.mpeg12.full_pel_forward_vector = 0;
         priv->picture.mpeg12.f_code[0][1] = priv->picture.mpeg12.f_code[0][0] = 14;
      }

      if (priv->picture.mpeg12.picture_coding_type == 3) {
         priv->picture.mpeg12.full_pel_backward_vector = vl_vlc_get_uimsbf(vlc, 1);
         /* backward_f_code */
         priv->picture.mpeg12.f_code[1][0] = vl_vlc_get_uimsbf(vlc, 3) - 1;
         priv->picture.mpeg12.f_code[1][1] = priv->picture.mpeg12.f_code[1][0];
      } else {
         priv->picture.mpeg12.full_pel_backward_vector = 0;
         priv->picture.mpeg12.f_code[0][1] = priv->picture.mpeg12.f_code[0][0] = 14;
      }

      /* extra_bit_picture */
      while (vl_vlc_get_uimsbf(vlc, 1)) {
         /* extra_information_picture */
         vl_vlc_get_uimsbf(vlc, 8);
         vl_vlc_fillbits(vlc);
      }

   } else if (code == 0xB5) {
      /* extension start code */
      vl_vlc_fillbits(vlc);

      /* extension_start_code_identifier */
      switch (vl_vlc_get_uimsbf(vlc, 4)) {
      case 0x3: /* quant matrix extension */

         /* load_intra_quantiser_matrix */
         if (vl_vlc_get_uimsbf(vlc, 1)) {
            /* intra_quantiser_matrix */
            priv->picture.mpeg12.intra_matrix = priv->codec_data.mpeg12.intra_matrix;
            for (i = 0; i < 64; ++i) {
               priv->codec_data.mpeg12.intra_matrix[vl_zscan_normal[i]] = vl_vlc_get_uimsbf(vlc, 8);
               vl_vlc_fillbits(vlc);
            }
         } else
            priv->picture.mpeg12.intra_matrix = default_intra_matrix;

         /* load_non_intra_quantiser_matrix */
         if (vl_vlc_get_uimsbf(vlc, 1)) {
            /* non_intra_quantiser_matrix */
            priv->picture.mpeg12.non_intra_matrix = priv->codec_data.mpeg12.non_intra_matrix;
            for (i = 0; i < 64; ++i) {
               priv->codec_data.mpeg12.non_intra_matrix[i] = vl_vlc_get_uimsbf(vlc, 8);
               vl_vlc_fillbits(vlc);
            }
         } else
            priv->picture.mpeg12.intra_matrix = default_non_intra_matrix;

         break;

      case 0x8: /* picture coding extension */

         priv->picture.mpeg12.f_code[0][0] = vl_vlc_get_uimsbf(vlc, 4) - 1;
         priv->picture.mpeg12.f_code[0][1] = vl_vlc_get_uimsbf(vlc, 4) - 1;
         priv->picture.mpeg12.f_code[1][0] = vl_vlc_get_uimsbf(vlc, 4) - 1;
         priv->picture.mpeg12.f_code[1][1] = vl_vlc_get_uimsbf(vlc, 4) - 1;
         priv->picture.mpeg12.intra_dc_precision = vl_vlc_get_uimsbf(vlc, 2);
         priv->picture.mpeg12.picture_structure = vl_vlc_get_uimsbf(vlc, 2);
         priv->picture.mpeg12.top_field_first = vl_vlc_get_uimsbf(vlc, 1);
         priv->picture.mpeg12.frame_pred_frame_dct = vl_vlc_get_uimsbf(vlc, 1);
         priv->picture.mpeg12.concealment_motion_vectors = vl_vlc_get_uimsbf(vlc, 1);
         priv->picture.mpeg12.q_scale_type = vl_vlc_get_uimsbf(vlc, 1);
         priv->picture.mpeg12.intra_vlc_format = vl_vlc_get_uimsbf(vlc, 1);
         priv->picture.mpeg12.alternate_scan = vl_vlc_get_uimsbf(vlc, 1);

         /* repeat_first_field */
         vl_vlc_get_uimsbf(vlc, 1);

         /* chroma_420_type */
         vl_vlc_get_uimsbf(vlc, 1);

         vl_vlc_fillbits(vlc);

         /* progressive_frame */
         vl_vlc_get_uimsbf(vlc, 1);

         /* composite_display_flag */
         if (vl_vlc_get_uimsbf(vlc, 1)) {

            /* v_axis */
            vl_vlc_get_uimsbf(vlc, 1);

            /* field_sequence */
            vl_vlc_get_uimsbf(vlc, 3);

            /* sub_carrier */
            vl_vlc_get_uimsbf(vlc, 1);

            /* burst_amplitude */
            vl_vlc_get_uimsbf(vlc, 7);

            /* sub_carrier_phase */
            vl_vlc_get_uimsbf(vlc, 8);
         }
         break;
      }

   } else if (code <= 0xAF) {
      /* slice start */
      unsigned bytes = (vl_vlc_valid_bits(vlc) / 8) + 4;
      uint8_t buf[12];
      const void *ptr = buf;
      unsigned i;

      if (!priv->frame_started)
         BeginFrame(priv);

      buf[0] = 0x00;
      buf[1] = 0x00;
      buf[2] = 0x01;
      buf[3] = code;
      for (i = 4; i < bytes; ++i)
         buf[i] = vl_vlc_get_uimsbf(vlc, 8);

      priv->codec->decode_bitstream(priv->codec, priv->target, &priv->picture.base,
                                    1, &ptr, &bytes);

      priv->bytes_left = vl_vlc_bits_left(vlc) / 8;
      priv->slice = vlc->data;

   } else if (code == 0xB2) {
      /* user data start */

   } else if (code == 0xB4) {
      /* sequence error */
   } else if (code == 0xB7) {
      /* sequence end */
   } else if (code == 0xB8) {
      /* group start */
   } else if (code >= 0xB9) {
      /* system start */
   } else {
      /* reserved */
   }

   /* resync to byte boundary */
   vl_vlc_eatbits(vlc, vl_vlc_valid_bits(vlc) % 8);
}