/*
 * Copyright (C) 2023 - 2024 René Rebe, ExactCODE GmbH
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2. A copy of the GNU General
 * Public License can be found in the file LICENSE.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANT-
 * ABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
 * Public License for more details.
 *
 * Alternatively, commercial licensing options are available from the
 * copyright holder ExactCODE GmbH Germany.
 */

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

#include <iostream>
#include <sstream>
#include <vector>

#include <libheif/heif.h>

#include "heif.hh"

/* TODO:
 * dpi
 * exif
 * icc
 * mutli images
 * writing
 */

extern "C" {
  static int64_t heif_get_position(void* userdata) {
    std::istream* stream = (std::istream*)userdata;
    //std::cerr << __FUNCTION__ << std::endl;
    if (!stream->good())
      stream->clear();
    return stream->tellg();
  }
  
  static int heif_read(void* data, size_t size, void* userdata) {
    std::istream* stream = (std::istream*)userdata;
    //std::cerr << __FUNCTION__ << " " << size << std::endl;
    stream->read((char*)data, size);
    if (stream->good()) {
      return heif_error_code::heif_error_Ok;
    } else {
      stream->clear();
      return heif_error_code::heif_error_Invalid_input;
    }
  }
  
  static int heif_seek(int64_t position, void* userdata) {
    std::istream* stream = (std::istream*)userdata;
    //std::cerr << __FUNCTION__ << std::endl;
    stream->seekg(position);
    if (stream->good()) {
      return heif_error_code::heif_error_Ok;
    } else {
      stream->clear();
      return heif_error_code::heif_error_Invalid_input;
    }
  }
  
  enum heif_reader_grow_status heif_wait_for_file_size(int64_t target_size, void* userdata) {
    std::istream* stream = (std::istream*)userdata;
    
    size_t pos = stream->tellg();
    //std::cerr << __FUNCTION__ << " " << " " << target_size << " (at: " << pos << std::endl;
    stream->seekg(target_size);
    heif_reader_grow_status ret = heif_reader_grow_status_size_reached;
    if (!stream->good()) {
      stream->clear();
      ret = heif_reader_grow_status_size_beyond_eof;
    }
    
    stream->seekg(pos);
    
    return ret;
  }
}

bool HEIFCodec::initialized = false;

HEIFCodec::HEIFCodec () {
  registerCodec ("heic", this);
};

HEIFCodec::~HEIFCodec () {
  if (initialized) {
    heif_deinit();
    initialized = false;
  }
};

int HEIFCodec::readImage(std::istream* stream, Image& image, const std::string& decompress)
{
  if (!initialized) {
    heif_init(nullptr);
    initialized = true;
  }

  if (true) {
    char magic[12];
    
    stream->read(magic, sizeof(magic));
    stream->seekg(0);
    
    enum heif_filetype_result filetype_check =
      heif_check_filetype((uint8_t*)magic, 12);
    
    if ((filetype_check == heif_filetype_no) ||
	(filetype_check == heif_filetype_yes_unsupported)) {
      fprintf(stderr, "Input file is not an HEIF/AVIF file or unsupported\n");
      return false;
    }
  }
  
  heif_context* ctx = heif_context_alloc();
  if (!ctx) {
    fprintf(stderr, "Could not create context object\n");
    return false;
  }
  
  heif_reader reader = {
    1,
    heif_get_position,
    heif_read,
    heif_seek,
    heif_wait_for_file_size,
  };

  heif_error err;
  err = heif_context_read_from_reader(ctx, &reader, stream, nullptr);
  if (err.code != 0) {
    std::cerr << "Could not read HEIF/AVIF file: " << err.message << "\n";
    return false;
  }

  int num_images = heif_context_get_number_of_top_level_images(ctx);
  if (num_images == 0) {
    fprintf(stderr, "File doesn't contain any images\n");
    return false;
  }

  //std::cout << "File contains " << num_images << " images\n";
  
  std::vector<heif_item_id> image_IDs(num_images);
  num_images = heif_context_get_list_of_top_level_image_IDs(ctx, image_IDs.data(), num_images);

  for (int idx = 0; idx < num_images; ++idx) {
    heif_image_handle* handle;
    err = heif_context_get_image_handle(ctx, image_IDs[idx], &handle);
    if (err.code) {
      std::cerr << "Could not read HEIF/AVIF image " << idx << ": "
                << err.message << "\n";
      return false;
    }

    int has_alpha = heif_image_handle_has_alpha_channel(handle);
    heif_decoding_options* decode_options = heif_decoding_options_alloc();
    //encoder->UpdateDecodingOptions(handle, decode_options);

    //decode_options->strict_decoding = strict_decoding;
    //decode_options->decoder_id = decoder_id;

    //decode_options->color_conversion_options.preferred_chroma_upsampling_algorithm = heif_chroma_upsampling_nearest_neighbor;
    //decode_options->color_conversion_options.preferred_chroma_upsampling_algorithm = heif_chroma_upsampling_bilinear;
    //decode_options->color_conversion_options.only_use_preferred_chroma_algorithm = true;
    
    int bit_depth = heif_image_handle_get_luma_bits_per_pixel(handle);
    if (bit_depth < 0) {
      heif_decoding_options_free(decode_options);
      heif_image_handle_release(handle);
      std::cerr << "Input image has undefined bit-depth\n";
      return false;
    }

    heif_chroma chroma = has_alpha ? heif_chroma_interleaved_RGBA : heif_chroma_interleaved_RGB;
    if (bit_depth > 8)
      chroma = has_alpha ? heif_chroma_interleaved_RRGGBBAA_BE : heif_chroma_interleaved_RRGGBB_BE;
    
    
    heif_image* himage = 0;
    err = heif_decode_image(handle,
			    &himage,
			    heif_colorspace_RGB,
			    chroma,
			    decode_options);
    heif_decoding_options_free(decode_options);
    if (err.code) {
      heif_image_handle_release(handle);
      std::cerr << "Could not decode image: " << idx << ": " << err.message << "\n";
      return false;
    }

    // show decoding warnings
    for (int i = 0;; i++) {
      int n = heif_image_get_decoding_warnings(himage, i, &err, 1);
      if (n == 0) {
        break;
      }
      
      std::cerr << "Warning: " << err.message << "\n";
    }
    
    if (himage) {
      bool withAlpha = (heif_image_get_chroma_format(himage) == heif_chroma_interleaved_RGBA ||
			heif_image_get_chroma_format(himage) == heif_chroma_interleaved_RRGGBBAA_BE);
      
      int width = heif_image_get_width(himage, heif_channel_interleaved);
      int height = heif_image_get_height(himage, heif_channel_interleaved);
      
      int bitDepth = 8;
      int input_bpp = heif_image_get_bits_per_pixel_range(himage, heif_channel_interleaved);
      if (input_bpp > 8)
	bitDepth = 16;
      
      image.spp = withAlpha ? 4 : 3;
      image.bps = bitDepth;
      image.resize(width, height);
      image.setResolution(72, 72);
      
      const int stride = image.stride();
      uint8_t* data = image.getRawData();
      int stride_rgb = 0;
      const uint8_t* row_rgb =
	heif_image_get_plane_readonly(himage,
				      heif_channel_interleaved, &stride_rgb);
      for (int y = 0; y < height; ++y) {
	memcpy(data + stride * y, row_rgb + stride_rgb * y, stride);
      }
      
      heif_image_release(himage);
      heif_image_handle_release(handle);
    }
  }
  
  return true;
}


bool HEIFCodec::writeImage(std::ostream* stream, Image& image, int quality,
			     const std::string& compress)
{
  return false;
}

HEIFCodec heif_loader;
