/*
 * (C) Copyright 1996- 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 <limits>

#include "atlas/array.h"
#include "atlas/field/MissingValue.h"
#include "atlas/functionspace.h"
#include "atlas/grid.h"
#include "atlas/interpolation.h"
#include "atlas/interpolation/NonLinear.h"
#include "atlas/mesh.h"
#include "atlas/meshgenerator.h"
#include "atlas/runtime/Exception.h"

#include "tests/AtlasTestEnvironment.h"


namespace atlas {
namespace test {


const double missingValue    = 42.;
const double missingValueEps = 1e-9;
const double nan             = std::numeric_limits<double>::quiet_NaN();

using field::MissingValue;
using util::Config;

CASE( "Interpolation with MissingValue" ) {
    /*
       Set input field full of 1's, with 9 nodes
         1 ... 1 ... 1
         :     :     :
         1-----m ... 1  m: missing value
         |i   i|     :  i: interpolation on two points, this quadrilateral only
         1-----1 ... 1
     */
    RectangularDomain domain( {0, 2}, {0, 2}, "degrees" );
    Grid gridA( "L90", domain );

    const idx_t nbNodes = 9;
    ATLAS_ASSERT( gridA.size() == nbNodes );

    Mesh meshA = MeshGenerator( "structured" ).generate( gridA );

    functionspace::NodeColumns fsA( meshA );
    Field fieldA = fsA.createField<double>( option::name( "A" ) );

    fieldA.metadata().set( "missing_value", missingValue );
    fieldA.metadata().set( "missing_value_epsilon", missingValueEps );

    auto viewA = array::make_view<double, 1>( fieldA );
    for ( idx_t j = 0; j < fsA.nodes().size(); ++j ) {
        viewA( j ) = 1;
    }


    // Set output field (2 points)
    functionspace::PointCloud fsB( {PointLonLat{0.1, 0.1}, PointLonLat{0.9, 0.9}} );
    Field fieldB( "B", array::make_datatype<double>(), array::make_shape( fsB.size() ) );

    auto viewB = array::make_view<double, 1>( fieldB );
    ATLAS_ASSERT( viewB.size() == 2 );


    SECTION( "missing-if-all-missing" ) {
        Interpolation interpolation( Config( "type", "finite-element" ).set( "non_linear", "missing-if-all-missing" ),
                                     fsA, fsB );

        for ( std::string type : {"equals", "approximately-equals", "nan"} ) {
            fieldA.metadata().set( "missing_value_type", type );
            viewA( 4 ) = type == "nan" ? nan : missingValue;

            EXPECT( MissingValue( fieldA ) );
            interpolation.execute( fieldA, fieldB );

            MissingValue mv( fieldB );
            EXPECT( mv );
            EXPECT( mv( viewB( 0 ) ) == false );
            EXPECT( mv( viewB( 1 ) ) == false );
        }
    }


    SECTION( "missing-if-any-missing" ) {
        Interpolation interpolation( Config( "type", "finite-element" ).set( "non_linear", "missing-if-any-missing" ),
                                     fsA, fsB );

        for ( std::string type : {"equals", "approximately-equals", "nan"} ) {
            fieldA.metadata().set( "missing_value_type", type );
            viewA( 4 ) = type == "nan" ? nan : missingValue;

            EXPECT( MissingValue( fieldA ) );
            interpolation.execute( fieldA, fieldB );

            MissingValue mv( fieldB );
            EXPECT( mv );
            EXPECT( mv( viewB( 0 ) ) );
            EXPECT( mv( viewB( 1 ) ) );
        }
    }


    SECTION( "missing-if-heaviest-missing" ) {
        Interpolation interpolation(
            Config( "type", "finite-element" ).set( "non_linear", "missing-if-heaviest-missing" ), fsA, fsB );

        for ( std::string type : {"equals", "approximately-equals", "nan"} ) {
            fieldA.metadata().set( "missing_value_type", type );
            viewA( 4 ) = type == "nan" ? nan : missingValue;

            EXPECT( MissingValue( fieldA ) );
            interpolation.execute( fieldA, fieldB );

            MissingValue mv( fieldB );
            EXPECT( mv );
            EXPECT( mv( viewB( 0 ) ) == false );
            EXPECT( mv( viewB( 1 ) ) );
        }
    }
}


}  // namespace test
}  // namespace atlas


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