/*
 * (C) Copyright 2025- ECMWF.
 *
 * This software is licensed under the terms of the Apache Licence Version 2.0
 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
 *
 * In applying this licence, ECMWF does not waive the privileges and immunities
 * granted to it by virtue of its status as an intergovernmental organisation nor
 * does it submit to any jurisdiction.
 */


#include <vector>

#include "eckit/geo/Exceptions.h"
#include "eckit/spec/Custom.h"
#include "eckit/testing/Test.h"
#include "eckit/types/FloatCompare.h"

#include "eccodes/eccodes.h"
#include "eccodes/geo/GribFromSpec.h"

#define CHECK(a) CODES_CHECK(a, nullptr)


bool get_string(const grib_handle* h, const char* key, std::string& value)
{
    char buffer[10240];
    size_t size = sizeof(buffer);
    ASSERT(h);
    int err = codes_get_string(h, key, buffer, &size);

    if (err == CODES_NOT_FOUND) {
        return false;
    }

    ASSERT(err == 0);

    value = buffer;
    return true;
}


void set_string(grib_handle* h, const char* key, const std::string& value)
{
    size_t length = value.length();
    CHECK(codes_set_string(h, key, value.c_str(), &length));
}


CASE("grid: O2")
{
    const ::eckit::spec::Custom spec{ { "grid", "o2" } };

    for (const auto* name : {
             "GRIB1",
             "GRIB2",
         }) {
        auto* sample = codes_grib_handle_new_from_samples(nullptr, name);
        EXPECT(sample != nullptr);

        auto* h = eccodes::geo::GribFromSpec::set(sample, spec);
        EXPECT(h != nullptr);

        long valid = 0;
        set_string(h, "messageValidityChecks", "grid");
        CHECK(codes_get_long(h, "isMessageValid", &valid));
        EXPECT(valid == 1);

        std::string type;
        get_string(h, "gridType", type);
        EXPECT(type == "reduced_gg");

        std::string gridName;
        get_string(h, "gridName", gridName);
        EXPECT(gridName == "O2");

        long N = 0;
        CHECK(codes_get_long(h, "N", &N));
        EXPECT(N == 2);

        long numberOfDataPoints = 0;
        CHECK(codes_get_long(h, "numberOfDataPoints", &numberOfDataPoints));
        EXPECT(numberOfDataPoints == 88);

        size_t pl_size = 0;
        CHECK(codes_get_size(h, "pl", &pl_size));

        std::vector<long> pl(pl_size, 0);
        auto size = pl_size;
        CHECK(codes_get_long_array(h, "pl", pl.data(), &size));
        ASSERT(pl_size == size);

        std::vector<long> pl_expected{ 20L, 24, 24, 20 };
        EXPECT(pl == pl_expected);

        std::vector<double> area(4);
        CHECK(codes_get_double(h, "latitudeOfFirstGridPointInDegrees", &area[0]));
        CHECK(codes_get_double(h, "longitudeOfFirstGridPointInDegrees", &area[1]));
        CHECK(codes_get_double(h, "latitudeOfLastGridPointInDegrees", &area[2]));
        CHECK(codes_get_double(h, "longitudeOfLastGridPointInDegrees", &area[3]));

        EXPECT(eckit::types::is_strictly_greater(90., area[0]));
        EXPECT(eckit::types::is_approximately_equal(area[1], 0.));
        EXPECT(eckit::types::is_strictly_greater(area[2], -90.));
        EXPECT(eckit::types::is_strictly_greater(360., area[3]));

        codes_handle_delete(h);
    }
}


CASE("grid: 1/1")
{
    const ::eckit::spec::Custom spec{ { "grid", "1/1" } };

    for (const auto* name : {
             "GRIB1",
             "GRIB2",
         }) {
        auto* sample = codes_grib_handle_new_from_samples(nullptr, name);
        EXPECT(sample != nullptr);

        auto* h = eccodes::geo::GribFromSpec::set(sample, spec);
        EXPECT(h != nullptr);

        long valid = 0;
        set_string(h, "messageValidityChecks", "grid");
        CHECK(codes_get_long(h, "isMessageValid", &valid));
        EXPECT(valid == 1);

        std::string type;
        get_string(h, "gridType", type);
        EXPECT(type == "regular_ll");

        long Ni                 = 0;
        long Nj                 = 0;
        long numberOfDataPoints = 0;
        CHECK(codes_get_long(h, "Ni", &Ni));
        CHECK(codes_get_long(h, "Nj", &Nj));
        CHECK(codes_get_long(h, "numberOfDataPoints", &numberOfDataPoints));

        EXPECT(Ni * Nj == numberOfDataPoints);

        std::vector<double> area(4);
        CHECK(codes_get_double(h, "latitudeOfFirstGridPointInDegrees", &area[0]));
        CHECK(codes_get_double(h, "longitudeOfFirstGridPointInDegrees", &area[1]));
        CHECK(codes_get_double(h, "latitudeOfLastGridPointInDegrees", &area[2]));
        CHECK(codes_get_double(h, "longitudeOfLastGridPointInDegrees", &area[3]));

        EXPECT(eckit::types::is_approximately_equal(area[0], 90.));
        EXPECT(eckit::types::is_approximately_equal(area[1], 0.));
        EXPECT(eckit::types::is_approximately_equal(area[2], -90.));
        EXPECT(eckit::types::is_approximately_equal(area[3], 360. - 1., 0.5 * 1e-6));

        codes_handle_delete(h);
    }
}


CASE("grid: reduced_ll")
{
    const ::eckit::spec::Custom spec{ { "type", "reduced_ll" }, { "pl", std::vector<long>{ 0, 10, 0 } }, { "area", std::vector<double>{ 90., 0., -90., 360. } } };

    for (const auto* name : {
             "GRIB1",
             "GRIB2",
         }) {
        auto* sample = codes_grib_handle_new_from_samples(nullptr, name);
        EXPECT(sample != nullptr);

        auto* h = eccodes::geo::GribFromSpec::set(sample, spec);
        EXPECT(h != nullptr);

        long valid = 0;
        set_string(h, "messageValidityChecks", "grid");
        CHECK(codes_get_long(h, "isMessageValid", &valid));
        EXPECT(valid == 1);

        std::string type;
        get_string(h, "gridType", type);
        EXPECT(type == "reduced_ll");

        long Nj                 = 3;
        long numberOfDataPoints = 10;
        CHECK(codes_get_long(h, "Nj", &Nj));
        CHECK(codes_get_long(h, "numberOfDataPoints", &numberOfDataPoints));

        std::vector<double> area(4);
        CHECK(codes_get_double(h, "latitudeOfFirstGridPointInDegrees", &area[0]));
        CHECK(codes_get_double(h, "longitudeOfFirstGridPointInDegrees", &area[1]));
        CHECK(codes_get_double(h, "latitudeOfLastGridPointInDegrees", &area[2]));
        CHECK(codes_get_double(h, "longitudeOfLastGridPointInDegrees", &area[3]));

        EXPECT(eckit::types::is_approximately_equal(area[0], 90.));
        EXPECT(eckit::types::is_approximately_equal(area[1], 0.));
        EXPECT(eckit::types::is_approximately_equal(area[2], -90.));
        EXPECT(eckit::types::is_approximately_equal(area[3], 324.));

        codes_handle_delete(h);
    }
}


int main(int argc, char* argv[])
{
    const char* ev_name = "ECCODES_ECKIT_GEO";
    const char* ev_val = getenv(ev_name);
    if (ev_val && atol(ev_val) != 0) {
        return eckit::testing::run_tests(argc, argv);
    }
    printf("%s: This test is disabled (env. variable %s is not set)", argv[0], ev_name);
    return 0;
}
