/*
 * (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 "atlas/grid/Partitioner.h"
#include "atlas/functionspace/FunctionSpace.h"
#include "atlas/grid/Distribution.h"
#include "atlas/grid/Grid.h"
#include "atlas/grid/detail/distribution/DistributionImpl.h"
#include "atlas/grid/detail/partitioner/Partitioner.h"
#include "atlas/mesh/Mesh.h"
#include "atlas/option.h"
#include "atlas/parallel/mpi/mpi.h"
#include "atlas/runtime/Exception.h"
#include "atlas/runtime/Trace.h"
#include "atlas/util/Config.h"

namespace atlas {
namespace grid {

using Factory = detail::partitioner::PartitionerFactory;

bool Partitioner::exists( const std::string& type ) {
    return Factory::has( type );
}

Partitioner::Partitioner( const std::string& type ) : Handle( Factory::build( type ) ) {}

Partitioner::Partitioner( const std::string& type, const idx_t nb_partitions ) :
    Handle( Factory::build( type, nb_partitions ) ) {}

namespace {
detail::partitioner::Partitioner* partitioner_from_config( const std::string& type,
                                                           const Partitioner::Config& config ) {
    long partitions = mpi::size();
    config.get( "partitions", partitions );
    return Factory::build( type, partitions, config );
}
detail::partitioner::Partitioner* partitioner_from_config( const Partitioner::Config& config ) {
    std::string type;
    if ( not config.get( "type", type ) ) {
        throw_Exception( "'type' missing in configuration for Partitioner", Here() );
    }
    return partitioner_from_config( type, config );
}
}  // namespace

Partitioner::Partitioner( const std::string& type, const Config& config ) :
    Handle( partitioner_from_config( type, config ) ) {}

Partitioner::Partitioner( const Config& config ) : Handle( partitioner_from_config( config ) ) {}

void Partitioner::partition( const Grid& grid, int part[] ) const {
    ATLAS_TRACE( "Partitioner::partition [type=" + get()->type() + "]" );
    get()->partition( grid, part );
}

Distribution Partitioner::partition( const Grid& grid ) const {
    return get()->partition( grid );
}

idx_t Partitioner::nb_partitions() const {
    return get()->nb_partitions();
}

std::string Partitioner::type() const {
    return get()->type();
}

MatchingPartitioner::MatchingPartitioner() : Partitioner() {}

grid::detail::partitioner::Partitioner* matching_mesh_partititioner( const Mesh& mesh,
                                                                     const Partitioner::Config& config ) {
    std::string type( "lonlat-polygon" );
    config.get( "type", type );
    return grid::detail::partitioner::MatchingPartitionerFactory::build( type, mesh );
}

MatchingPartitioner::MatchingPartitioner( const Mesh& mesh ) : MatchingPartitioner( mesh, util::NoConfig() ) {}


MatchingPartitioner::MatchingPartitioner( const Mesh& mesh, const Config& config ) :
    Partitioner( matching_mesh_partititioner( mesh, config ) ) {}


grid::detail::partitioner::Partitioner* matching_functionspace_partititioner( const FunctionSpace& functionspace,
                                                                              const Partitioner::Config& config ) {
    std::string type( "lonlat-polygon" );
    config.get( "type", type );
    return grid::detail::partitioner::MatchingPartitionerFactory::build( type, functionspace );
}

MatchingPartitioner::MatchingPartitioner( const FunctionSpace& functionspace ) :
    MatchingPartitioner( functionspace, util::NoConfig() ) {}


MatchingPartitioner::MatchingPartitioner( const FunctionSpace& functionspace, const Config& config ) :
    Partitioner( matching_functionspace_partititioner( functionspace, config ) ) {}


extern "C" {

detail::partitioner::Partitioner* atlas__grid__Partitioner__new( const Partitioner::Config* config ) {
    detail::partitioner::Partitioner* p;
    {
        Partitioner partitioner( *config );
        p = const_cast<detail::partitioner::Partitioner*>( partitioner.get() );
        p->attach();
    }
    p->detach();
    return p;
}

detail::partitioner::Partitioner* atlas__grid__Partitioner__new_type( const char* type ) {
    detail::partitioner::Partitioner* p;
    {
        Partitioner partitioner{option::type( type )};
        p = const_cast<detail::partitioner::Partitioner*>( partitioner.get() );
        p->attach();
    }
    p->detach();
    return p;
}


detail::partitioner::Partitioner* atlas__grid__MatchingMeshPartitioner__new( const Mesh::Implementation* mesh,
                                                                             const Partitioner::Config* config ) {
    detail::partitioner::Partitioner* p;
    {
        MatchingPartitioner partitioner( Mesh( mesh ), *config );
        p = const_cast<detail::partitioner::Partitioner*>( partitioner.get() );
        p->attach();
    }
    p->detach();
    return p;
}

detail::partitioner::Partitioner* atlas__grid__MatchingFunctionSpacePartitioner__new(
    const FunctionSpace::Implementation* functionspace, const Partitioner::Config* config ) {
    detail::partitioner::Partitioner* p;
    {
        MatchingPartitioner partitioner( FunctionSpace( functionspace ), *config );
        p = const_cast<detail::partitioner::Partitioner*>( partitioner.get() );
        p->attach();
    }
    p->detach();
    return p;
}

Distribution::Implementation* atlas__grid__Partitioner__partition( const Partitioner::Implementation* This,
                                                                   const Grid::Implementation* grid ) {
    Distribution::Implementation* d;
    {
        Distribution distribution = This->partition( Grid( grid ) );
        d                         = const_cast<Distribution::Implementation*>( distribution.get() );
        d->attach();
    }
    d->detach();
    return d;
}

void atlas__grid__Partitioner__delete( detail::partitioner::Partitioner* This ) {
    delete This;
}

}  // extern "C"

}  // namespace grid
}  // namespace atlas
