/*
 * (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 <sstream>

#include "atlas/field/Field.h"
#include "atlas/field/FieldSet.h"
#include "atlas/grid/Grid.h"
#include "atlas/runtime/Exception.h"

namespace atlas {
namespace field {

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

FieldSetImpl::FieldSetImpl( const std::string& /*name*/ ) : name_() {}

void FieldSetImpl::clear() {
    index_.clear();
    fields_.clear();
}

Field FieldSetImpl::add( const Field& field ) {
    if ( field.name().size() ) {
        index_[field.name()] = size();
    }
    else {
        std::stringstream name;
        name << name_ << "[" << size() << "]";
        index_[name.str()] = size();
    }
    fields_.push_back( field );
    return field;
}

bool FieldSetImpl::has_field( const std::string& name ) const {
    return index_.count( name );
}

Field& FieldSetImpl::field( const std::string& name ) const {
    if ( !has_field( name ) ) {
        const std::string msg( "FieldSet" + ( name_.length() ? " \"" + name_ + "\"" : "" ) + ": cannot find field \"" +
                               name + "\"" );
        throw_Exception( msg, Here() );
    }
    return const_cast<Field&>( fields_[index_.at( name )] );
}

void FieldSetImpl::haloExchange( bool on_device ) const {
    for ( idx_t i = 0; i < size(); ++i ) {
        field( i ).haloExchange( on_device );
    }
}

void FieldSetImpl::adjointHaloExchange( bool on_device ) const {
    for ( idx_t i = 0; i < size(); ++i ) {
        field( i ).adjointHaloExchange( on_device );
    }
}

void FieldSetImpl::set_dirty( bool value ) const {
    for ( idx_t i = 0; i < size(); ++i ) {
        field( i ).set_dirty( value );
    }
}

std::vector<std::string> FieldSetImpl::field_names() const {
    std::vector<std::string> ret;

    for ( const_iterator field = cbegin(); field != cend(); ++field ) {
        ret.push_back( field->name() );
    }

    return ret;
}

//-----------------------------------------------------------------------------
// C wrapper interfaces to C++ routines
extern "C" {

FieldSetImpl* atlas__FieldSet__new( char* name ) {
    FieldSetImpl* fset = new FieldSetImpl( std::string( name ) );
    fset->name()       = name;
    return fset;
}

void atlas__FieldSet__delete( FieldSetImpl* This ) {
    ATLAS_ASSERT( This != nullptr, "Reason: Use of uninitialised atlas_FieldSet" );
    delete This;
}

void atlas__FieldSet__add_field( FieldSetImpl* This, FieldImpl* field ) {
    ATLAS_ASSERT( This != nullptr, "Reason: Use of uninitialised atlas_FieldSet" );
    ATLAS_ASSERT( field != nullptr, "Reason: Use of uninitialised atlas_Field" );
    This->add( field );
}

int atlas__FieldSet__has_field( const FieldSetImpl* This, char* name ) {
    ATLAS_ASSERT( This != nullptr, "Reason: Use of uninitialised atlas_FieldSet" );
    return This->has_field( std::string( name ) );
}

idx_t atlas__FieldSet__size( const FieldSetImpl* This ) {
    ATLAS_ASSERT( This != nullptr, "Reason: Use of uninitialised atlas_FieldSet" );
    return This->size();
}

FieldImpl* atlas__FieldSet__field_by_name( FieldSetImpl* This, char* name ) {
    ATLAS_ASSERT( This != nullptr, "Reason: Use of uninitialised atlas_FieldSet" );
    return This->field( std::string( name ) ).get();
}

FieldImpl* atlas__FieldSet__field_by_idx( FieldSetImpl* This, idx_t idx ) {
    ATLAS_ASSERT( This != nullptr, "Reason: Use of uninitialised atlas_FieldSet" );
    return This->operator[]( idx ).get();
}

void atlas__FieldSet__set_dirty( FieldSetImpl* This, int value ) {
    ATLAS_ASSERT( This != nullptr, "Reason: Use of uninitialised atlas_FieldSet" );
    This->set_dirty( value );
}

void atlas__FieldSet__halo_exchange( FieldSetImpl* This, int on_device ) {
    ATLAS_ASSERT( This != nullptr, "Reason: Use of uninitialised atlas_FieldSet" );
    This->haloExchange( on_device );
}
}
//-----------------------------------------------------------------------------

}  // namespace field

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

FieldSet::FieldSet() : Handle( new Implementation() ) {}

FieldSet::FieldSet( const std::string& name ) : Handle( new Implementation( name ) ) {}

FieldSet::FieldSet( const Field& field ) : Handle( new Implementation() ) {
    get()->add( field );
}

void FieldSet::set_dirty( bool value ) const {
    get()->set_dirty( value );
}

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

}  // namespace atlas
