/*
 * 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;


/* Read an advisory ue(v) VUI syntax element.

   A malformed exp-Golomb code (UVLC_ERROR) is a hard error: we lost the bit
   position and nothing after it can be parsed.

   A well-formed value that merely exceeds the range allowed by the standard is
   only a warning. The chroma siting and bitstream_restriction() elements are
   hints for the decoder that are never used in the decoding process, so an
   out-of-range value cannot affect correctness or memory safety. Real-world
   streams (e.g. HEIF images from consumer encoders) contain such values, so we
   clamp to the value the standard infers when the element is absent and keep
   parsing instead of rejecting the whole SPS (issue #539).
 */
#define READ_ADVISORY_VLC(variable, maxval, defaultval)   \
  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; \
  } \
  if (vlc > (maxval)) { \
    errqueue->add_warning(DE265_ERROR_CODED_PARAMETER_OUT_OF_RANGE, false);  \
    vlc = (defaultval); \
  } \
  (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) {
    READ_ADVISORY_VLC(chroma_sample_loc_type_top_field,    5, 0);
    READ_ADVISORY_VLC(chroma_sample_loc_type_bottom_field, 5, 0);
  }
  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);

    // Same defaults as in the else-branch below (the values inferred when absent).
    READ_ADVISORY_VLC(min_spatial_segmentation_idc,  4095, 0);
    READ_ADVISORY_VLC(max_bytes_per_pic_denom,       16,   2);
    READ_ADVISORY_VLC(max_bits_per_min_cu_denom,     16,   1);
    READ_ADVISORY_VLC(log2_max_mv_length_horizontal, 15,   15);
    READ_ADVISORY_VLC(log2_max_mv_length_vertical,   15,   15);
  }
  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
}
