/*****************************************************************************
 * This file is part of Kvazaar HEVC encoder.
 *
 * Copyright (c) 2021, Tampere University, ITU/ISO/IEC, project contributors
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 * 
 * * Redistributions of source code must retain the above copyright notice, this
 *   list of conditions and the following disclaimer.
 * 
 * * Redistributions in binary form must reproduce the above copyright notice, this
 *   list of conditions and the following disclaimer in the documentation and/or
 *   other materials provided with the distribution.
 * 
 * * Neither the name of the Tampere University or ITU/ISO/IEC nor the names of its
 *   contributors may be used to endorse or promote products derived from
 *   this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * INCLUDING NEGLIGENCE OR OTHERWISE ARISING IN ANY WAY OUT OF THE USE OF THIS
 ****************************************************************************/

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

#include "scalinglist.h"
#include "rdo.h"
#include "tables.h"


const uint8_t kvz_g_scaling_list_num[4]    = { 6, 6, 6, 2};
const uint16_t kvz_g_scaling_list_size[4]  = {   16,  64, 256,1024};
static const uint8_t g_scaling_list_size_x[4] = { 4, 8,16,32};

static const coeff_t g_quant_default_4x4[16] =
{
  16,16,16,16,
  16,16,16,16,
  16,16,16,16,
  16,16,16,16
};

static const coeff_t g_quant_intra_default_8x8[64] =
{
  16,16,16,16,17,18,21,24,
  16,16,16,16,17,19,22,25,
  16,16,17,18,20,22,25,29,
  16,16,18,21,24,27,31,36,
  17,17,20,24,30,35,41,47,
  18,19,22,27,35,44,54,65,
  21,22,25,31,41,54,70,88,
  24,25,29,36,47,65,88,115
};

static const coeff_t g_quant_inter_default_8x8[64] =
{
  16,16,16,16,17,18,20,24,
  16,16,16,17,18,20,24,25,
  16,16,17,18,20,24,25,28,
  16,17,18,20,24,25,28,33,
  17,18,20,24,25,28,33,41,
  18,20,24,25,28,33,41,54,
  20,24,25,28,33,41,54,71,
  24,25,28,33,41,54,71,91
};

const coeff_t kvz_g_quant_scales[6]        = { 26214,23302,20560,18396,16384,14564 };
const coeff_t kvz_g_inv_quant_scales[6]    = { 40,45,51,57,64,72 };


/**
 * \brief Initialize scaling lists
 *
 */
void kvz_scalinglist_init(scaling_list_t * const scaling_list)
{
  uint32_t sizeId,listId,qp;

  for (sizeId = 0; sizeId < 4; sizeId++) {
    for (listId = 0; listId < kvz_g_scaling_list_num[sizeId]; listId++) {
      for (qp = 0; qp < 6; qp++) {
        if (!(sizeId == 3 && listId == 3)) {
          scaling_list->quant_coeff[sizeId][listId][qp]    = (coeff_t*)calloc(kvz_g_scaling_list_size[sizeId], sizeof(int32_t));
          scaling_list->de_quant_coeff[sizeId][listId][qp] = (coeff_t*)calloc(kvz_g_scaling_list_size[sizeId], sizeof(int32_t));
          scaling_list->error_scale[sizeId][listId][qp]    = (double*)calloc(kvz_g_scaling_list_size[sizeId], sizeof(double));
        }
      }
      scaling_list->scaling_list_coeff[sizeId][listId] = (coeff_t*)calloc(MIN(MAX_MATRIX_COEF_NUM, kvz_g_scaling_list_size[sizeId]), sizeof(int32_t));
    }
  }
  // alias, assign pointer to an existing array
  for (qp = 0; qp < 6; qp++) {
    scaling_list->quant_coeff[3][3][qp]    = scaling_list->quant_coeff[3][1][qp];
    scaling_list->de_quant_coeff[3][3][qp] = scaling_list->de_quant_coeff[3][1][qp];
    scaling_list->error_scale[3][3][qp]    = scaling_list->error_scale[3][1][qp];
  }
  
  //Initialize dc (otherwise we switch on undef in kvz_scalinglist_set)
  for (sizeId = 0; sizeId < SCALING_LIST_SIZE_NUM; ++sizeId) {
    for (listId = 0; listId < SCALING_LIST_NUM; ++listId) {
      scaling_list->scaling_list_dc[sizeId][listId] = 0;
    }
  }
  
  scaling_list->enable = 0;
  scaling_list->use_default_list = 0;
}

/**
 * \brief Destroy scaling list allocated memory
 *
 */
void kvz_scalinglist_destroy(scaling_list_t * const scaling_list)
{
  uint32_t sizeId,listId,qp;

  for (sizeId = 0; sizeId < 4; sizeId++) {
    for (listId = 0; listId < kvz_g_scaling_list_num[sizeId]; listId++) {
      for (qp = 0; qp < 6; qp++) {
        if (!(sizeId == 3 && listId == 3)) {
          FREE_POINTER(scaling_list->quant_coeff[sizeId][listId][qp]);
          FREE_POINTER(scaling_list->de_quant_coeff[sizeId][listId][qp]);
          FREE_POINTER(scaling_list->error_scale[sizeId][listId][qp]);
        }
      }
      FREE_POINTER(scaling_list->scaling_list_coeff[sizeId][listId]);
    }
  }
}

int kvz_scalinglist_parse(scaling_list_t * const scaling_list, FILE *fp)
{
  #define LINE_BUFSIZE 1024
  static const char matrix_type[4][6][20] =
  {
    {
      "INTRA4X4_LUMA",
      "INTRA4X4_CHROMAU",
      "INTRA4X4_CHROMAV",
      "INTER4X4_LUMA",
      "INTER4X4_CHROMAU",
      "INTER4X4_CHROMAV"
    },
    {
      "INTRA8X8_LUMA",
      "INTRA8X8_CHROMAU",
      "INTRA8X8_CHROMAV",
      "INTER8X8_LUMA",
      "INTER8X8_CHROMAU",
      "INTER8X8_CHROMAV"
    },
    {
      "INTRA16X16_LUMA",
      "INTRA16X16_CHROMAU",
      "INTRA16X16_CHROMAV",
      "INTER16X16_LUMA",
      "INTER16X16_CHROMAU",
      "INTER16X16_CHROMAV"
    },
    {
      "INTRA32X32_LUMA",
      "INTER32X32_LUMA",
    },
  };
  static const char matrix_type_dc[2][6][22] =
  {
    {
      "INTRA16X16_LUMA_DC",
      "INTRA16X16_CHROMAU_DC",
      "INTRA16X16_CHROMAV_DC",
      "INTER16X16_LUMA_DC",
      "INTER16X16_CHROMAU_DC",
      "INTER16X16_CHROMAV_DC"
    },
    {
      "INTRA32X32_LUMA_DC",
      "INTER32X32_LUMA_DC",
    },
  };

  uint32_t size_id;
  for (size_id = 0; size_id < SCALING_LIST_SIZE_NUM; size_id++) {
    uint32_t list_id;
    uint32_t size = MIN(MAX_MATRIX_COEF_NUM, (int32_t)kvz_g_scaling_list_size[size_id]);
    //const uint32_t * const scan = (size_id == 0) ? kvz_g_sig_last_scan[SCAN_DIAG][1] : g_sig_last_scan_32x32;

    for (list_id = 0; list_id < kvz_g_scaling_list_num[size_id]; list_id++) {
      int found;
      uint32_t i;
      int32_t data;
      //This IS valid (our pointer is dynamically allocated in kvz_scalinglist_init)
      int32_t *coeff = (int32_t*) scaling_list->scaling_list_coeff[size_id][list_id];
      char line[LINE_BUFSIZE + 1] = { 0 }; // +1 for null-terminator

      // Go back for each matrix.
      fseek(fp, 0, SEEK_SET);

      do {
        if (!fgets(line, LINE_BUFSIZE, fp) ||
            ((found = !!strstr(line, matrix_type[size_id][list_id])) == 0 && feof(fp)))
          return 0;
      } while (!found);

      for (i = 0; i < size;) {
        char *p;
        if (!fgets(line, LINE_BUFSIZE, fp))
          return 0;
        p = line;

        // Read coefficients per line.
        // The comma (,) character is used as a separator.
        // The coefficients are stored in up-right diagonal order.
        do {
          int ret = sscanf(p, "%d", &data);
          if (ret != 1)
            break;
          else if (data < 1 || data > 255)
            return 0;

          coeff[i++] = data;
          if (i == size)
            break;

          // Seek to the next newline, null-terminator or comma.
          while (*p != '\n' && *p != '\0' && *p != ',')
            ++p;
          if (*p == ',')
            ++p;
        } while (*p != '\n' && *p != '\0');
      }

      // Set DC value.
      if (size_id >= SCALING_LIST_16x16) {
        fseek(fp, 0, SEEK_SET);

        do {
          if (!fgets(line, LINE_BUFSIZE, fp) ||
              ((found = !!strstr(line, matrix_type_dc[size_id - SCALING_LIST_16x16][list_id])) == 0 && feof(fp)))
            return 0;
        } while (!found);
        if (1 != fscanf(fp, "%d", &data) || data < 1 || data > 255)
          return 0;

        scaling_list->scaling_list_dc[size_id][list_id] = data;
      } else
        scaling_list->scaling_list_dc[size_id][list_id] = coeff[0];
    }
  }

  scaling_list->enable = 1;
  return 1;
  #undef LINE_BUFSIZE
}

const coeff_t *kvz_scalinglist_get_default(const uint32_t size_id, const uint32_t list_id)
{
  const coeff_t *list_ptr = g_quant_intra_default_8x8; // Default to "8x8" intra
  switch(size_id) {
    case SCALING_LIST_4x4:
      list_ptr = g_quant_default_4x4;
      break;
    case SCALING_LIST_8x8:
    case SCALING_LIST_16x16:
      if (list_id > 2) list_ptr = g_quant_inter_default_8x8;
      break;
    case SCALING_LIST_32x32:
      if (list_id > 0) list_ptr = g_quant_inter_default_8x8;
      break;
  }
  return list_ptr;
}


/**
 * \brief get scaling list for decoder
 *
 */
static void scalinglist_process_dec(const coeff_t * const coeff, coeff_t *dequantcoeff,
                                    int32_t inv_quant_scales, uint32_t height,
                                    uint32_t width, uint32_t ratio,
                                    int32_t size_num, uint32_t dc,
                                    uint8_t flat)
{
  uint32_t j,i;

  // Flat scaling list
  if (flat) {
    for (j = 0; j < height * width; j++) {
      *dequantcoeff++ = inv_quant_scales<<4;
    }
  } else {
    for (j = 0; j < height; j++) {
      for (i = 0; i < width; i++) {
        dequantcoeff[j*width + i] = inv_quant_scales * coeff[size_num * (j / ratio) + i / ratio];
      }
    }
    if (ratio > 1) {
      dequantcoeff[0] = inv_quant_scales * dc;
    }
  }
}

/**
 * \brief get scaling list for encoder
 *
 */
void kvz_scalinglist_process_enc(const coeff_t * const coeff, coeff_t * quantcoeff, const int32_t quant_scales,
                             const uint32_t height, const uint32_t width, const uint32_t ratio, 
                             const int32_t size_num, const uint32_t dc, const uint8_t flat)
{
  uint32_t j,i;
  int32_t nsqth = (height < width) ? 4: 1; //!< height ratio for NSQT
  int32_t nsqtw = (width < height) ? 4: 1; //!< width ratio for NSQT

  // Flat scaling list
  if (flat) {
    for (j = 0; j < height * width; j++) {
      *quantcoeff++ = quant_scales>>4;
    }
  } else {
    for (j = 0; j < height; j++) {
      for (i = 0; i < width; i++) {
        uint32_t coeffpos  = size_num * (j * nsqth / ratio) + i * nsqtw / ratio;
        quantcoeff[j*width + i] = quant_scales / ((coeffpos > 63) ? 1 : coeff[coeffpos]);
      }
    }
    if (ratio > 1) {
      quantcoeff[0] = quant_scales / dc;
    }
  }
}



/** set error scale coefficients
 * \param list List ID
 * \param uiSize Size
 * \param uiQP Quantization parameter
 */
static void scalinglist_set_err_scale(uint8_t bitdepth, scaling_list_t * const scaling_list, uint32_t list,uint32_t size, uint32_t qp)
{
  uint32_t log2_tr_size   = kvz_g_convert_to_bit[ g_scaling_list_size_x[size] ] + 2;
  int32_t transform_shift = MAX_TR_DYNAMIC_RANGE - bitdepth - log2_tr_size;  // Represents scaling through forward transform

  uint32_t i,max_num_coeff  = kvz_g_scaling_list_size[size];
  const coeff_t *quantcoeff = scaling_list->quant_coeff[size][list][qp];
  //This cast is allowed, since error_scale is a malloc'd pointer in kvz_scalinglist_init
  double *err_scale         = (double *) scaling_list->error_scale[size][list][qp];

  // Compensate for scaling of bitcount in Lagrange cost function
  double scale = CTX_FRAC_ONE_BIT;
  // Compensate for scaling through forward transform
  scale = scale*pow(2.0,-2.0*transform_shift);
  for(i=0;i<max_num_coeff;i++) {
    err_scale[i] = scale / quantcoeff[i] / quantcoeff[i] / (1<<(2*(bitdepth-8)));
  }
}


/**
 * \brief set scaling lists
 *
 */
void kvz_scalinglist_set(scaling_list_t * const scaling_list, const coeff_t* const coeff, uint32_t listId, uint32_t sizeId, uint32_t qp)
{
  const uint32_t width  = g_scaling_list_size_x[sizeId];
  const uint32_t height = g_scaling_list_size_x[sizeId];
  const uint32_t ratio  = g_scaling_list_size_x[sizeId] / MIN(8, g_scaling_list_size_x[sizeId]);
  const uint32_t dc = scaling_list->scaling_list_dc[sizeId][listId] != 0 ? scaling_list->scaling_list_dc[sizeId][listId] : 16;
  //These cast are allowed, since these are pointer's to malloc'd area in kvz_scalinglist_init
  coeff_t*quantcoeff   = (coeff_t*) scaling_list->quant_coeff[sizeId][listId][qp];
  coeff_t*dequantcoeff = (coeff_t*) scaling_list->de_quant_coeff[sizeId][listId][qp];

  // Encoder list
  kvz_scalinglist_process_enc(coeff, quantcoeff, kvz_g_quant_scales[qp]<<4, height, width, ratio,
                          MIN(8, g_scaling_list_size_x[sizeId]), dc, !scaling_list->enable);
  // Decoder list
  scalinglist_process_dec(coeff, dequantcoeff, kvz_g_inv_quant_scales[qp], height, width, ratio,
                          MIN(8, g_scaling_list_size_x[sizeId]), dc, !scaling_list->enable);


  // TODO: support NSQT
  // if(sizeId == /*SCALING_LIST_32x32*/3 || sizeId == /*SCALING_LIST_16x16*/2) { //for NSQT
  //   quantcoeff   = g_quant_coeff[listId][qp][sizeId-1][/*SCALING_LIST_VER*/1];
  //   kvz_scalinglist_process_enc(coeff,quantcoeff,g_quantScales[qp]<<4,height,width>>2,ratio,MIN(8,g_scalingListSizeX[sizeId]),/*scalingList->getScalingListDC(sizeId,listId)*/0);

  //   quantcoeff   = g_quant_coeff[listId][qp][sizeId-1][/*SCALING_LIST_HOR*/2];
  //   kvz_scalinglist_process_enc(coeff,quantcoeff,g_quantScales[qp]<<4,height>>2,width,ratio,MIN(8,g_scalingListSizeX[sizeId]),/*scalingList->getScalingListDC(sizeId,listId)*/0);
  // }
}

/**
 * \brief
 *
 */
void kvz_scalinglist_process(scaling_list_t * const scaling_list, uint8_t bitdepth)
{
  uint32_t size,list,qp;

  for (size = 0; size < SCALING_LIST_SIZE_NUM; size++) {
    for (list = 0; list < kvz_g_scaling_list_num[size]; list++) {
      const coeff_t* const list_ptr = scaling_list->use_default_list ?
                                       kvz_scalinglist_get_default(size, list) :
                                       scaling_list->scaling_list_coeff[size][list];

      for (qp = 0; qp < SCALING_LIST_REM_NUM; qp++) {
        kvz_scalinglist_set(scaling_list, list_ptr, list, size, qp);
        scalinglist_set_err_scale(bitdepth, scaling_list, list, size, qp);
      }
    }
  }
}