/*
 * (C) Copyright 2013 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 <algorithm>

#include "eckit/exception/Exceptions.h"
#include "eckit/filesystem/PathName.h"
#include "eckit/io/DataHandle.h"

#include "atlas/array/MakeView.h"
#include "atlas/field/FieldSet.h"
#include "atlas/functionspace/NodeColumns.h"
#include "atlas/functionspace/Spectral.h"
#include "atlas/functionspace/StructuredColumns.h"
#include "atlas/grid.h"
#include "atlas/grid/Distribution.h"
#include "atlas/grid/Partitioner.h"
#include "atlas/grid/detail/partitioner/EqualRegionsPartitioner.h"
#include "atlas/grid/detail/partitioner/TransPartitioner.h"
#include "atlas/mesh/Mesh.h"
#include "atlas/mesh/Nodes.h"
#include "atlas/meshgenerator.h"
#include "atlas/output/Gmsh.h"
#include "atlas/parallel/mpi/mpi.h"
#include "atlas/trans/LegendreCacheCreator.h"
#include "atlas/trans/Trans.h"
#include "atlas/trans/VorDivToUV.h"
#include "atlas/trans/detail/TransFactory.h"

#include "tests/AtlasTestEnvironment.h"

#if ATLAS_HAVE_TRANS
#include "atlas/library/config.h"
#include "atlas/trans/ifs/TransIFS.h"
#include "atlas/trans/ifs/TransIFSNodeColumns.h"
#include "atlas/trans/ifs/TransIFSStructuredColumns.h"
#if ATLAS_HAVE_ECTRANS
#include "ectrans/transi.h"
#else
#include "transi/trans.h"
#endif
#endif

using namespace eckit;
using atlas::grid::detail::partitioner::EqualRegionsPartitioner;
using atlas::grid::detail::partitioner::TransPartitioner;

namespace atlas {
namespace test {

//-----------------------------------------------------------------------------

struct AtlasTransEnvironment : public AtlasTestEnvironment {
    AtlasTransEnvironment(int argc, char* argv[]): AtlasTestEnvironment(argc, argv) {
        if (mpi::comm().size() == 1) {
            trans_use_mpi(false);
        }
        trans_init();
    }

    ~AtlasTransEnvironment() { trans_finalize(); }
};

//-----------------------------------------------------------------------------

void read_rspecg(const trans::TransImpl& trans, std::vector<double>& rspecg, std::vector<int>& nfrom, int& nfld) {
    Log::info() << "read_rspecg ...\n";
    nfld = 2;
    if (mpi::comm().rank() == 0) {
        rspecg.resize(nfld * trans.nb_spectral_coefficients_global());
        for (size_t i = 0; i < trans.nb_spectral_coefficients_global(); ++i) {
            rspecg[i * nfld + 0] = (i == 0 ? 1. : 0.);  // scalar field 1
            rspecg[i * nfld + 1] = (i == 0 ? 2. : 0.);  // scalar field 2
        }
    }
    nfrom.resize(nfld);
    for (int jfld = 0; jfld < nfld; ++jfld) {
        nfrom[jfld] = 1;
    }

    Log::info() << "read_rspecg ... done" << std::endl;
}

//-----------------------------------------------------------------------------

void read_rspecg(Field spec) {
    Log::info() << "read_rspecg ...\n";
    if (mpi::comm().rank() == 0) {
        functionspace::Spectral funcspace = spec.functionspace();
        int nb_spectral_coefficients_global =
            functionspace::Spectral(spec.functionspace()).nb_spectral_coefficients_global();
        auto view = array::make_view<double, 2>(spec);
        ATLAS_ASSERT(view.shape(0) == nb_spectral_coefficients_global);
        ATLAS_ASSERT(view.shape(1) >= 2);
        for (int i = 0; i < nb_spectral_coefficients_global; ++i) {
            view(i, 0) = (i == 0 ? 1. : 0.);  // scalar field 1
            view(i, 1) = (i == 0 ? 2. : 0.);  // scalar field 2
        }
    }
    Log::info() << "read_rspecg ... done" << std::endl;
}

//-----------------------------------------------------------------------------

CASE("test_trans_distribution_matches_atlas") {
    EXPECT(grid::Partitioner::exists("trans"));

    // Create grid and trans object
    Grid g("N80");

    EXPECT(StructuredGrid(g).ny() == 160);

    auto trans_partitioner = new TransPartitioner();
    grid::Partitioner partitioner(trans_partitioner);
    grid::Distribution distribution(g, partitioner);

    trans::TransIFS trans(g, 159);
    ::Trans_t* t = trans;

    ATLAS_DEBUG_VAR(trans.truncation());
    EXPECT(trans.truncation() == 159);

    // -------------- do checks -------------- //
    EXPECT(t->nproc == int(mpi::comm().size()));
    EXPECT(t->myproc == int(mpi::comm().rank() + 1));

    if (mpi::comm().rank() == 0)  // all tasks do the same, so only one needs to check
    {
        int max_nb_regions_EW(0);
        for (int j = 0; j < trans_partitioner->nb_bands(); ++j) {
            max_nb_regions_EW = std::max(max_nb_regions_EW, trans_partitioner->nb_regions(j));
        }

        EXPECT(t->n_regions_NS == trans_partitioner->nb_bands());
        EXPECT(t->n_regions_EW == max_nb_regions_EW);

        EXPECT(distribution.nb_partitions() == idx_t(mpi::comm().size()));
        EXPECT(idx_t(distribution.size()) == g.size());

        std::vector<int> npts(distribution.nb_partitions(), 0);

        for (idx_t j = 0; j < g.size(); ++j) {
            ++npts[distribution.partition(j)];
        }

        EXPECT(t->ngptotg == g.size());
        EXPECT(t->ngptot == npts[mpi::comm().rank()]);
        EXPECT(t->ngptotmx == *std::max_element(npts.begin(), npts.end()));

        // array::LocalView<int,1> n_regions ( trans.n_regions() ) ;
        for (int j = 0; j < trans_partitioner->nb_bands(); ++j) {
            EXPECT(t->n_regions[j] == trans_partitioner->nb_regions(j));
        }
    }
}

CASE("test_trans_options") {
    util::Config opts(option::fft("FFTW") | option::split_latitudes(false) | option::read_legendre("readfile"));
    Log::info() << "trans_opts = " << opts << std::endl;
}

#ifdef TRANS_HAVE_IO
CASE("test_write_read_cache") {
    Log::info() << "test_write_read_cache" << std::endl;
    using namespace trans;
    if (mpi::comm().size() == 1) {
        // Create trans that will write file
        Trans trans_write_F24(Grid("F24"), 23,
                              option::write_legendre("cached_legendre_coeffs-F24") | option::flt(false));
        Trans trans_write_N24(Grid("N24"), 23,
                              option::write_legendre("cached_legendre_coeffs-N24") | option::flt(false));
        Trans trans_write_O24(Grid("O24"), 23,
                              option::write_legendre("cached_legendre_coeffs-O24") | option::flt(false));

        // Create trans that will read from file
        Trans trans_read_F24(Grid("F24"), 23, option::read_legendre("cached_legendre_coeffs-F24") | option::flt(false));
        Trans trans_read_N24(Grid("N24"), 23, option::read_legendre("cached_legendre_coeffs-N24") | option::flt(false));
        Trans trans_read_O24(Grid("O24"), 23, option::read_legendre("cached_legendre_coeffs-O24") | option::flt(false));

        LegendreCache legendre_cache_F24("cached_legendre_coeffs-F24");
        LegendreCache legendre_cache_N24("cached_legendre_coeffs-N24");
        LegendreCache legendre_cache_O24("cached_legendre_coeffs-O24");

        Trans trans_cache_F24(legendre_cache_F24, Grid("F24"), 23, option::flt(false));
        Trans trans_cache_N24(legendre_cache_N24, Grid("N24"), 23, option::flt(false));
        Trans trans_cache_O24(legendre_cache_O24, Grid("O24"), 23, option::flt(false));
    }
}
#endif

CASE("test_distspec") {
    trans::TransIFS trans(Grid("F80"), 159);
    Log::info() << "Trans initialized" << std::endl;
    std::vector<double> rspecg;
    std::vector<int> nfrom;
    int nfld;
    Log::info() << "Read rspecg" << std::endl;
    read_rspecg(trans, rspecg, nfrom, nfld);

    std::vector<double> rspec(nfld * trans.trans()->nspec2);
    std::vector<int> nto(nfld, 1);
    std::vector<double> rgp(nfld * trans.trans()->ngptot);
    std::vector<double> rgpg(nfld * trans.grid().size());
    std::vector<double> specnorms(nfld, 0);

    trans.distspec(nfld, nfrom.data(), rspecg.data(), rspec.data());
    trans.specnorm(nfld, rspec.data(), specnorms.data());
    trans.invtrans(nfld, rspec.data(), rgp.data());
    trans.gathgrid(nfld, nto.data(), rgp.data(), rgpg.data());

    if (mpi::comm().rank() == 0) {
        ATLAS_DEBUG_VAR(specnorms[0]);
        ATLAS_DEBUG_VAR(specnorms[1]);
        EXPECT(eckit::types::is_approximately_equal(specnorms[0], 1., 1.e-10));
        EXPECT(eckit::types::is_approximately_equal(specnorms[1], 2., 1.e-10));
    }

    Log::info() << "end test_distspec" << std::endl;
}

CASE("test_distspec_speconly") {
    functionspace::Spectral fs(159);
    int nfld  = 2;
    Field glb = fs.createField<double>(option::global() | option::levels(nfld));
    read_rspecg(glb);

    std::vector<double> specnorms(nfld, 0);

    Field dist = fs.createField(glb);

    if (not dist.contiguous()) {
        EXPECT_THROWS_AS(fs.scatter(glb, dist), eckit::Exception);
        if (mpi::comm().size() == 1) {
            dist = glb;
            EXPECT_THROWS_AS(fs.norm(dist, specnorms), eckit::Exception);
        }
        return;
    }
    else {
        EXPECT_NO_THROW(fs.scatter(glb, dist));
        EXPECT_NO_THROW(fs.norm(dist, specnorms));
    }


    if (mpi::comm().rank() == 0) {
        ATLAS_DEBUG_VAR(specnorms[0]);
        ATLAS_DEBUG_VAR(specnorms[1]);
        EXPECT(eckit::types::is_approximately_equal(specnorms[0], 1., 1.e-10));
        EXPECT(eckit::types::is_approximately_equal(specnorms[1], 2., 1.e-10));
    }
    Log::info() << "end test_distspec_only" << std::endl;
}

CASE("test_distribution") {
    Grid g("O80");

    Log::info() << "test_distribution" << std::endl;

    grid::Distribution d_trans = grid::Partitioner(new TransPartitioner()).partition(g);
    Log::info() << "trans distribution created" << std::endl;

    grid::Distribution d_eqreg = grid::Partitioner(new EqualRegionsPartitioner()).partition(g);
    Log::info() << "eqregions distribution created" << std::endl;

    if (mpi::comm().rank() == 0) {
        EXPECT(d_trans.nb_partitions() == d_eqreg.nb_partitions());
        EXPECT(d_trans.max_pts() == d_eqreg.max_pts());
        EXPECT(d_trans.min_pts() == d_eqreg.min_pts());

        EXPECT(d_trans.nb_pts() == d_eqreg.nb_pts());
    }
}

CASE("test_generate_mesh") {
    Log::info() << "test_generate_mesh" << std::endl;
    Grid g("O80");
    StructuredMeshGenerator generate(atlas::util::Config("angle", 0)("triangulate", true));

    Mesh m_default = generate(g);

    Log::info() << "trans_distribution" << std::endl;
    grid::Distribution trans_distribution = grid::Partitioner(new TransPartitioner()).partition(g);
    Mesh m_trans                          = generate(g, trans_distribution);

    Log::info() << "eqreg_distribution" << std::endl;
    grid::Distribution eqreg_distribution = grid::Partitioner(new EqualRegionsPartitioner()).partition(g);
    Mesh m_eqreg                          = generate(g, eqreg_distribution);

    array::ArrayView<int, 1> p_default = array::make_view<int, 1>(m_default.nodes().partition());
    array::ArrayView<int, 1> p_trans   = array::make_view<int, 1>(m_trans.nodes().partition());
    array::ArrayView<int, 1> p_eqreg   = array::make_view<int, 1>(m_eqreg.nodes().partition());

    for (idx_t j = 0; j < p_default.shape(0); ++j) {
        EXPECT(p_default(j) == p_trans(j));
        EXPECT(p_default(j) == p_eqreg(j));
    }

    output::Gmsh("N16_trans.msh").write(m_trans);
}

CASE("test_spectral_fields") {
    Log::info() << "test_spectral_fields" << std::endl;

    Grid g("O48");
    StructuredMeshGenerator generate(atlas::util::Config("angle", 0)("triangulate", false));
    Mesh m = generate(g);

    trans::Trans trans(g, 47);

    functionspace::NodeColumns nodal(m);
    functionspace::Spectral spectral(trans);

    Field spf = spectral.createField<double>(option::name("spf"));
    Field gpf = nodal.createField<double>(option::name("gpf"));

    array::make_view<double, 1>(gpf).assign(0);

    EXPECT_NO_THROW(trans.dirtrans(gpf, spf));
    EXPECT_NO_THROW(trans.invtrans(spf, gpf));

    FieldSet gpfields;
    gpfields.add(gpf);
    FieldSet spfields;
    spfields.add(spf);

    EXPECT_NO_THROW(trans.dirtrans(gpfields, spfields));
    EXPECT_NO_THROW(trans.invtrans(spfields, gpfields));

    gpfields.add(gpf);
    EXPECT_THROWS_AS(trans.dirtrans(gpfields, spfields), eckit::Exception);
}

CASE("test_nomesh") {
    Log::info() << "test_spectral_fields" << std::endl;

    Grid g("O48");
    trans::Trans trans(g, 47);

    functionspace::Spectral spectral(trans);
    functionspace::StructuredColumns gridpoints(g, grid::Partitioner("trans"));

    Field spfg = spectral.createField<double>(option::name("spf") | option::global());
    Field spf  = spectral.createField<double>(option::name("spf"));
    Field gpf  = gridpoints.createField<double>(option::name("gpf"));
    Field gpfg = gridpoints.createField<double>(option::name("gpf") | option::global());

    array::ArrayView<double, 1> spg = array::make_view<double, 1>(spfg);

    spectral.parallel_for(option::global(), [&](int real, int imag, int n, int m) {
        spg(real) = +m * spectral.truncation() + n;
        spg(imag) = (n == 0 ? 0 : -m * spectral.truncation() + n);
    });

    if (not spf.contiguous()) {
        EXPECT_THROWS_AS(spectral.scatter(spfg, spf), eckit::Exception);
        if (mpi::comm().size() == 1) {
            spf = spfg;
        }
        else {
            return;
        }
    }
    else {
        EXPECT_NO_THROW(spectral.scatter(spfg, spf));
    }

    array::ArrayView<double, 1> sp = array::make_view<double, 1>(spf);

    spectral.parallel_for([&](idx_t real, idx_t imag, int n, int m) {
        EXPECT(int(sp(real)) == +m * spectral.truncation() + n);
        EXPECT(int(sp(imag)) == (n == 0 ? 0 : -m * spectral.truncation() + n));

        sp(real) = (n == 0 ? 4. : 0.);
        sp(imag) = 0.;
    });

    EXPECT_NO_THROW(trans.invtrans(spf, gpf));

    EXPECT_NO_THROW(gridpoints.gather(gpf, gpfg));

    if (mpi::comm().rank() == 0) {
        array::ArrayView<double, 1> gpg = array::make_view<double, 1>(gpfg);
        for (idx_t jp = 0; jp < gpg.size(); ++jp) {
            EXPECT(is_approximately_equal(gpg(jp), 4., 0.001));
            Log::debug() << "gpg(" << jp << ")   :   " << gpg(jp) << std::endl;
        }
    }

    EXPECT_NO_THROW(gridpoints.scatter(gpfg, gpf));

    EXPECT_NO_THROW(trans.dirtrans(gpf, spf));

    if (not spf.contiguous()) {
        EXPECT_THROWS_AS(spectral.gather(spf, spfg), eckit::Exception);
        if (mpi::comm().size() == 1) {
            spfg = spf;
        }
        else {
            return;
        }
    }
    else {
        EXPECT_NO_THROW(spectral.gather(spf, spfg));
    }

    spectral.parallel_for(option::global(), [&](idx_t real, idx_t imag, int n) {
        EXPECT(is_approximately_equal(spg(real), (n == 0 ? 4. : 0.), 0.001));
        EXPECT(is_approximately_equal(spg(imag), 0., 0.001));
    });
}

CASE("test_trans_factory") {
    Log::info() << "test_trans_factory" << std::endl;

    trans::TransFactory::list(Log::info());
    Log::info() << std::endl;

    functionspace::StructuredColumns gp(Grid("O48"), grid::Partitioner("trans"));
    functionspace::Spectral sp(47);

    trans::Trans trans1 = trans::Trans(gp, sp);
    EXPECT(bool(trans1) == true);

    trans::Trans trans2 = trans::Trans(Grid("O48"), 47);
    EXPECT(bool(trans2) == true);
}

CASE("test_trans_using_grid") {
    Log::info() << "test_trans_using_grid" << std::endl;

    trans::Trans trans(Grid("O48"), 47);

    functionspace::StructuredColumns gp(trans.grid(), grid::Partitioner("trans"));
    functionspace::Spectral sp(trans.truncation());

    Field spf = sp.createField<double>(option::name("spf"));
    Field gpf = gp.createField<double>(option::name("gpf"));

    array::make_view<double, 1>(gpf).assign(0);

    EXPECT_NO_THROW(trans.dirtrans(gpf, spf));
    EXPECT_NO_THROW(trans.invtrans(spf, gpf));

    FieldSet gpfields;
    gpfields.add(gpf);
    FieldSet spfields;
    spfields.add(spf);

    EXPECT_NO_THROW(trans.dirtrans(gpfields, spfields));
    EXPECT_NO_THROW(trans.invtrans(spfields, gpfields));

    gpfields.add(gpf);
    EXPECT_THROWS_AS(trans.dirtrans(gpfields, spfields), eckit::Exception);
}

CASE("test_trans_using_functionspace_NodeColumns") {
    Log::info() << "test_trans_using_functionspace_NodeColumns" << std::endl;

    functionspace::NodeColumns gp(MeshGenerator("structured").generate(Grid("O48")));
    functionspace::Spectral sp(47);

    trans::Trans trans(gp, sp);

    Field spf = sp.createField<double>(option::name("spf"));
    Field gpf = gp.createField<double>(option::name("gpf"));

    array::make_view<double, 1>(gpf).assign(0);

    EXPECT_NO_THROW(trans.dirtrans(gpf, spf));
    EXPECT_NO_THROW(trans.invtrans(spf, gpf));

    FieldSet gpfields;
    gpfields.add(gpf);
    FieldSet spfields;
    spfields.add(spf);

    EXPECT_NO_THROW(trans.dirtrans(gpfields, spfields));
    EXPECT_NO_THROW(trans.invtrans(spfields, gpfields));

    gpfields.add(gpf);
    EXPECT_THROWS_AS(trans.dirtrans(gpfields, spfields), eckit::Exception);
}

CASE("test_trans_using_functionspace_StructuredColumns") {
    Log::info() << "test_trans_using_functionspace_StructuredColumns" << std::endl;

    functionspace::StructuredColumns gp(Grid("O48"), grid::Partitioner("trans"));
    functionspace::Spectral sp(47);

    trans::Trans trans(gp, sp);

    Field spf = sp.createField<double>(option::name("spf"));
    Field gpf = gp.createField<double>(option::name("gpf"));

    array::make_view<double, 1>(gpf).assign(0);

    EXPECT_NO_THROW(trans.dirtrans(gpf, spf));
    EXPECT_NO_THROW(trans.invtrans(spf, gpf));

    FieldSet gpfields;
    gpfields.add(gpf);
    FieldSet spfields;
    spfields.add(spf);

    EXPECT_NO_THROW(trans.dirtrans(gpfields, spfields));
    EXPECT_NO_THROW(trans.invtrans(spfields, gpfields));

    gpfields.add(gpf);
    EXPECT_THROWS_AS(trans.dirtrans(gpfields, spfields), eckit::Exception);
}

CASE("test_trans_MIR_lonlat") {
    Log::info() << "test_trans_MIR_lonlat" << std::endl;

    Grid grid("L48");
    trans::Trans trans(grid, 47);

    // global fields
    std::vector<double> spf(trans.spectralCoefficients(), 0.);
    std::vector<double> gpf(grid.size());

    if (mpi::comm().size() == 1) {
        EXPECT_NO_THROW(trans.invtrans(1, spf.data(), gpf.data(), option::global()));

        EXPECT_NO_THROW(trans.dirtrans(1, gpf.data(), spf.data(), option::global()));
    }
}

CASE("test_trans_VorDivToUV") {
    int nfld = 1;                          // TODO: test for nfld>1
    std::vector<int> truncation_array{1};  // truncation_array{159,160,1279};
    for (size_t i = 0; i < truncation_array.size(); ++i) {
        int truncation = truncation_array[i];
        int nspec2     = (truncation + 1) * (truncation + 2);

        Log::info() << "truncation = " << truncation << std::endl;

        std::vector<double> field_vor(nfld * nspec2, 0.);
        std::vector<double> field_div(nfld * nspec2, 0.);

        // TODO: initialise field_vor and field_div with something meaningful
        field_vor[2 * nfld] = 1.;
        Log::info() << "vor: " << std::endl;
        for (int j = 0; j < nfld * nspec2; j++) {
            Log::info() << field_vor[j] << " ";
        }
        Log::info() << std::endl;

        // With IFS
        if (trans::VorDivToUVFactory::has("ifs")) {
            trans::VorDivToUV vordiv_to_UV(truncation, option::type("ifs"));
            EXPECT(vordiv_to_UV.truncation() == truncation);

            std::vector<double> field_U(nfld * nspec2);
            std::vector<double> field_V(nfld * nspec2);

            vordiv_to_UV.execute(nspec2, nfld, field_vor.data(), field_div.data(), field_U.data(), field_V.data());

            // TODO: do some meaningful checks
            Log::info() << "Trans library" << std::endl;
            Log::info() << "U: " << std::endl;
            for (int j = 0; j < nfld * nspec2; j++) {
                Log::info() << field_U[j] << " ";
            }
            Log::info() << std::endl;
        }

        // With Local
        {
            trans::VorDivToUV vordiv_to_UV(truncation, option::type("local"));
            EXPECT(vordiv_to_UV.truncation() == truncation);

            std::vector<double> field_U(nfld * nspec2);
            std::vector<double> field_V(nfld * nspec2);

            vordiv_to_UV.execute(nspec2, nfld, field_vor.data(), field_div.data(), field_U.data(), field_V.data());

            // TODO: do some meaningful checks
            Log::info() << "Local transform" << std::endl;
            Log::info() << "U: " << std::endl;
            for (int j = 0; j < nfld * nspec2; j++) {
                Log::info() << field_U[j] << " ";
            }
            Log::info() << std::endl;
        }
    }
}

#ifdef TRANS_HAVE_IO
CASE("ATLAS-256: Legendre coefficient expected unique identifiers") {
    if (mpi::comm().size() == 1) {
        util::Config options;
        options.set(option::type("ifs"));
        options.set("flt", false);

        auto uids = {
            // clang-format off
            "ifs-T20-RegularGaussianN320-OPT4189816c2e",
            "ifs-T20-RegularGaussianN640-OPT4189816c2e",
            "ifs-T20-RegularGaussianN1280-OPT4189816c2e",
            "ifs-T20-ReducedGaussianN320-PL52955330f8-OPT4189816c2e",
            "ifs-T20-ReducedGaussianN640-PL296ba3f6fb-OPT4189816c2e",
            "ifs-T20-ReducedGaussianN1280-PL9e14f63837-OPT4189816c2e",
            "ifs-T20-ReducedGaussianN320-PL440b952c43-OPT4189816c2e",
            "ifs-T20-ReducedGaussianN640-PLcbba2659c1-OPT4189816c2e",
            "ifs-T20-ReducedGaussianN1280-PL8eadc35e89-OPT4189816c2e",
            "ifs-T20-L360x181-OPT4189816c2e",
            "ifs-T20-L3600x1801-OPT4189816c2e",
            "ifs-T639-RegularGaussianN320-OPT4189816c2e",
            "ifs-T639-RegularGaussianN640-OPT4189816c2e",
            "ifs-T639-RegularGaussianN1280-OPT4189816c2e",
            "ifs-T639-ReducedGaussianN320-PL52955330f8-OPT4189816c2e",
            "ifs-T639-ReducedGaussianN640-PL296ba3f6fb-OPT4189816c2e",
            "ifs-T639-ReducedGaussianN1280-PL9e14f63837-OPT4189816c2e",
            "ifs-T639-ReducedGaussianN320-PL440b952c43-OPT4189816c2e",
            "ifs-T639-ReducedGaussianN640-PLcbba2659c1-OPT4189816c2e",
            "ifs-T639-ReducedGaussianN1280-PL8eadc35e89-OPT4189816c2e",
            "ifs-T639-L360x181-OPT4189816c2e",
            "ifs-T639-L3600x1801-OPT4189816c2e",
            "ifs-T1279-RegularGaussianN320-OPT4189816c2e",
            "ifs-T1279-RegularGaussianN640-OPT4189816c2e",
            "ifs-T1279-RegularGaussianN1280-OPT4189816c2e",
            "ifs-T1279-ReducedGaussianN320-PL52955330f8-OPT4189816c2e",
            "ifs-T1279-ReducedGaussianN640-PL296ba3f6fb-OPT4189816c2e",
            "ifs-T1279-ReducedGaussianN1280-PL9e14f63837-OPT4189816c2e",
            "ifs-T1279-ReducedGaussianN320-PL440b952c43-OPT4189816c2e",
            "ifs-T1279-ReducedGaussianN640-PLcbba2659c1-OPT4189816c2e",
            "ifs-T1279-ReducedGaussianN1280-PL8eadc35e89-OPT4189816c2e",
            "ifs-T1279-L360x181-OPT4189816c2e",
            "ifs-T1279-L3600x1801-OPT4189816c2e",
            "ifs-T20-grid-800ac12540-OPT4189816c2e",
            "ifs-T20-grid-0915e0f040-OPT4189816c2e",
            "ifs-T20-grid-7c400822f0-OPT4189816c2e",
            "ifs-T20-grid-800ac12540-OPT4189816c2e",
            "ifs-T20-grid-0915e0f040-OPT4189816c2e",
            "ifs-T20-grid-7c400822f0-OPT4189816c2e",
            "ifs-T20-grid-800ac12540-OPT4189816c2e",
            "ifs-T20-grid-0915e0f040-OPT4189816c2e",
            "ifs-T20-grid-7c400822f0-OPT4189816c2e",
            "ifs-T20-grid-7824deccdf-OPT4189816c2e",
            "ifs-T20-grid-7d1771559e-OPT4189816c2e",
            "ifs-T639-grid-800ac12540-OPT4189816c2e",
            "ifs-T639-grid-0915e0f040-OPT4189816c2e",
            "ifs-T639-grid-7c400822f0-OPT4189816c2e",
            "ifs-T639-grid-800ac12540-OPT4189816c2e",
            "ifs-T639-grid-0915e0f040-OPT4189816c2e",
            "ifs-T639-grid-7c400822f0-OPT4189816c2e",
            "ifs-T639-grid-800ac12540-OPT4189816c2e",
            "ifs-T639-grid-0915e0f040-OPT4189816c2e",
            "ifs-T639-grid-7c400822f0-OPT4189816c2e",
            "ifs-T639-grid-7824deccdf-OPT4189816c2e",
            "ifs-T639-grid-7d1771559e-OPT4189816c2e",
            "ifs-T1279-grid-800ac12540-OPT4189816c2e",
            "ifs-T1279-grid-0915e0f040-OPT4189816c2e",
            "ifs-T1279-grid-7c400822f0-OPT4189816c2e",
            "ifs-T1279-grid-800ac12540-OPT4189816c2e",
            "ifs-T1279-grid-0915e0f040-OPT4189816c2e",
            "ifs-T1279-grid-7c400822f0-OPT4189816c2e",
            "ifs-T1279-grid-800ac12540-OPT4189816c2e",
            "ifs-T1279-grid-0915e0f040-OPT4189816c2e",
            "ifs-T1279-grid-7c400822f0-OPT4189816c2e",
            "ifs-T1279-grid-7824deccdf-OPT4189816c2e",
            "ifs-T1279-grid-7d1771559e-OPT4189816c2e",
            // clang-format on
        };
        auto uid = uids.begin();

        auto domains    = std::vector<Domain>{GlobalDomain(), RectangularDomain({-10, 10}, {-20, 20})};
        auto spectral_T = std::vector<int>{20, 639, 1279};
        auto grids      = std::vector<std::string>{"F320", "F640", "F1280", "N320", "N640", "N1280",
                                              "O320", "O640", "O1280", "L90",  "L900"};
        for (auto& domain : domains) {
            for (int T : spectral_T) {
                for (auto name : grids) {
                    Log::info() << "Case name:" << name << ", T:" << T << ", domain:" << domain << ", UID:'" << *uid
                                << "'" << std::endl;

                    Grid grid(name, domain);
                    auto test = trans::LegendreCacheCreator(grid, T, options).uid();
                    ATLAS_DEBUG_VAR(test);
                    EXPECT(test == *uid);

                    uid++;
                }
            }
        }
    }
}
#endif

//-----------------------------------------------------------------------------

}  // namespace test
}  // namespace atlas

int main(int argc, char** argv) {
    return atlas::test::run<atlas::test::AtlasTransEnvironment>(argc, argv);
}
