/*
 * (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 "eckit/types/Types.h"

#include "atlas/array/ArrayView.h"
#include "atlas/array/MakeView.h"
#include "atlas/field/Field.h"
#include "atlas/field/FieldSet.h"
#include "atlas/functionspace/NodeColumns.h"
#include "atlas/functionspace/Spectral.h"
#include "atlas/grid/Grid.h"
#include "atlas/grid/StructuredGrid.h"
#include "atlas/mesh/Mesh.h"
#include "atlas/mesh/Nodes.h"
#include "atlas/meshgenerator.h"
#include "atlas/parallel/mpi/mpi.h"
#include "atlas/trans/Trans.h"

#include "tests/AtlasTestEnvironment.h"

using namespace eckit;
using namespace atlas::functionspace;
using namespace atlas::util;

namespace atlas {
namespace test {

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

CASE("test_functionspace_NodeColumns_no_halo") {
    Grid grid("O8");
    Mesh mesh = StructuredMeshGenerator().generate(grid);
    functionspace::NodeColumns nodes_fs(mesh);
    Field field(nodes_fs.createField<int>());
    array::ArrayView<int, 1> value = array::make_view<int, 1>(field);
    array::ArrayView<int, 1> ghost = array::make_view<int, 1>(mesh.nodes().ghost());
    const size_t nb_nodes          = mesh.nodes().size();
    for (size_t j = 0; j < nb_nodes; ++j) {
        if (ghost(j)) {
            value(j) = -1;
        }
        else {
            value(j) = 1;
        }
    }
    nodes_fs.haloExchange(field);
    for (size_t j = 0; j < nb_nodes; ++j) {
        EXPECT(value(j) == 1);
    }
}

CASE("test_functionspace_NodeColumns") {
    ReducedGaussianGrid grid({4, 8, 8, 4});

    StructuredMeshGenerator generator;
    // generator.options.set("3d",true);
    Mesh mesh = generator.generate(grid);

    // grid.reset();

    idx_t nb_levels = 10;

    functionspace::NodeColumns nodes_fs(mesh, option::halo(1) | option::levels(nb_levels));
    // NodesColumnFunctionSpace columns_fs("columns",mesh,nb_levels,Halo(1));

    // EXPECT( nodes_fs.nb_nodes() == columns_fs.nb_nodes() );
    // EXPECT( columns_fs.nb_levels() == 10 );

    Field surface_scalar_field = nodes_fs.createField<double>(option::name("scalar") | option::levels(false));
    Field surface_vector_field =
        nodes_fs.createField<double>(option::name("vector") | option::levels(false) | option::variables(2));
    Field surface_tensor_field =
        nodes_fs.createField<double>(option::name("tensor") | option::levels(false) | option::variables(2 * 2));

    EXPECT(surface_scalar_field.name() == std::string("scalar"));
    EXPECT(surface_vector_field.name() == std::string("vector"));
    EXPECT(surface_tensor_field.name() == std::string("tensor"));

    EXPECT(surface_scalar_field.size() == nodes_fs.nb_nodes());
    EXPECT(surface_vector_field.size() == nodes_fs.nb_nodes() * 2);
    EXPECT(surface_tensor_field.size() == nodes_fs.nb_nodes() * 2 * 2);

    EXPECT(surface_scalar_field.rank() == 1);
    EXPECT(surface_vector_field.rank() == 2);
    EXPECT(surface_tensor_field.rank() == 2);

    EXPECT(surface_scalar_field.levels() == 0);
    EXPECT(surface_vector_field.levels() == 0);
    EXPECT(surface_tensor_field.levels() == 0);

    array::ArrayView<double, 1> surface_scalar = array::make_view<double, 1>(surface_scalar_field);
    array::ArrayView<double, 2> surface_vector = array::make_view<double, 2>(surface_vector_field);
    array::ArrayView<double, 2> surface_tensor = array::make_view<double, 2>(surface_tensor_field);

    EXPECT(surface_scalar.shape(0) == nodes_fs.nb_nodes());
    EXPECT(surface_vector.shape(0) == nodes_fs.nb_nodes());
    EXPECT(surface_tensor.shape(0) == nodes_fs.nb_nodes());
    EXPECT(surface_vector.shape(1) == 2);
    EXPECT(surface_tensor.shape(1) == 2 * 2);

    Field columns_scalar_field = nodes_fs.createField<double>(option::name("scalar"));
    Field columns_vector_field = nodes_fs.createField<double>(option::name("vector") | option::variables(2));
    Field columns_tensor_field = nodes_fs.createField<double>(option::name("tensor") | option::variables(2 * 2));

    EXPECT(columns_scalar_field.name() == std::string("scalar"));
    EXPECT(columns_vector_field.name() == std::string("vector"));
    EXPECT(columns_tensor_field.name() == std::string("tensor"));

    EXPECT(columns_scalar_field.size() == nodes_fs.nb_nodes() * nb_levels);
    EXPECT(columns_vector_field.size() == nodes_fs.nb_nodes() * nb_levels * 2);
    EXPECT(columns_tensor_field.size() == nodes_fs.nb_nodes() * nb_levels * 2 * 2);

    EXPECT(columns_scalar_field.rank() == 2);
    EXPECT(columns_vector_field.rank() == 3);
    EXPECT(columns_tensor_field.rank() == 3);

    EXPECT(columns_scalar_field.levels() == nb_levels);
    EXPECT(columns_vector_field.levels() == nb_levels);
    EXPECT(columns_tensor_field.levels() == nb_levels);

    array::ArrayView<double, 2> columns_scalar = array::make_view<double, 2>(columns_scalar_field);
    array::ArrayView<double, 3> columns_vector = array::make_view<double, 3>(columns_vector_field);
    array::ArrayView<double, 3> columns_tensor = array::make_view<double, 3>(columns_tensor_field);

    EXPECT(columns_scalar.shape(0) == nodes_fs.nb_nodes());
    EXPECT(columns_vector.shape(0) == nodes_fs.nb_nodes());
    EXPECT(columns_tensor.shape(0) == nodes_fs.nb_nodes());
    EXPECT(columns_scalar.shape(1) == nb_levels);
    EXPECT(columns_vector.shape(1) == nb_levels);
    EXPECT(columns_tensor.shape(1) == nb_levels);
    EXPECT(columns_vector.shape(2) == 2);
    EXPECT(columns_tensor.shape(2) == 2 * 2);

    Field field                  = nodes_fs.createField<int>(option::name("partition"));
    array::ArrayView<int, 2> arr = array::make_view<int, 2>(field);
    arr.assign(int(mpi::comm().rank()));
    // field->dump( Log::info() );
    nodes_fs.haloExchange(field);
    // field->dump( Log::info() );

    Field field2 = nodes_fs.createField<int>(option::name("partition2") | option::variables(2));
    Log::info() << "field2.rank() = " << field2.rank() << std::endl;
    array::ArrayView<int, 3> arr2 = array::make_view<int, 3>(field2);
    arr2.assign(int(mpi::comm().rank()));

    // field2->dump( Log::info() );
    nodes_fs.haloExchange(field2);
    // field2->dump( Log::info() );

    Log::info() << nodes_fs.checksum(field) << std::endl;

    size_t root     = mpi::comm().size() - 1;
    Field glb_field = nodes_fs.createField(option::name("partition") | option::datatype(field.datatype()) |
                                           option::levels(field.levels()) | option::variables(field.variables()) |
                                           option::global(root));
    nodes_fs.gather(field, glb_field);

    EXPECT(glb_field.rank() == field.rank());
    EXPECT(glb_field.levels() == nb_levels);
    EXPECT(field.levels() == nb_levels);

    Log::info() << "field = " << field << std::endl;
    Log::info() << "global_field = " << glb_field << std::endl;

    Log::info() << "local points = " << nodes_fs.nb_nodes() << std::endl;
    Log::info() << "grid points = " << grid.size() << std::endl;
    Log::info() << "glb_field.shape(0) = " << glb_field.shape(0) << std::endl;

    EXPECT(glb_field.metadata().get<bool>("global") == true);
    EXPECT(glb_field.metadata().get<int>("owner") == int(root));

    // glb_field->dump( Log::info() );

    if (mpi::comm().rank() == root) {
        glb_field.metadata().set("test_broadcast", 123);
    }

    arr.assign(-1);
    nodes_fs.scatter(glb_field, field);
    EXPECT(field.metadata().get<int>("test_broadcast") == 123);
    nodes_fs.haloExchange(field);
    // field->dump( Log::info() );

    Log::info() << nodes_fs.checksum(field) << std::endl;

    FieldSet fields;
    fields.add(field);
    fields.add(field2);
    Log::info() << nodes_fs.checksum(fields) << std::endl;

    Log::info() << "Testing collectives for nodes scalar field" << std::endl;
    {
        Field field                         = surface_scalar_field;
        const functionspace::NodeColumns fs = nodes_fs;

        double max;
        double min;
        double sum;
        double mean;
        double stddev;
        idx_t N;
        gidx_t gidx_max;
        gidx_t gidx_min;

        auto sfc_arr = array::make_view<double, 1>(field);
        sfc_arr.assign(mpi::comm().rank() + 1);
        fs.maximum(surface_scalar_field, max);
        EXPECT(max == double(mpi::comm().size()));

        fs.minimum(surface_scalar_field, min);
        EXPECT(min == 1);

        fs.maximumAndLocation(field, max, gidx_max);
        EXPECT(max == double(mpi::comm().size()));
        Log::info() << "global index for maximum: " << gidx_max << std::endl;

        fs.minimumAndLocation(field, min, gidx_min);
        EXPECT(min == 1);
        Log::info() << "global index for minimum: " << gidx_min << std::endl;

        fs.orderIndependentSum(field, sum, N);
        Log::info() << "oisum: " << sum << std::endl;
        Log::info() << "oiN: " << N << std::endl;

        fs.sum(field, sum, N);
        Log::info() << "sum: " << sum << std::endl;
        Log::info() << "N: " << N << std::endl;

        fs.mean(field, mean, N);
        Log::info() << "mean: " << mean << std::endl;
        Log::info() << "N: " << N << std::endl;

        fs.meanAndStandardDeviation(field, mean, stddev, N);
        Log::info() << "mean: " << mean << std::endl;
        Log::info() << "standard deviation: " << stddev << std::endl;
        Log::info() << "N: " << N << std::endl;

        int sumint;
        fs.orderIndependentSum(field, sumint, N);
        Log::info() << "sumint: " << sumint << std::endl;

        fs.sum(field, sumint, N);
        Log::info() << "sumint: " << sumint << std::endl;
    }

    Log::info() << "Testing collectives for nodes vector field" << std::endl;
    {
        Field& field                        = surface_vector_field;
        const functionspace::NodeColumns fs = nodes_fs;

        std::vector<double> max;
        std::vector<double> min;
        std::vector<double> sum;
        std::vector<double> mean;
        std::vector<double> stddev;
        idx_t N;
        std::vector<gidx_t> gidx_max;
        std::vector<gidx_t> gidx_min;

        auto vec_arr = array::make_view<double, 2>(field);
        vec_arr.assign(mpi::comm().rank() + 1);
        fs.maximum(field, max);
        std::vector<double> check_max(field.variables(), mpi::comm().size());
        EXPECT(max == check_max);

        fs.minimum(field, min);
        std::vector<double> check_min(field.variables(), 1);
        EXPECT(min == check_min);

        fs.maximumAndLocation(field, max, gidx_max);
        EXPECT(max == check_max);
        Log::info() << "global index for maximum: " << gidx_max << std::endl;

        fs.minimumAndLocation(field, min, gidx_min);
        EXPECT(min == check_min);
        Log::info() << "global index for minimum: " << gidx_min << std::endl;

        fs.orderIndependentSum(field, sum, N);
        Log::info() << "oisum: " << sum << std::endl;
        Log::info() << "oiN: " << N << std::endl;

        fs.mean(field, mean, N);
        Log::info() << "mean: " << mean << std::endl;
        Log::info() << "N: " << N << std::endl;

        fs.meanAndStandardDeviation(field, mean, stddev, N);
        Log::info() << "mean: " << mean << std::endl;
        Log::info() << "standard deviation: " << stddev << std::endl;
        Log::info() << "N: " << N << std::endl;

        std::vector<int> sumint;
        fs.orderIndependentSum(field, sumint, N);
        Log::info() << "sumint: " << sumint << std::endl;

        fs.sum(field, sumint, N);
        Log::info() << "sumint: " << sumint << std::endl;
    }

    Log::info() << "Testing collectives for columns scalar field" << std::endl;
    if (1) {
        Field& field                        = columns_scalar_field;
        const functionspace::NodeColumns fs = nodes_fs;
        double max;
        double min;
        double sum;
        double mean;
        double stddev;
        idx_t N;
        gidx_t gidx_max;
        gidx_t gidx_min;
        idx_t level;

        EXPECT(field.levels() == nb_levels);

        auto arr = array::make_view<double, 2>(field);
        arr.assign(mpi::comm().rank() + 1);
        fs.maximum(field, max);
        EXPECT(max == double(mpi::comm().size()));

        fs.minimum(field, min);
        EXPECT(min == 1);

        fs.maximumAndLocation(field, max, gidx_max, level);
        EXPECT(max == double(mpi::comm().size()));
        Log::info() << "global index for maximum: " << gidx_max << std::endl;
        Log::info() << "level for maximum: " << level << std::endl;

        fs.minimumAndLocation(field, min, gidx_min, level);
        EXPECT(min == 1);
        Log::info() << "global index for minimum: " << gidx_min << std::endl;
        Log::info() << "level for minimum: " << level << std::endl;

        fs.orderIndependentSum(field, sum, N);
        Log::info() << "order independent sum: " << sum << std::endl;
        Log::info() << "N: " << N << std::endl;

        fs.sum(field, sum, N);
        Log::info() << "sum: " << sum << std::endl;
        Log::info() << "N: " << N << std::endl;

        fs.mean(field, mean, N);
        Log::info() << "mean: " << mean << std::endl;
        Log::info() << "N: " << N << std::endl;

        fs.meanAndStandardDeviation(field, mean, stddev, N);
        Log::info() << "mean: " << mean << std::endl;
        Log::info() << "standard deviation: " << stddev << std::endl;
        Log::info() << "N: " << N << std::endl;

        int sumint;
        fs.orderIndependentSum(field, sumint, N);
        Log::info() << "order independent sum in int: " << sumint << std::endl;

        fs.sum(field, sumint, N);
        Log::info() << "sum in int: " << sumint << std::endl;

        Field max_per_level("max", array::make_datatype<double>(), array::make_shape(nb_levels));
        Field min_per_level("min", array::make_datatype<double>(), array::make_shape(nb_levels));
        Field sum_per_level("sum", array::make_datatype<double>(), array::make_shape(nb_levels));
        Field mean_per_level("mean", array::make_datatype<double>(), array::make_shape(nb_levels));
        Field stddev_per_level("stddev", array::make_datatype<double>(), array::make_shape(nb_levels));
        Field gidx_per_level("gidx", array::make_datatype<gidx_t>(), array::make_shape(nb_levels));

        fs.maximumPerLevel(field, max_per_level);
        // max_per_level.dump(Log::info());
        fs.minimumPerLevel(field, min_per_level);
        // min_per_level.dump(Log::info());
        fs.sumPerLevel(field, sum_per_level, N);
        // sum_per_level.dump(Log::info());
        fs.meanPerLevel(field, mean_per_level, N);
        // mean_per_level.dump(Log::info());
        fs.meanAndStandardDeviationPerLevel(field, mean_per_level, stddev_per_level, N);
        // mean_per_level.dump(Log::info());
        // stddev_per_level.dump(Log::info());
        fs.orderIndependentSumPerLevel(field, sum_per_level, N);
        // sum_per_level.dump(Log::info());
    }

    Log::info() << "Testing collectives for columns vector field" << std::endl;
    if (1) {
        Field& field                        = columns_vector_field;
        const functionspace::NodeColumns fs = nodes_fs;
        idx_t nvar                          = field.variables();
        std::vector<double> max;
        std::vector<double> min;
        std::vector<double> sum;
        std::vector<double> mean;
        std::vector<double> stddev;
        idx_t N;
        std::vector<gidx_t> gidx_max;
        std::vector<gidx_t> gidx_min;
        std::vector<idx_t> levels;

        auto vec_arr = array::make_view<double, 3>(field);
        vec_arr.assign(mpi::comm().rank() + 1);
        fs.maximum(field, max);
        std::vector<double> check_max(nvar, mpi::comm().size());
        EXPECT(max == check_max);

        fs.minimum(field, min);
        std::vector<double> check_min(nvar, 1);
        EXPECT(min == check_min);

        fs.maximumAndLocation(field, max, gidx_max, levels);
        EXPECT(max == check_max);
        Log::info() << "global index for maximum: " << gidx_max << std::endl;

        fs.minimumAndLocation(field, min, gidx_min, levels);
        EXPECT(min == check_min);
        Log::info() << "global index for minimum: " << gidx_min << std::endl;

        fs.orderIndependentSum(field, sum, N);
        Log::info() << "oisum: " << sum << std::endl;
        Log::info() << "N: " << N << std::endl;

        fs.mean(field, mean, N);
        Log::info() << "mean: " << mean << std::endl;
        Log::info() << "N: " << N << std::endl;

        fs.meanAndStandardDeviation(field, mean, stddev, N);
        Log::info() << "mean: " << mean << std::endl;
        Log::info() << "standard deviation: " << stddev << std::endl;
        Log::info() << "N: " << N << std::endl;

        std::vector<int> sumint;
        fs.orderIndependentSum(field, sumint, N);
        Log::info() << "sumint: " << sumint << std::endl;

        fs.sum(field, sumint, N);
        Log::info() << "sumint: " << sumint << std::endl;

        Field max_per_level("max", array::make_datatype<double>(), array::make_shape(nb_levels, nvar));
        Field min_per_level("min", array::make_datatype<double>(), array::make_shape(nb_levels, nvar));
        Field sum_per_level("sum", array::make_datatype<double>(), array::make_shape(nb_levels, nvar));
        Field mean_per_level("mean", array::make_datatype<double>(), array::make_shape(nb_levels, nvar));
        Field stddev_per_level("stddev", array::make_datatype<double>(), array::make_shape(nb_levels, nvar));
        Field gidx_per_level("gidx", array::make_datatype<gidx_t>(), array::make_shape(nb_levels, nvar));

        fs.maximumPerLevel(field, max_per_level);
        // max_per_level.dump(Log::info());

        fs.minimumPerLevel(field, min_per_level);
        // min_per_level.dump(Log::info());

        fs.sumPerLevel(field, sum_per_level, N);
        // sum_per_level.dump(Log::info());

        fs.meanPerLevel(field, mean_per_level, N);
        // mean_per_level.dump(Log::info());

        fs.meanAndStandardDeviationPerLevel(field, mean_per_level, stddev_per_level, N);
        // mean_per_level.dump(Log::info());
        // stddev_per_level.dump(Log::info());

        fs.orderIndependentSumPerLevel(field, sum_per_level, N);
        // sum_per_level.dump(Log::info());
    }

    Field tmp = nodes_fs.createField(option::datatypeT<double>() | option::global(0) | option::levels(10) |
                                     option::name("tmp"));
}

CASE("test_SpectralFunctionSpace") {
    idx_t truncation = 159;
    idx_t nb_levels  = 10;
    idx_t nspec2g    = (truncation + 1) * (truncation + 2);

    Spectral spectral_fs(truncation);

    Field surface_scalar_field = spectral_fs.createField<double>(option::name("scalar"));

    EXPECT(surface_scalar_field.name() == std::string("scalar"));

    EXPECT(surface_scalar_field.size() == nspec2g);

    EXPECT(surface_scalar_field.rank() == 1);

    auto surface_scalar = array::make_view<double, 1>(surface_scalar_field);

    EXPECT(surface_scalar.shape(0) == nspec2g);

    Field columns_scalar_field = spectral_fs.createField<double>(option::name("scalar") | option::levels(nb_levels));

    EXPECT(columns_scalar_field.name() == std::string("scalar"));

    EXPECT(columns_scalar_field.size() == nspec2g * nb_levels);

    EXPECT(columns_scalar_field.rank() == 2);

    auto columns_scalar = array::make_view<double, 2>(columns_scalar_field);

    EXPECT(columns_scalar.shape(0) == nspec2g);
    EXPECT(columns_scalar.shape(1) == nb_levels);
}

#if ATLAS_HAVE_TRANS

CASE("test_SpectralFunctionSpace_trans_dist") {
    trans::Trans trans(Grid("F80"), 159);
    idx_t nb_levels(10);

    Spectral spectral_fs(trans);
    idx_t nspec2 = spectral_fs.nb_spectral_coefficients();

    Field surface_scalar_field = spectral_fs.createField<double>(option::name("scalar"));

    EXPECT(surface_scalar_field.name() == std::string("scalar"));

    EXPECT(surface_scalar_field.size() == nspec2);

    EXPECT(surface_scalar_field.rank() == 1);

    auto surface_scalar = array::make_view<double, 1>(surface_scalar_field);

    EXPECT(surface_scalar.shape(0) == nspec2);
    // size_t surface_scalar_shape[] = { nspec2 };
    // EXPECT( eckit::testing::make_view(
    // surface_scalar.shape(),surface_scalar.shape()+1) ==
    // eckit::testing::make_view(surface_scalar_shape,surface_scalar_shape+1) );

    Field columns_scalar_field = spectral_fs.createField<double>(option::name("scalar") | option::levels(nb_levels));

    EXPECT(columns_scalar_field.name() == std::string("scalar"));

    EXPECT(columns_scalar_field.size() == nspec2 * nb_levels);

    EXPECT(columns_scalar_field.rank() == 2);

    auto columns_scalar = array::make_view<double, 2>(columns_scalar_field);

    EXPECT(columns_scalar.shape(0) == nspec2);
    EXPECT(columns_scalar.shape(1) == nb_levels);
    // size_t columns_scalar_shape[] = { nspec2, nb_levels };
    // EXPECT(eckit::testing::make_view(columns_scalar.shape(),columns_scalar.shape()+2)
    // == eckit::testing::make_view(columns_scalar_shape,columns_scalar_shape+2));
}
CASE("test_SpectralFunctionSpace_trans_global") {
    idx_t nb_levels(10);
    idx_t truncation = 159;

    Spectral spectral_fs(truncation, option::levels(nb_levels));
    idx_t nspec2g = spectral_fs.nb_spectral_coefficients_global();

    Field surface_scalar_field =
        spectral_fs.createField<double>(option::name("scalar") | option::levels(false) | option::global());

    EXPECT(surface_scalar_field.name() == std::string("scalar"));

    if (eckit::mpi::comm().rank() == 0) {
        EXPECT(surface_scalar_field.size() == nspec2g);
    }

    EXPECT(surface_scalar_field.rank() == 1);

    EXPECT(surface_scalar_field.metadata().get<bool>("global"));

    EXPECT(surface_scalar_field.metadata().get<size_t>("owner") == 0);

    auto surface_scalar = array::make_view<double, 1>(surface_scalar_field);

    if (eckit::mpi::comm().rank() == 0) {
        EXPECT(surface_scalar.shape(0) == nspec2g);
    }
    Field columns_scalar_field = spectral_fs.createField<double>(option::name("scalar") | option::global());

    EXPECT(columns_scalar_field.name() == std::string("scalar"));

    if (eckit::mpi::comm().rank() == 0) {
        EXPECT(columns_scalar_field.size() == nspec2g * nb_levels);
    }
    else {
        EXPECT(columns_scalar_field.size() == 0);
    }

    EXPECT(columns_scalar_field.rank() == 2);

    auto columns_scalar = array::make_view<double, 2>(columns_scalar_field);

    if (eckit::mpi::comm().rank() == 0) {
        EXPECT(columns_scalar.shape(0) == nspec2g);
        EXPECT(columns_scalar.shape(1) == nb_levels);
    }
}
CASE("test_SpectralFunctionSpace_norm") {
    trans::Trans trans(Grid("F80"), 159);
    size_t nb_levels(10);

    Spectral spectral_fs(trans);

    Field twoD_field   = spectral_fs.createField<double>(option::name("2d"));
    Field threeD_field = spectral_fs.createField<double>(option::name("3d") | option::levels(nb_levels));

    // Set first wave number
    {
        auto twoD = array::make_view<double, 1>(twoD_field);
        twoD.assign(0.);
        if (mpi::comm().rank() == 0) {
            twoD(0) = 1.;
        }

        auto threeD = array::make_view<double, 2>(threeD_field);
        threeD.assign(0.);
        for (size_t jlev = 0; jlev < nb_levels; ++jlev) {
            if (mpi::comm().rank() == 0) {
                threeD((size_t)0, jlev) = jlev;
            }
        }
    }

    double twoD_norm(0.);
    std::vector<double> threeD_norms(threeD_field.levels(), 0.);

    spectral_fs.norm(twoD_field, twoD_norm);
    if (not threeD_field.contiguous()) {
        EXPECT_THROWS_AS(spectral_fs.norm(threeD_field, threeD_norms), eckit::Exception);
        return;
    }
    else {
        EXPECT_NO_THROW(spectral_fs.norm(threeD_field, threeD_norms));
    }

    if (eckit::mpi::comm().rank() == 0) {
        EXPECT(eckit::types::is_approximately_equal(twoD_norm,
                                                    1.0));  // before is_approximately_equal tolerance was 1.e-10
        for (size_t jlev = 0; jlev < nb_levels; ++jlev) {
            EXPECT(eckit::types::is_approximately_equal(
                threeD_norms[jlev],
                double(jlev)));  // before is_approximately_equal tolerance was 1.e-10
        }
    }
}

#endif

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

}  // namespace test
}  // namespace atlas

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