#include "ami_nc.h"
#include "../../../old/nc2pro/hdf5_utils.h"
#include "../../hdf_utils.h"
#include "core/resources.h"
#include "logger.h"
#include "nlohmann/json_utils.h"
#include "projection/standard/proj_json.h"
#include "utils/string.h"
#include <H5Cpp.h>
#include <H5LTpublic.h>

namespace satdump
{
    namespace firstparty
    {
        AMINcProcessor::ParsedGK2AAMI AMINcProcessor::parse_gk2a_ami_netcdf(std::vector<uint8_t> &data)
        {
            ParsedGK2AAMI image_out;
            hsize_t image_dims[2];

            H5::H5File file(H5LTopen_file_image(data.data(), data.size(), H5F_ACC_RDONLY));

            std::vector<std::string> name_parts = splitString(hdfpp_read_attribute_string(file.openAttribute("file_name")), '_');
            image_out.channel = name_parts[3];
            std::transform(image_out.channel.begin(), image_out.channel.end(), image_out.channel.begin(), ::toupper);

            image_out.longitude = hdfpp_read_attribute_double(file.openAttribute("sub_longitude")) * 180 / M_PI;
            image_out.cfac = hdfpp_read_attribute_double(file.openAttribute("cfac"));
            image_out.lfac = hdfpp_read_attribute_double(file.openAttribute("lfac"));
            image_out.coff = hdfpp_read_attribute_double(file.openAttribute("coff"));
            image_out.loff = hdfpp_read_attribute_double(file.openAttribute("loff"));
            image_out.nominal_satellite_height = hdfpp_read_attribute_double(file.openAttribute("nominal_satellite_height"));
            image_out.calibration_scale = hdfpp_read_attribute_double(file.openAttribute("DN_to_Radiance_Gain"));
            image_out.calibration_offset = hdfpp_read_attribute_double(file.openAttribute("DN_to_Radiance_Offset"));
            if (file.attrExists("Radiance_to_Albedo_c"))
                image_out.kappa = hdfpp_read_attribute_double(file.openAttribute("Radiance_to_Albedo_c"));
            image_out.timestamp = hdfpp_read_attribute_double(file.openAttribute("observation_start_time")) + 946728000; // Jan 1, 2000 12:00 UTC
            image_out.sat_name = hdfpp_read_attribute_string(file.openAttribute("satellite_name"));

            {
                if (!file.nameExists("image_pixel_values"))
                    return image_out;

                auto dataset = file.openDataSet("image_pixel_values");

                int rank = dataset.getSpace().getSimpleExtentNdims();
                dataset.getSpace().getSimpleExtentDims(image_dims);

                if (rank != 2)
                    return image_out;

                image_out.img = image::Image(16, image_dims[1], image_dims[0], 1);
                dataset.read(image_out.img.raw_data(), H5::PredType::NATIVE_UINT16, H5::DataSpace(2, image_dims));
                int bit_depth = hdfpp_read_attribute_double(file.openDataSet("image_pixel_values").openAttribute("number_of_valid_bits_per_pixel"));
                int bit_depth_exponentiation = pow(2, bit_depth) - 1;
                image_out.calibration_scale /= pow(2, 16 - bit_depth);

                for (size_t i = 0; i < image_out.img.size(); i++)
                {
                    if (image_out.img.get(i) == bit_depth_exponentiation)
                        image_out.img.set(i, 0);
                    else
                        image_out.img.set(i, image_out.img.get(i) << (16 - bit_depth));
                }
            }

            return image_out;
        }

        void AMINcProcessor::ingestFile(std::vector<uint8_t> vec) { all_images.push_back(parse_gk2a_ami_netcdf(vec)); }

        std::shared_ptr<satdump::products::Product> AMINcProcessor::getProduct()
        {
            // Saving
            std::shared_ptr<satdump::products::ImageProduct> ami_products = std::make_shared<satdump::products::ImageProduct>();
            ami_products->instrument_name = "ami";

            time_t prod_timestamp = 0;
            std::string sat_name = "GK-2A";
            size_t largest_width = 0, largest_height = 0;
            double center_longitude = 0;
            ParsedGK2AAMI largestImg;

            for (int i = 0; i < (int)all_images.size(); i++)
            {
                auto &img = all_images[i];
                ami_products->images.push_back({i, "AMI-" + img.channel, img.channel, img.img, 16});

                bool waslarger = false;
                if (img.img.width() > largest_width)
                {
                    largest_width = img.img.width();
                    waslarger = true;
                }
                if (img.img.height() > largest_height)
                {
                    largest_height = img.img.height();
                    waslarger = true;
                }

                img.img.clear();
                if (waslarger)
                    largestImg = img;

                prod_timestamp = img.timestamp;
                sat_name = img.sat_name;
                center_longitude = img.longitude;
            }

            ami_products->set_product_timestamp(prod_timestamp);
            ami_products->set_product_source(sat_name);

            {
                nlohmann::json proj_cfg;

                constexpr double k = 624597.0334223134;
                double scalar_x = (pow(2.0, 16.0) / double(largestImg.cfac)) * k;
                double scalar_y = (pow(2.0, 16.0) / double(largestImg.lfac)) * k;

                for (auto &img : ami_products->images)
                    img.ch_transform = satdump::ChannelTransform().init_affine(double(largest_width) / img.image.width(), double(largest_height) / img.image.height(), 0, 0);

                proj_cfg["type"] = "geos";
                proj_cfg["lon0"] = center_longitude;
                proj_cfg["sweep_x"] = false;
                proj_cfg["scalar_x"] = scalar_x;
                proj_cfg["scalar_y"] = scalar_y;
                proj_cfg["offset_x"] = double(largestImg.coff) * -scalar_x;
                proj_cfg["offset_y"] = double(largestImg.loff) * -scalar_y;
                proj_cfg["width"] = largest_width;
                proj_cfg["height"] = largest_height;
                proj_cfg["altitude"] = 35786023.00;
                ami_products->set_proj_cfg(proj_cfg);
            }

            nlohmann::json calib_cfg;
            for (size_t i = 0; i < all_images.size(); i++)
            {
                calib_cfg["vars"]["scale"][i] = all_images[i].calibration_scale;
                calib_cfg["vars"]["offset"][i] = all_images[i].calibration_offset;
                calib_cfg["vars"]["kappa"][i] = all_images[i].kappa;
            }
            ami_products->set_calibration("goes_nc_abi", calib_cfg);

            for (int i = 0; i < (int)all_images.size(); i++)
            {
                double wavelength_nm = std::stod(all_images[i].channel.substr(2, all_images[i].channel.size() - 1)) * 100;
                ami_products->set_channel_wavenumber(i, 1e7 / wavelength_nm);
                if (all_images[i].kappa > 0)
                    ami_products->set_channel_unit(i, CALIBRATION_ID_ALBEDO);
                else
                    ami_products->set_channel_unit(i, CALIBRATION_ID_EMISSIVE_RADIANCE);
            }

            return ami_products;
        }
    } // namespace firstparty
} // namespace satdump