/*
 * H.265 video codec.
 * Copyright (c) 2013-2014 struktur AG, Dirk Farin <farin@struktur.de>
 *
 * This file is part of libde265.
 *
 * libde265 is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 3 of
 * the License, or (at your option) any later version.
 *
 * libde265 is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with libde265.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "vui.h"
#include "decctx.h"

#include <assert.h>
#include <stdlib.h>
#include <string.h>

#define READ_VLC(variable, vlctype)   \
  if ((vlc = br->get_ ## vlctype()) == UVLC_ERROR) {   \
    errqueue->add_warning(DE265_ERROR_CODED_PARAMETER_OUT_OF_RANGE, false);  \
    return DE265_ERROR_CODED_PARAMETER_OUT_OF_RANGE; \
  } \
  (variable) = vlc;


#define NUM_SAR_PRESETS 17

static uint16_t sar_presets[NUM_SAR_PRESETS+1][2] = {
  { 0,0 },
  { 1,1 },
  { 12,11 },
  { 10,11 },
  { 16,11 },
  { 40,33 },
  { 24,11 },
  { 20,11 },
  { 32,11 },
  { 80,33 },
  { 18,11 },
  { 15,11 },
  { 64,33 },
  { 160,99 },
  { 4,3 },
  { 3,2 },
  { 2,1 }
};

#define EXTENDED_SAR 255


const char* get_video_format_name(enum VideoFormat format)
{
  switch (format) {
  case VideoFormat_Component: return "component";
  case VideoFormat_PAL:       return "PAL";
  case VideoFormat_NTSC:      return "NTSC";
  case VideoFormat_SECAM:     return "SECAM";
  case VideoFormat_MAC:       return "MAC";
  default:                    return "unspecified";
  }
}


video_usability_information::video_usability_information() = default;


de265_error video_usability_information::hrd_parameters(error_queue* errqueue, bitreader* br, const seq_parameter_set* sps)
{
  uint32_t vlc;

  nal_hrd_parameters_present_flag = br->get_bits(1);
  vcl_hrd_parameters_present_flag = br->get_bits(1);

  if (nal_hrd_parameters_present_flag || vcl_hrd_parameters_present_flag)
  {
    sub_pic_hrd_params_present_flag = br->get_bits(1);
    if (sub_pic_hrd_params_present_flag)
    {
      tick_divisor_minus2 = br->get_bits(8);
      du_cpb_removal_delay_increment_length_minus1 = br->get_bits(5);
      sub_pic_cpb_params_in_pic_timing_sei_flag = br->get_bits(1);
      dpb_output_delay_du_length_minus1 = br->get_bits(5);
    }
    bit_rate_scale = br->get_bits(4);
    cpb_size_scale = br->get_bits(4);


    if (sub_pic_hrd_params_present_flag)
    {
      cpb_size_du_scale = br->get_bits(4);
    }
    initial_cpb_removal_delay_length_minus1 = br->get_bits(5);
    au_cpb_removal_delay_length_minus1 = br->get_bits(5);
    dpb_output_delay_length_minus1 = br->get_bits(5);
  }
  int  i, nalOrVcl;

  for (i = 0; i < sps->sps_max_sub_layers; i++)
  {
    fixed_pic_rate_general_flag[i] = br->get_bits(1);
    if (!fixed_pic_rate_general_flag[i])
    {
      fixed_pic_rate_within_cvs_flag[i] = br->get_bits(1);
    }
    else
    {
      fixed_pic_rate_within_cvs_flag[i] = true;
    }

    low_delay_hrd_flag[i] = 0;// Inferred to be 0 when not present
    cpb_cnt_minus1[i] = 0;    // Inferred to be 0 when not present

    if (fixed_pic_rate_within_cvs_flag[i])
    {
      READ_VLC(elemental_duration_in_tc_minus1[i], uvlc);
    }
    else
    {
      low_delay_hrd_flag[i] = br->get_bits(1);
    }
    if (!low_delay_hrd_flag[i])
    {
      READ_VLC(cpb_cnt_minus1[i], uvlc);
      if (cpb_cnt_minus1[i] > 31) {
	return DE265_ERROR_CODED_PARAMETER_OUT_OF_RANGE;
      }
    }

    for (nalOrVcl = 0; nalOrVcl < 2; nalOrVcl++)
    {
      if (((nalOrVcl == 0) && nal_hrd_parameters_present_flag) ||
        ((nalOrVcl == 1) && vcl_hrd_parameters_present_flag))
      {
        for (uint32_t j = 0; j <= cpb_cnt_minus1[i]; j++)
        {
          READ_VLC(bit_rate_value_minus1[i][j][nalOrVcl], uvlc);
          READ_VLC(cpb_size_value_minus1[i][j][nalOrVcl], uvlc);

          if (sub_pic_hrd_params_present_flag)
          {
            READ_VLC(cpb_size_du_value_minus1[i][j][nalOrVcl], uvlc);
            READ_VLC(bit_rate_du_value_minus1[i][j][nalOrVcl], uvlc);
          }
          cbr_flag[i][j][nalOrVcl] = br->get_bits(1);
        }
      }
    }
  }
  return DE265_OK;
}

de265_error video_usability_information::read(error_queue* errqueue, bitreader* br,
                                              const seq_parameter_set* sps)
{
  uint32_t vlc;


  // --- sample aspect ratio (SAR) ---

  aspect_ratio_info_present_flag = br->get_bits(1);
  if (aspect_ratio_info_present_flag) {
    int aspect_ratio_idc = br->get_bits(8);
    if (aspect_ratio_idc <= NUM_SAR_PRESETS) {
      sar_width = sar_presets[aspect_ratio_idc][0];
      sar_height = sar_presets[aspect_ratio_idc][1];
    }
    else if (aspect_ratio_idc == EXTENDED_SAR) {
      sar_width = br->get_bits(16);
      sar_height = br->get_bits(16);
    }
    else {
      sar_width = 0;
      sar_height = 0;
    }
  }
  else {
    sar_width = 0;
    sar_height = 0;
  }


  // --- overscan ---

  overscan_info_present_flag = br->get_bits(1);
  if (overscan_info_present_flag) {
    overscan_appropriate_flag = br->get_bits(1);
  }


  // --- video signal type ---

  { // defaults
    video_format = VideoFormat_Unspecified;
    video_full_range_flag = false;
    colour_primaries = 2;
    transfer_characteristics = 2;
    matrix_coeffs = 2;
  }

  video_signal_type_present_flag = br->get_bits(1);
  if (video_signal_type_present_flag) {
    int video_format_idc = br->get_bits(3);
    if (video_format_idc > 5) {
      video_format_idc = VideoFormat_Unspecified;
    }
    video_format = (VideoFormat)video_format_idc;

    video_full_range_flag = br->get_bits(1);

    colour_description_present_flag = br->get_bits(1);
    if (colour_description_present_flag) {
      colour_primaries = br->get_bits(8);
      if (colour_primaries == 0 ||
        colour_primaries == 3 ||
        colour_primaries >= 11) {
        colour_primaries = 2;
      }

      transfer_characteristics = br->get_bits(8);
      if (transfer_characteristics == 0 ||
        transfer_characteristics == 3 ||
        transfer_characteristics >= 18) {
        transfer_characteristics = 2;
      }

      matrix_coeffs = br->get_bits(8);
      
      if (matrix_coeffs >= 11) {
        matrix_coeffs = 2;
      }
    }
  }


  // --- chroma / interlaced ---

  chroma_loc_info_present_flag = br->get_bits(1);
  if (chroma_loc_info_present_flag) {
    if ((vlc = br->get_uvlc()) == UVLC_ERROR || vlc > 5) {
      errqueue->add_warning(DE265_ERROR_CODED_PARAMETER_OUT_OF_RANGE, false);
      return DE265_ERROR_CODED_PARAMETER_OUT_OF_RANGE;
    }
    chroma_sample_loc_type_top_field = vlc;

    if ((vlc = br->get_uvlc()) == UVLC_ERROR || vlc > 5) {
      errqueue->add_warning(DE265_ERROR_CODED_PARAMETER_OUT_OF_RANGE, false);
      return DE265_ERROR_CODED_PARAMETER_OUT_OF_RANGE;
    }
    chroma_sample_loc_type_bottom_field = vlc;
  }
  else {
    chroma_sample_loc_type_top_field = 0;
    chroma_sample_loc_type_bottom_field = 0;
  }

  neutral_chroma_indication_flag = br->get_bits(1);
  field_seq_flag = br->get_bits(1);
  frame_field_info_present_flag = br->get_bits(1);


  // --- default display window ---

  default_display_window_flag = br->get_bits(1);
  if (default_display_window_flag) {
    READ_VLC(def_disp_win_left_offset, uvlc);
    READ_VLC(def_disp_win_right_offset, uvlc);
    READ_VLC(def_disp_win_top_offset, uvlc);
    READ_VLC(def_disp_win_bottom_offset, uvlc);
  }
  else {
    def_disp_win_left_offset = 0;
    def_disp_win_right_offset = 0;
    def_disp_win_top_offset = 0;
    def_disp_win_bottom_offset = 0;
  }


  // --- timing ---

  vui_timing_info_present_flag = br->get_bits(1);
  if (vui_timing_info_present_flag) {
    vui_num_units_in_tick = br->get_bits(32);
    vui_time_scale = br->get_bits(32);

    vui_poc_proportional_to_timing_flag = br->get_bits(1);
    if (vui_poc_proportional_to_timing_flag) {
      if ((vlc = br->get_uvlc()) == UVLC_ERROR) {
        errqueue->add_warning(DE265_ERROR_CODED_PARAMETER_OUT_OF_RANGE, false);
        return DE265_ERROR_CODED_PARAMETER_OUT_OF_RANGE;
      }

      vui_num_ticks_poc_diff_one = vlc + 1;
    }

    // --- hrd parameters ---

    vui_hrd_parameters_present_flag = br->get_bits(1);
    if (vui_hrd_parameters_present_flag) {
      de265_error err;
      err = hrd_parameters(errqueue, br, sps);
      if (err) {
	return err;
      }
    }
  }

  // --- bitstream restriction ---

  bitstream_restriction_flag = br->get_bits(1);
  if (bitstream_restriction_flag) {
    tiles_fixed_structure_flag = br->get_bits(1);
    motion_vectors_over_pic_boundaries_flag = br->get_bits(1);
    restricted_ref_pic_lists_flag = br->get_bits(1);

    if ((vlc = br->get_uvlc()) == UVLC_ERROR || vlc > 4095) {
      errqueue->add_warning(DE265_ERROR_CODED_PARAMETER_OUT_OF_RANGE, false);
      return DE265_ERROR_CODED_PARAMETER_OUT_OF_RANGE;
    }
    min_spatial_segmentation_idc = vlc;

    if ((vlc = br->get_uvlc()) == UVLC_ERROR || vlc > 16) {
      errqueue->add_warning(DE265_ERROR_CODED_PARAMETER_OUT_OF_RANGE, false);
      return DE265_ERROR_CODED_PARAMETER_OUT_OF_RANGE;
    }
    max_bytes_per_pic_denom = vlc;

    if ((vlc = br->get_uvlc()) == UVLC_ERROR || vlc > 16) {
      errqueue->add_warning(DE265_ERROR_CODED_PARAMETER_OUT_OF_RANGE, false);
      return DE265_ERROR_CODED_PARAMETER_OUT_OF_RANGE;
    }
    max_bits_per_min_cu_denom = vlc;

    if ((vlc = br->get_uvlc()) == UVLC_ERROR || vlc > 15) {
      errqueue->add_warning(DE265_ERROR_CODED_PARAMETER_OUT_OF_RANGE, false);
      return DE265_ERROR_CODED_PARAMETER_OUT_OF_RANGE;
    }
    log2_max_mv_length_horizontal = vlc;

    if ((vlc = br->get_uvlc()) == UVLC_ERROR || vlc > 15) {
      errqueue->add_warning(DE265_ERROR_CODED_PARAMETER_OUT_OF_RANGE, false);
      return DE265_ERROR_CODED_PARAMETER_OUT_OF_RANGE;
    }
    log2_max_mv_length_vertical = vlc;
  }
  else {
    tiles_fixed_structure_flag = false;
    motion_vectors_over_pic_boundaries_flag = true;
    restricted_ref_pic_lists_flag = false; // NOTE: default not specified in standard 2014/10

    min_spatial_segmentation_idc = 0;
    max_bytes_per_pic_denom   = 2;
    max_bits_per_min_cu_denom = 1;
    log2_max_mv_length_horizontal = 15;
    log2_max_mv_length_vertical   = 15;
  }

  //vui_read = true;

  return DE265_OK;
}


void video_usability_information::dump(int fd) const
{
  //#if (_MSC_VER >= 1500)
  //#define LOG0(t) loginfo(LogHeaders, t)
  //#define LOG1(t,d) loginfo(LogHeaders, t,d)
  //#define LOG2(t,d1,d2) loginfo(LogHeaders, t,d1,d2)
  //#define LOG3(t,d1,d2,d3) loginfo(LogHeaders, t,d1,d2,d3)

  FILE* fh;
  if (fd==1) fh=stdout;
  else if (fd==2) fh=stderr;
  else { return; }

#define LOG0(t) log2fh(fh, t)
#define LOG1(t,d) log2fh(fh, t,d)
#define LOG2(t,d1,d2) log2fh(fh, t,d1,d2)
#define LOG3(t,d1,d2,d3) log2fh(fh, t,d1,d2,d3)

  LOG0("----------------- VUI -----------------\n");
  LOG2("sample aspect ratio        : %d:%d\n", sar_width,sar_height);
  LOG1("overscan_info_present_flag : %d\n", overscan_info_present_flag);
  LOG1("overscan_appropriate_flag  : %d\n", overscan_appropriate_flag);

  LOG1("video_signal_type_present_flag: %d\n", video_signal_type_present_flag);
  if (video_signal_type_present_flag) {
    LOG1("  video_format                : %s\n", get_video_format_name(video_format));
    LOG1("  video_full_range_flag       : %d\n", video_full_range_flag);
    LOG1("  colour_description_present_flag : %d\n", colour_description_present_flag);
    LOG1("  colour_primaries            : %d\n", colour_primaries);
    LOG1("  transfer_characteristics    : %d\n", transfer_characteristics);
    LOG1("  matrix_coeffs               : %d\n", matrix_coeffs);
  }

  LOG1("chroma_loc_info_present_flag: %d\n", chroma_loc_info_present_flag);
  if (chroma_loc_info_present_flag) {
    LOG1("  chroma_sample_loc_type_top_field   : %d\n", chroma_sample_loc_type_top_field);
    LOG1("  chroma_sample_loc_type_bottom_field: %d\n", chroma_sample_loc_type_bottom_field);
  }

  LOG1("neutral_chroma_indication_flag: %d\n", neutral_chroma_indication_flag);
  LOG1("field_seq_flag                : %d\n", field_seq_flag);
  LOG1("frame_field_info_present_flag : %d\n", frame_field_info_present_flag);

  LOG1("default_display_window_flag   : %d\n", default_display_window_flag);
  LOG1("  def_disp_win_left_offset    : %d\n", def_disp_win_left_offset);
  LOG1("  def_disp_win_right_offset   : %d\n", def_disp_win_right_offset);
  LOG1("  def_disp_win_top_offset     : %d\n", def_disp_win_top_offset);
  LOG1("  def_disp_win_bottom_offset  : %d\n", def_disp_win_bottom_offset);

  LOG1("vui_timing_info_present_flag  : %d\n", vui_timing_info_present_flag);
  if (vui_timing_info_present_flag) {
    LOG1("  vui_num_units_in_tick       : %d\n", vui_num_units_in_tick);
    LOG1("  vui_time_scale              : %d\n", vui_time_scale);
  }

  LOG1("vui_poc_proportional_to_timing_flag : %d\n", vui_poc_proportional_to_timing_flag);
  LOG1("vui_num_ticks_poc_diff_one          : %d\n", vui_num_ticks_poc_diff_one);

  LOG1("vui_hrd_parameters_present_flag : %d\n", vui_hrd_parameters_present_flag);
  if (vui_hrd_parameters_present_flag) {
    //hrd_parameters vui_hrd_parameters;
  }


  // --- bitstream restriction ---

  LOG1("bitstream_restriction_flag         : %d\n", bitstream_restriction_flag);
  if (bitstream_restriction_flag) {
    LOG1("  tiles_fixed_structure_flag       : %d\n", tiles_fixed_structure_flag);
    LOG1("  motion_vectors_over_pic_boundaries_flag : %d\n", motion_vectors_over_pic_boundaries_flag);
    LOG1("  restricted_ref_pic_lists_flag    : %d\n", restricted_ref_pic_lists_flag);
    LOG1("  min_spatial_segmentation_idc     : %d\n", min_spatial_segmentation_idc);
    LOG1("  max_bytes_per_pic_denom          : %d\n", max_bytes_per_pic_denom);
    LOG1("  max_bits_per_min_cu_denom        : %d\n", max_bits_per_min_cu_denom);
    LOG1("  log2_max_mv_length_horizontal    : %d\n", log2_max_mv_length_horizontal);
    LOG1("  log2_max_mv_length_vertical      : %d\n", log2_max_mv_length_vertical);
  }

#undef LOG0
#undef LOG1
#undef LOG2
#undef LOG3
  //#endif
}
