/*
 * HEIF codec.
 * Copyright (c) 2017 Dirk Farin <dirk.farin@gmail.com>
 *
 * This file is part of libheif.
 *
 * libheif 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.
 *
 * libheif 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 libheif.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "libheif/heif.h"
#include "libheif/heif_plugin.h"
#include "encoder_rav1e.h"
#include <vector>
#include <memory>
#include <cstring>
#include <cassert>
#include <string>
#include <algorithm>

#include <iostream>  // TODO: remove me

#include "rav1e.h"


struct encoder_struct_rav1e
{
  int speed; // 0-10

  int quality; // TODO: not sure yet how to map quality to min/max q
  int min_q;
  int threads;

  int tile_rows = 1; // 1,2,4,8,16,32,64
  int tile_cols = 1; // 1,2,4,8,16,32,64

  heif_chroma chroma;

  // --- output

  std::vector<uint8_t> compressed_data;
  bool data_read = false;
};

//static const char* kError_out_of_memory = "Out of memory";

static const char* kParam_min_q = "min-q";
static const char* kParam_threads = "threads";
static const char* kParam_speed = "speed";

static const char* kParam_chroma = "chroma";
static const char* const kParam_chroma_valid_values[] = {
    "420", "422", "444", nullptr
};

static int valid_tile_num_values[] = {1, 2, 4, 8, 16, 32, 64};

static struct heif_error heif_error_codec_library_error = {heif_error_Encoder_plugin_error,
                                                           heif_suberror_Unspecified,
                                                           "rav1e error"};

static const int RAV1E_PLUGIN_PRIORITY = 20;

#define MAX_PLUGIN_NAME_LENGTH 80

static char plugin_name[MAX_PLUGIN_NAME_LENGTH];


static void rav1e_set_default_parameters(void* encoder);


static const char* rav1e_plugin_name()
{
  snprintf(plugin_name, MAX_PLUGIN_NAME_LENGTH, "Rav1e encoder v%s", rav1e_version_short());

  return plugin_name;
}


#define MAX_NPARAMETERS 10

static struct heif_encoder_parameter rav1e_encoder_params[MAX_NPARAMETERS];
static const struct heif_encoder_parameter* rav1e_encoder_parameter_ptrs[MAX_NPARAMETERS + 1];

static void rav1e_init_parameters()
{
  struct heif_encoder_parameter* p = rav1e_encoder_params;
  const struct heif_encoder_parameter** d = rav1e_encoder_parameter_ptrs;
  int i = 0;

  assert(i < MAX_NPARAMETERS);
  p->version = 2;
  p->name = kParam_speed;
  p->type = heif_encoder_parameter_type_integer;
  p->integer.default_value = 8;
  p->has_default = true;
  p->integer.have_minimum_maximum = true;
  p->integer.minimum = 0;
  p->integer.maximum = 10;
  p->integer.valid_values = NULL;
  p->integer.num_valid_values = 0;
  d[i++] = p++;

  assert(i < MAX_NPARAMETERS);
  p->version = 2;
  p->name = kParam_threads;
  p->type = heif_encoder_parameter_type_integer;
  p->integer.default_value = 4;
  p->has_default = true;
  p->integer.have_minimum_maximum = true;
  p->integer.minimum = 1;
  p->integer.maximum = 16;
  p->integer.valid_values = NULL;
  p->integer.num_valid_values = 0;
  d[i++] = p++;

  assert(i < MAX_NPARAMETERS);
  p->version = 2;
  p->name = "tile-rows";
  p->type = heif_encoder_parameter_type_integer;
  p->integer.default_value = 4;
  p->has_default = true;
  p->integer.have_minimum_maximum = false;
  p->integer.valid_values = valid_tile_num_values;
  p->integer.num_valid_values = 7;
  d[i++] = p++;

  assert(i < MAX_NPARAMETERS);
  p->version = 2;
  p->name = "tile-cols";
  p->type = heif_encoder_parameter_type_integer;
  p->integer.default_value = 4;
  p->has_default = true;
  p->integer.have_minimum_maximum = false;
  p->integer.valid_values = valid_tile_num_values;
  p->integer.num_valid_values = 7;
  d[i++] = p++;


  /*
  assert(i < MAX_NPARAMETERS);
  p->version = 2;
  p->name = heif_encoder_parameter_name_quality;
  p->type = heif_encoder_parameter_type_integer;
  p->integer.default_value = 50;
  p->has_default = true;
  p->integer.have_minimum_maximum = true;
  p->integer.minimum = 0;
  p->integer.maximum = 100;
  p->integer.valid_values = NULL;
  p->integer.num_valid_values = 0;
  d[i++] = p++;
*/

  /*
  assert(i < MAX_NPARAMETERS);
  p->version = 2;
  p->name = heif_encoder_parameter_name_lossless;
  p->type = heif_encoder_parameter_type_boolean;
  p->boolean.default_value = false;
  p->has_default = true;
  d[i++] = p++;
*/

  assert(i < MAX_NPARAMETERS);
  p->version = 2;
  p->name = kParam_chroma;
  p->type = heif_encoder_parameter_type_string;
  p->string.default_value = "420";
  p->has_default = true;
  p->string.valid_values = kParam_chroma_valid_values;
  d[i++] = p++;

  assert(i < MAX_NPARAMETERS);
  p->version = 2;
  p->name = kParam_min_q;
  p->type = heif_encoder_parameter_type_integer;
  p->integer.default_value = 0;
  p->has_default = true;
  p->integer.have_minimum_maximum = true;
  p->integer.minimum = 0;
  p->integer.maximum = 255;
  p->integer.valid_values = NULL;
  p->integer.num_valid_values = 0;
  d[i++] = p++;

  d[i++] = nullptr;
}


const struct heif_encoder_parameter** rav1e_list_parameters(void* encoder)
{
  return rav1e_encoder_parameter_ptrs;
}

static void rav1e_init_plugin()
{
  rav1e_init_parameters();
}


static void rav1e_cleanup_plugin()
{
}

struct heif_error rav1e_new_encoder(void** enc)
{
  auto* encoder = new encoder_struct_rav1e();
  struct heif_error err = heif_error_ok;

  *enc = encoder;

  // set default parameters

  rav1e_set_default_parameters(encoder);

  return err;
}

void rav1e_free_encoder(void* encoder_raw)
{
  auto* encoder = (struct encoder_struct_rav1e*) encoder_raw;

  delete encoder;
}


struct heif_error rav1e_set_parameter_quality(void* encoder_raw, int quality)
{
  auto* encoder = (struct encoder_struct_rav1e*) encoder_raw;

  if (quality < 0 || quality > 100) {
    return heif_error_invalid_parameter_value;
  }

  encoder->quality = quality;

  return heif_error_ok;
}

struct heif_error rav1e_get_parameter_quality(void* encoder_raw, int* quality)
{
  auto* encoder = (struct encoder_struct_rav1e*) encoder_raw;

  *quality = encoder->quality;

  return heif_error_ok;
}

struct heif_error rav1e_set_parameter_lossless(void* encoder_raw, int enable)
{
  struct encoder_struct_rav1e* encoder = (struct encoder_struct_rav1e*) encoder_raw;

  if (enable) {
    encoder->min_q = 0;
  }

  return heif_error_ok;
}

struct heif_error rav1e_get_parameter_lossless(void* encoder_raw, int* enable)
{
  struct encoder_struct_rav1e* encoder = (struct encoder_struct_rav1e*) encoder_raw;

  *enable = (encoder->min_q == 0);

  return heif_error_ok;
}

struct heif_error rav1e_set_parameter_logging_level(void* encoder_raw, int logging)
{
#if 0
  struct encoder_struct_x265* encoder = (struct encoder_struct_x265*)encoder_raw;

  if (logging<0 || logging>4) {
    return heif_error_invalid_parameter_value;
  }

  encoder->logLevel = logging;
#endif

  return heif_error_ok;
}

struct heif_error rav1e_get_parameter_logging_level(void* encoder_raw, int* loglevel)
{
#if 0
  struct encoder_struct_x265* encoder = (struct encoder_struct_x265*)encoder_raw;

  *loglevel = encoder->logLevel;
#else
  *loglevel = 0;
#endif

  return heif_error_ok;
}

#define set_value(paramname, paramvar) if (strcmp(name, paramname)==0) { encoder->paramvar = value; return heif_error_ok; }
#define get_value(paramname, paramvar) if (strcmp(name, paramname)==0) { *value = encoder->paramvar; return heif_error_ok; }


struct heif_error rav1e_set_parameter_integer(void* encoder_raw, const char* name, int value)
{
  struct encoder_struct_rav1e* encoder = (struct encoder_struct_rav1e*) encoder_raw;

  if (strcmp(name, heif_encoder_parameter_name_quality) == 0) {
    return rav1e_set_parameter_quality(encoder, value);
  }
  else if (strcmp(name, heif_encoder_parameter_name_lossless) == 0) {
    return rav1e_set_parameter_lossless(encoder, value);
  }

  set_value(kParam_min_q, min_q);
  set_value(kParam_threads, threads);
  set_value(kParam_speed, speed);
  set_value("tile-rows", tile_rows);
  set_value("tile-cols", tile_cols);

  return heif_error_unsupported_parameter;
}

struct heif_error rav1e_get_parameter_integer(void* encoder_raw, const char* name, int* value)
{
  struct encoder_struct_rav1e* encoder = (struct encoder_struct_rav1e*) encoder_raw;

  if (strcmp(name, heif_encoder_parameter_name_quality) == 0) {
    return rav1e_get_parameter_quality(encoder, value);
  }
  else if (strcmp(name, heif_encoder_parameter_name_lossless) == 0) {
    return rav1e_get_parameter_lossless(encoder, value);
  }

  get_value(kParam_min_q, min_q);
  get_value(kParam_threads, threads);
  get_value(kParam_speed, speed);
  get_value("tile-rows", tile_rows);
  get_value("tile-cols", tile_cols);

  return heif_error_unsupported_parameter;
}


struct heif_error rav1e_set_parameter_boolean(void* encoder_raw, const char* name, int value)
{
  auto* encoder = (struct encoder_struct_rav1e*) encoder_raw;

  if (strcmp(name, heif_encoder_parameter_name_lossless) == 0) {
    return rav1e_set_parameter_lossless(encoder, value);
  }

  //set_value(kParam_realtime, realtime_mode);

  return heif_error_unsupported_parameter;
}

struct heif_error rav1e_get_parameter_boolean(void* encoder_raw, const char* name, int* value)
{
  auto* encoder = (struct encoder_struct_rav1e*) encoder_raw;

  if (strcmp(name, heif_encoder_parameter_name_lossless) == 0) {
    return rav1e_get_parameter_lossless(encoder, value);
  }

  //get_value(kParam_realtime, realtime_mode);

  return heif_error_unsupported_parameter;
}


struct heif_error rav1e_set_parameter_string(void* encoder_raw, const char* name, const char* value)
{
  auto* encoder = (struct encoder_struct_rav1e*) encoder_raw;

  if (strcmp(name, kParam_chroma) == 0) {
    if (strcmp(value, "420") == 0) {
      encoder->chroma = heif_chroma_420;
      return heif_error_ok;
    }
    else if (strcmp(value, "422") == 0) {
      encoder->chroma = heif_chroma_422;
      return heif_error_ok;
    }
    else if (strcmp(value, "444") == 0) {
      encoder->chroma = heif_chroma_444;
      return heif_error_ok;
    }
    else {
      return heif_error_invalid_parameter_value;
    }
  }

  return heif_error_unsupported_parameter;
}


static void save_strcpy(char* dst, int dst_size, const char* src)
{
  strncpy(dst, src, dst_size - 1);
  dst[dst_size - 1] = 0;
}


struct heif_error rav1e_get_parameter_string(void* encoder_raw, const char* name,
                                             char* value, int value_size)
{
  struct encoder_struct_rav1e* encoder = (struct encoder_struct_rav1e*) encoder_raw;

  if (strcmp(name, kParam_chroma) == 0) {
    switch (encoder->chroma) {
      case heif_chroma_420:
        save_strcpy(value, value_size, "420");
        break;
      case heif_chroma_422:
        save_strcpy(value, value_size, "422");
        break;
      case heif_chroma_444:
        save_strcpy(value, value_size, "444");
        break;
      default:
        assert(false);
        return heif_error_invalid_parameter_value;
    }
    return heif_error_ok;
  }

  return heif_error_unsupported_parameter;
}


static void rav1e_set_default_parameters(void* encoder)
{
  for (const struct heif_encoder_parameter** p = rav1e_encoder_parameter_ptrs; *p; p++) {
    const struct heif_encoder_parameter* param = *p;

    if (param->has_default) {
      switch (param->type) {
        case heif_encoder_parameter_type_integer:
          rav1e_set_parameter_integer(encoder, param->name, param->integer.default_value);
          break;
        case heif_encoder_parameter_type_boolean:
          rav1e_set_parameter_boolean(encoder, param->name, param->boolean.default_value);
          break;
        case heif_encoder_parameter_type_string:
          rav1e_set_parameter_string(encoder, param->name, param->string.default_value);
          break;
      }
    }
  }
}


void rav1e_query_input_colorspace(heif_colorspace* colorspace, heif_chroma* chroma)
{
  *colorspace = heif_colorspace_YCbCr;
  *chroma = heif_chroma_420;
}


void rav1e_query_input_colorspace2(void* encoder_raw, heif_colorspace* colorspace, heif_chroma* chroma)
{
  auto* encoder = (struct encoder_struct_rav1e*) encoder_raw;

  *colorspace = heif_colorspace_YCbCr;
  *chroma = encoder->chroma;
}


struct heif_error rav1e_encode_image(void* encoder_raw, const struct heif_image* image,
                                     heif_image_input_class input_class)
{
  auto* encoder = (struct encoder_struct_rav1e*) encoder_raw;

  const heif_chroma chroma = heif_image_get_chroma_format(image);

  uint8_t yShift = 0;
  RaChromaSampling chromaSampling;
  RaChromaSamplePosition chromaPosition;
  RaPixelRange rav1eRange;

  if (input_class == heif_image_input_class_alpha) {
    chromaSampling = RA_CHROMA_SAMPLING_CS420; // I can't seem to get RA_CHROMA_SAMPLING_CS400 to work right now, unfortunately
    chromaPosition = RA_CHROMA_SAMPLE_POSITION_UNKNOWN; // TODO: set to CENTER when AV1 and rav1e supports this
    yShift = 1;
  }
  else {
    switch (chroma) {
      case heif_chroma_444:
        chromaSampling = RA_CHROMA_SAMPLING_CS444;
        chromaPosition = RA_CHROMA_SAMPLE_POSITION_COLOCATED;
        break;
      case heif_chroma_422:
        chromaSampling = RA_CHROMA_SAMPLING_CS422;
        chromaPosition = RA_CHROMA_SAMPLE_POSITION_COLOCATED;
        break;
      case heif_chroma_420:
        chromaSampling = RA_CHROMA_SAMPLING_CS420;
        chromaPosition = RA_CHROMA_SAMPLE_POSITION_UNKNOWN; // TODO: set to CENTER when AV1 and rav1e supports this
        yShift = 1;
        break;
      default:
        return heif_error_codec_library_error;
    }
  }

  struct heif_color_profile_nclx* nclx = nullptr;
  heif_error err = heif_image_get_nclx_color_profile(image, &nclx);
  if (err.code != heif_error_Ok) {
    nclx = nullptr;
  }

  // make sure NCLX profile is deleted at end of function
  auto nclx_deleter = std::unique_ptr<heif_color_profile_nclx, void (*)(heif_color_profile_nclx*)>(nclx, heif_nclx_color_profile_free);

  rav1eRange = RA_PIXEL_RANGE_FULL;
  if (nclx) {
    rav1eRange = nclx->full_range_flag ? RA_PIXEL_RANGE_FULL : RA_PIXEL_RANGE_LIMITED;
  }

  int bitDepth = heif_image_get_bits_per_pixel(image, heif_channel_Y);

  auto rav1eConfigRaw = rav1e_config_default();
  auto rav1eConfig = std::shared_ptr<RaConfig>(rav1eConfigRaw, [](RaConfig* c) { rav1e_config_unref(c); });

  if (rav1e_config_set_pixel_format(rav1eConfig.get(), (uint8_t) bitDepth, chromaSampling, chromaPosition, rav1eRange) < 0) {
    return heif_error_codec_library_error;
  }

  if (rav1e_config_parse(rav1eConfig.get(), "still_picture", "true") == -1) {
    return heif_error_codec_library_error;
  }
  if (rav1e_config_parse_int(rav1eConfig.get(), "width", heif_image_get_width(image, heif_channel_Y)) == -1) {
    return heif_error_codec_library_error;
  }
  if (rav1e_config_parse_int(rav1eConfig.get(), "height", heif_image_get_height(image, heif_channel_Y)) == -1) {
    return heif_error_codec_library_error;
  }
  if (rav1e_config_parse_int(rav1eConfig.get(), "threads", encoder->threads) == -1) {
    return heif_error_codec_library_error;
  }

  if (nclx &&
      (input_class == heif_image_input_class_normal ||
       input_class == heif_image_input_class_thumbnail)) {
    if (rav1e_config_set_color_description(rav1eConfig.get(),
                                           (RaMatrixCoefficients) nclx->matrix_coefficients,
                                           (RaColorPrimaries) nclx->color_primaries,
                                           (RaTransferCharacteristics) nclx->transfer_characteristics) == -1) {
      return heif_error_codec_library_error;
    }
  }

  if (rav1e_config_parse_int(rav1eConfig.get(), "min_quantizer", encoder->min_q) == -1) {
    return heif_error_codec_library_error;
  }

  int base_quantizer = ((100 - encoder->quality) * 255 + 50) / 100;

  if (rav1e_config_parse_int(rav1eConfig.get(), "quantizer", base_quantizer) == -1) {
    return heif_error_codec_library_error;
  }

  if (encoder->tile_rows != 1) {
    if (rav1e_config_parse_int(rav1eConfig.get(), "tile_rows", encoder->tile_rows) == -1) {
      return heif_error_codec_library_error;
    }
  }
  if (encoder->tile_cols != 1) {
    if (rav1e_config_parse_int(rav1eConfig.get(), "tile_cols", encoder->tile_cols) == -1) {
      return heif_error_codec_library_error;
    }
  }
  /*if (encoder->speed != -1)*/ {
    if (rav1e_config_parse_int(rav1eConfig.get(), "speed", encoder->speed) == -1) {
      return heif_error_codec_library_error;
    }
  }

  if (nclx) {
    rav1e_config_set_color_description(rav1eConfig.get(),
                                       (RaMatrixCoefficients) nclx->matrix_coefficients,
                                       (RaColorPrimaries) nclx->color_primaries,
                                       (RaTransferCharacteristics) nclx->transfer_characteristics);
  }

  RaContext* rav1eContextRaw = rav1e_context_new(rav1eConfig.get());
  if (!rav1eContextRaw) {
    return heif_error_codec_library_error;
  }
  auto rav1eContext = std::shared_ptr<RaContext>(rav1eContextRaw, [](RaContext* ctx) { rav1e_context_unref(ctx); });


  // --- copy libheif image to rav1e image

  auto rav1eFrameRaw = rav1e_frame_new(rav1eContext.get());
  auto rav1eFrame = std::shared_ptr<RaFrame>(rav1eFrameRaw, [](RaFrame* frm) { rav1e_frame_unref(frm); });

  int byteWidth = (bitDepth > 8) ? 2 : 1;
  // if (input_class == heif_image_input_class_alpha) {
  //} else
  {
    int strideY;
    const uint8_t* Y = heif_image_get_plane_readonly(image, heif_channel_Y, &strideY);
    int strideCb;
    const uint8_t* Cb = heif_image_get_plane_readonly(image, heif_channel_Cb, &strideCb);
    int strideCr;
    const uint8_t* Cr = heif_image_get_plane_readonly(image, heif_channel_Cr, &strideCr);


    uint32_t height = heif_image_get_height(image, heif_channel_Y);

    uint32_t uvHeight = (height + yShift) >> yShift;
    rav1e_frame_fill_plane(rav1eFrame.get(), 0, Y, strideY * height, strideY, byteWidth);
    rav1e_frame_fill_plane(rav1eFrame.get(), 1, Cb, strideCb * uvHeight, strideCb, byteWidth);
    rav1e_frame_fill_plane(rav1eFrame.get(), 2, Cr, strideCr * uvHeight, strideCr, byteWidth);
  }

  RaEncoderStatus encoderStatus = rav1e_send_frame(rav1eContext.get(), rav1eFrame.get());
  if (encoderStatus != 0) {
    return heif_error_codec_library_error;
  }

  // flush encoder
  encoderStatus = rav1e_send_frame(rav1eContext.get(), nullptr);
  if (encoderStatus != 0) {
    return heif_error_codec_library_error;
  }

  RaPacket* pkt = nullptr;
  encoderStatus = rav1e_receive_packet(rav1eContext.get(), &pkt);
  if (encoderStatus != 0) {
    return heif_error_codec_library_error;
  }

  if (pkt && pkt->data && (pkt->len > 0)) {
    encoder->compressed_data.resize(pkt->len);
    memcpy(encoder->compressed_data.data(), pkt->data, pkt->len);
    encoder->data_read = false;
  }

  if (pkt) {
    rav1e_packet_unref(pkt);
  }

  return heif_error_ok;
}

struct heif_error rav1e_get_compressed_data(void* encoder_raw, uint8_t** data, int* size,
                                            enum heif_encoded_data_type* type)
{
  auto* encoder = (struct encoder_struct_rav1e*) encoder_raw;

  if (encoder->data_read) {
    *data = nullptr;
    *size = 0;
  }
  else {
    *data = encoder->compressed_data.data();
    *size = (int) encoder->compressed_data.size();
    encoder->data_read = true;
  }

  return heif_error_ok;
}


static const struct heif_encoder_plugin encoder_plugin_rav1e
    {
        /* plugin_api_version */ 3,
        /* compression_format */ heif_compression_AV1,
        /* id_name */ "rav1e",
        /* priority */ RAV1E_PLUGIN_PRIORITY,
        /* supports_lossy_compression */ true,
        /* supports_lossless_compression */ false,
        /* get_plugin_name */ rav1e_plugin_name,
        /* init_plugin */ rav1e_init_plugin,
        /* cleanup_plugin */ rav1e_cleanup_plugin,
        /* new_encoder */ rav1e_new_encoder,
        /* free_encoder */ rav1e_free_encoder,
        /* set_parameter_quality */ rav1e_set_parameter_quality,
        /* get_parameter_quality */ rav1e_get_parameter_quality,
        /* set_parameter_lossless */ rav1e_set_parameter_lossless,
        /* get_parameter_lossless */ rav1e_get_parameter_lossless,
        /* set_parameter_logging_level */ rav1e_set_parameter_logging_level,
        /* get_parameter_logging_level */ rav1e_get_parameter_logging_level,
        /* list_parameters */ rav1e_list_parameters,
        /* set_parameter_integer */ rav1e_set_parameter_integer,
        /* get_parameter_integer */ rav1e_get_parameter_integer,
        /* set_parameter_boolean */ rav1e_set_parameter_boolean,
        /* get_parameter_boolean */ rav1e_get_parameter_boolean,
        /* set_parameter_string */ rav1e_set_parameter_string,
        /* get_parameter_string */ rav1e_get_parameter_string,
        /* query_input_colorspace */ rav1e_query_input_colorspace,
        /* encode_image */ rav1e_encode_image,
        /* get_compressed_data */ rav1e_get_compressed_data,
        /* query_input_colorspace (v2) */ rav1e_query_input_colorspace2,
        /* query_encoded_size (v3) */ nullptr
    };

const struct heif_encoder_plugin* get_encoder_plugin_rav1e()
{
  return &encoder_plugin_rav1e;
}


#if PLUGIN_RAV1E
heif_plugin_info plugin_info{
    1,
    heif_plugin_type_encoder,
    &encoder_plugin_rav1e
};
#endif
