/*
 * (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 <cmath>
#include <string>

#include "eckit/log/JSON.h"
#include "eckit/parser/JSONParser.h"

#include "atlas/array/ArrayView.h"
#include "atlas/array/DataType.h"
#include "atlas/array/MakeView.h"
#include "atlas/field/Field.h"
#include "atlas/field/State.h"
#include "atlas/grid/Grid.h"
#include "atlas/mesh/Mesh.h"
#include "atlas/runtime/Exception.h"
#include "atlas/runtime/Log.h"

#include "tests/AtlasTestEnvironment.h"

using namespace atlas::field;
using namespace atlas::field;

namespace atlas {
namespace test {

// -------------------------------------------------------------------
// Example State generator

// ---  Declaration (in .h file)
class MyStateGenerator : public StateGenerator {
public:
    MyStateGenerator( const eckit::Parametrisation& p = util::Config() ) : StateGenerator( p ) {}
    ~MyStateGenerator() override = default;
    void generate( State& state, const eckit::Parametrisation& p = util::Config() ) const override;
};

// ---  Implementation (in .cc file)
void MyStateGenerator::generate( State& state, const eckit::Parametrisation& p ) const {
    const util::Config* params = dynamic_cast<const util::Config*>( &p );
    if ( !params ) {
        throw_Exception( "Parametrisation has to be of atlas::util::Config type" );
    }

    util::Config geometry;
    if ( !params->get( "geometry", geometry ) ) {
        throw_Exception( "Could not find 'geometry' in Parametrisation", Here() );
    }

    std::string grid_uid;
    if ( geometry.get( "grid", grid_uid ) ) {
        Grid grid( grid_uid );
        if ( !geometry.has( "ngptot" ) ) {
            geometry.set( "ngptot", grid.size() );
        }
    }

    if ( !geometry.has( "ngptot" ) ) {
        throw_Exception( "Could not find 'ngptot' in Parametrisation" );
    }

    std::vector<util::Config> fields;
    if ( params->get( "fields", fields ) ) {
        for ( size_t i = 0; i < fields.size(); ++i ) {
            util::Config fieldparams;
            // Subsequent "set" calls can overwrite eachother, so that
            // finetuning is possible in e.g. the fields Parametrisation (such as
            // creator, nlev, ngptot)
            fieldparams.set( "creator", "IFS" );
            fieldparams.set( geometry );
            fieldparams.set( fields[i] );
            state.add( Field( fieldparams ) );

            // debug info
            std::stringstream s;
            eckit::JSON json( s );
            json << fieldparams;
            Log::debug() << "fieldparams = " << s.str() << std::endl;
        }
    }
}

// Register in factory
StateGeneratorBuilder<MyStateGenerator> __MyStateGenerator( "MyStateGenerator" );

// ===================================================================
//                               BEGIN TESTS
// ===================================================================

CASE( "state" ) {
    State state;
    EXPECT( state.size() == 0 );

    state.add( Field( "myfield", array::make_datatype<double>(), array::make_shape( 10, 1 ) ) );
    state.add( Field( "", array::make_datatype<double>(), array::make_shape( 10, 2 ) ) );
    state.add( Field( "", array::make_datatype<double>(), array::make_shape( 10, 3 ) ) );

    EXPECT( state.size() == 3 );
    EXPECT( state.has( "myfield" ) );
    EXPECT( state.has( "field_00001" ) );
    EXPECT( state.has( "field_00002" ) );

    EXPECT( state.field( 0 ).name() == std::string( "field_00001" ) );
    EXPECT( state.field( 1 ).name() == std::string( "field_00002" ) );
    EXPECT( state.field( 2 ).name() == std::string( "myfield" ) );

    state.remove( "myfield" );
    EXPECT( state.size() == 2 );
    EXPECT( !state.has( "myfield" ) );

    state.remove( "field_00002" );
    EXPECT( state.size() == 1 );
    EXPECT( !state.has( "field_00002" ) );
}

CASE( "state_generator" ) {
    EXPECT( StateGeneratorFactory::has( "MyStateGenerator" ) );
    std::unique_ptr<StateGenerator> stategenerator( StateGeneratorFactory::build( "MyStateGenerator" ) );
}

CASE( "state_create" ) {
    util::Config p;
    util::Config geometry;
    geometry.set( "grid", "O80" );
    geometry.set( "ngptot", 350 );
    geometry.set( "nproma", 3 );
    geometry.set( "nlev", 5 );
    p.set( "geometry", geometry );

    std::vector<util::Config> fields( 5 );
    fields[0].set( "name", "temperature" );
    fields[0].set( "datatype", array::DataType::real32().str() );

    fields[1].set( "name", "wind" );
    fields[1].set( "nvar", 2 );  // vector field u,v
    fields[1].set( "datatype", array::DataType::real64().str() );

    fields[2].set( "name", "soiltype" );
    fields[2].set( "datatype", array::DataType::int32().str() );
    fields[2].set( "nlev", 1 );  // We can overwrite nlev from geometry here

    fields[3].set( "name", "GFL" );
    fields[3].set( "nvar", 12 );  // assume 12 variables in GFL array
    fields[3].set( "datatype", array::DataType::real64().str() );

    fields[4].set( "name", "array" );
    fields[4].set( "datatype", array::DataType::int64().str() );
    fields[4].set( "creator", "ArraySpec" );
    fields[4].set( "shape", array::make_shape( 10, 2 ) );

    p.set( "fields", fields );

    // We can also translate parameters to a json:
    std::stringstream json;
    eckit::JSON js( json );
    js << p;
    Log::info() << "json = " << json.str() << std::endl;

    // And we can create back parameters from json:
    util::Config from_json_stream( json );

    // And if we have a json file, we could create Parameters from the file:
    // StateGenerater::Parameters from_json_file( eckit::PathName("file.json") );

    State state( "MyStateGenerator", p );

    EXPECT( state.has( "temperature" ) );
    EXPECT( state.has( "wind" ) );
    EXPECT( state.has( "soiltype" ) );

    Log::info() << state.field( "temperature" ) << std::endl;
    Log::info() << state.field( "wind" ) << std::endl;
    Log::info() << state.field( "soiltype" ) << std::endl;
    Log::info() << state.field( "GFL" ) << std::endl;

    array::ArrayView<float, 4> temperature = array::make_view<float, 4>( state.field( "temperature" ) );
    temperature( 0, 0, 0, 0 )              = 0;

    array::ArrayView<double, 4> wind = array::make_view<double, 4>( state.field( "wind" ) );
    wind( 0, 0, 0, 0 )               = 0;

    array::ArrayView<int, 4> soiltype = array::make_view<int, 4>( state.field( "soiltype" ) );
    soiltype( 0, 0, 0, 0 )            = 0;

    array::ArrayView<long, 2> array = array::make_view<long, 2>( state["array"] );
    array( 0, 0 )                   = 0;
}

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

}  // namespace test
}  // namespace atlas

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