/*
 * (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/utils/Hash.h"

#include "atlas/functionspace.h"
#include "atlas/grid/Grid.h"
#include "atlas/library/defines.h"
#include "atlas/runtime/Exception.h"
#include "atlas/runtime/Log.h"
#include "atlas/trans/Trans.h"
#include "atlas/trans/detail/TransFactory.h"
#include "atlas/trans/detail/TransImpl.h"

namespace atlas {
namespace trans {

namespace {
util::Config options(const eckit::Configuration& config) {
    util::Config opts = Trans::config();
    opts.set(eckit::LocalConfiguration(config));
    return opts;
}
}  // namespace

void Trans::listBackends(std::ostream& out) {
    TransFactory::list(out);
}

bool Trans::hasBackend(const std::string& backend) {
    return TransFactory::has(backend);
}

void Trans::backend(const std::string& backend) {
    ATLAS_ASSERT(hasBackend(backend));
    TransFactory::backend(backend);
}

std::string Trans::backend() {
    return TransFactory::backend();
}

const eckit::Configuration& Trans::config() {
    return TransFactory::config();
}

void Trans::config(const eckit::Configuration& _config) {
    TransFactory::config(_config);
}

Trans::Trans(const FunctionSpace& gp, const FunctionSpace& sp, const eckit::Configuration& config):
    Handle(TransFactory::build(gp, sp, config)) {}

Trans::Trans(const Grid& grid, int truncation, const eckit::Configuration& config):
    Handle(TransFactory::build(grid, truncation, config)) {}

Trans::Trans(const Grid& grid, const Domain& domain, int truncation, const eckit::Configuration& config):
    Handle(TransFactory::build(grid, domain, truncation, config)) {}

Trans::Trans(const Cache& cache, const FunctionSpace& gp, const FunctionSpace& sp, const eckit::Configuration& config):
    Handle(TransFactory::build(cache, gp, sp, config)) {}

Trans::Trans(const Cache& cache, const Grid& grid, int truncation, const eckit::Configuration& config):
    Handle(TransFactory::build(cache, grid, truncation, config)) {}

Trans::Trans(const Cache& cache, const Grid& grid, const Domain& domain, int truncation,
             const eckit::Configuration& config):
    Handle(TransFactory::build(cache, grid, domain, truncation, config)) {}

int Trans::truncation() const {
    return get()->truncation();
}

const Grid& Trans::grid() const {
    return get()->grid();
}

const functionspace::Spectral& Trans::spectral() const {
    return get()->spectral();
}

size_t Trans::spectralCoefficients() const {
    return get()->spectralCoefficients();
}

void Trans::dirtrans(const Field& gpfield, Field& spfield, const eckit::Configuration& config) const {
    get()->dirtrans(gpfield, spfield, options(config));
}

void Trans::dirtrans(const FieldSet& gpfields, FieldSet& spfields, const eckit::Configuration& config) const {
    get()->dirtrans(gpfields, spfields, options(config));
}

void Trans::dirtrans_wind2vordiv(const Field& gpwind, Field& spvor, Field& spdiv,
                                 const eckit::Configuration& config) const {
    get()->dirtrans_wind2vordiv(gpwind, spvor, spdiv, options(config));
}

void Trans::dirtrans_adj(const Field& spfield, Field& gpfield, const eckit::Configuration& config) const {
    get()->dirtrans_adj(spfield, gpfield, options(config));
}

void Trans::dirtrans_adj(const FieldSet& spfields, FieldSet& gpfields, const eckit::Configuration& config) const {
    get()->dirtrans_adj(spfields, gpfields, options(config));
}

void Trans::dirtrans_wind2vordiv_adj(const Field& spvor, const Field& spdiv, Field& gpwind,
                                     const eckit::Configuration& config) const {
    get()->dirtrans_wind2vordiv_adj(spvor, spdiv, gpwind, options(config));
}

void Trans::invtrans(const Field& spfield, Field& gpfield, const eckit::Configuration& config) const {
    get()->invtrans(spfield, gpfield, options(config));
}

void Trans::invtrans(const FieldSet& spfields, FieldSet& gpfields, const eckit::Configuration& config) const {
    get()->invtrans(spfields, gpfields, options(config));
}

void Trans::invtrans_grad(const Field& spfield, Field& gradfield, const eckit::Configuration& config) const {
    get()->invtrans_grad(spfield, gradfield, options(config));
}

void Trans::invtrans_grad(const FieldSet& spfields, FieldSet& gradfields, const eckit::Configuration& config) const {
    get()->invtrans_grad(spfields, gradfields, options(config));
}

void Trans::invtrans_vordiv2wind(const Field& spvor, const Field& spdiv, Field& gpwind,
                                 const eckit::Configuration& config) const {
    get()->invtrans_vordiv2wind(spvor, spdiv, gpwind, options(config));
}


void Trans::invtrans_adj(const Field& gpfield, Field& spfield, const eckit::Configuration& config) const {
    get()->invtrans_adj(gpfield, spfield, options(config));
}

void Trans::invtrans_adj(const FieldSet& gpfields, FieldSet& spfields, const eckit::Configuration& config) const {
    get()->invtrans_adj(gpfields, spfields, options(config));
}

void Trans::invtrans_grad_adj(const Field& gradfield, Field& spfield, const eckit::Configuration& config) const {
    get()->invtrans_grad_adj(gradfield, spfield, options(config));
}

void Trans::invtrans_grad_adj(const FieldSet& gradfields, FieldSet& spfields,
                              const eckit::Configuration& config) const {
    get()->invtrans_grad_adj(gradfields, spfields, options(config));
}

void Trans::invtrans_vordiv2wind_adj(const Field& gpwind, Field& spvor, Field& spdiv,
                                     const eckit::Configuration& config) const {
    get()->invtrans_vordiv2wind_adj(gpwind, spvor, spdiv, options(config));
}

// -- IFS type fields --
// These fields have special interpretation required. You need to know what
// you're doing.
// See IFS trans library.

/*!
 * @brief invtrans
 * @param nb_scalar_fields
 * @param scalar_spectra
 * @param nb_vordiv_fields
 * @param vorticity_spectra
 * @param divergence_spectra
 * @param gp_fields
 */
void Trans::invtrans(const int nb_scalar_fields, const double scalar_spectra[], const int nb_vordiv_fields,
                     const double vorticity_spectra[], const double divergence_spectra[], double gp_fields[],
                     const eckit::Configuration& config) const {
    get()->invtrans(nb_scalar_fields, scalar_spectra, nb_vordiv_fields, vorticity_spectra, divergence_spectra,
                    gp_fields, options(config));
}

/*!
 * @brief invtrans
 * @param nb_fields
 * @param scalar_spectra
 * @param scalar_fields
 */
void Trans::invtrans(const int nb_scalar_fields, const double scalar_spectra[], double gp_fields[],
                     const eckit::Configuration& config) const {
    get()->invtrans(nb_scalar_fields, scalar_spectra, gp_fields, options(config));
}

/*!
 * @brief Inverse transform of vorticity/divergence to wind(U/V)
 * @param nb_fields [in] Number of fields ( both components of wind count as 1 )
 */
void Trans::invtrans(const int nb_vordiv_fields, const double vorticity_spectra[], const double divergence_spectra[],
                     double gp_fields[], const eckit::Configuration& config) const {
    get()->invtrans(nb_vordiv_fields, vorticity_spectra, divergence_spectra, gp_fields, options(config));
}

/*!
 * @brief invtrans_adj
 * @param nb_scalar_fields
 * @param scalar_spectra
 * @param nb_vordiv_fields
 * @param vorticity_spectra
 * @param divergence_spectra
 * @param gp_fields
 */
void Trans::invtrans_adj(const int nb_scalar_fields, const double gp_fields[], const int nb_vordiv_fields,
                         double vorticity_spectra[], double divergence_spectra[], double scalar_spectra[],
                         const eckit::Configuration& config) const {
    get()->invtrans_adj(nb_scalar_fields, gp_fields, nb_vordiv_fields, vorticity_spectra, divergence_spectra,
                        scalar_spectra, options(config));
}

/*!
 * @brief invtrans_adj
 * @param nb_fields
 * @param scalar_spectra
 * @param scalar_fields
 */
void Trans::invtrans_adj(const int nb_scalar_fields, const double gp_fields[], double scalar_spectra[],
                         const eckit::Configuration& config) const {
    get()->invtrans_adj(nb_scalar_fields, gp_fields, scalar_spectra, options(config));
}

/*!
 * @brief Adjoint of Inverse transform of vorticity/divergence to wind(U/V)
 * @param nb_fields [in] Number of fields ( both components of wind count as 1 )
 */
void Trans::invtrans_adj(const int nb_vordiv_fields, const double gp_fields[], double vorticity_spectra[],
                         double divergence_spectra[], const eckit::Configuration& config) const {
    get()->invtrans_adj(nb_vordiv_fields, gp_fields, vorticity_spectra, divergence_spectra, options(config));
}


/*!
 * @brief Direct transform of scalar fields
 */
void Trans::dirtrans(const int nb_fields, const double scalar_fields[], double scalar_spectra[],
                     const eckit::Configuration& config) const {
    get()->dirtrans(nb_fields, scalar_fields, scalar_spectra, options(config));
}

/*!
 * @brief Direct transform of wind(U/V) to vorticity/divergence
 * @param nb_fields [in] Number of fields ( both components of wind count as 1 )
 */
void Trans::dirtrans(const int nb_fields, const double wind_fields[], double vorticity_spectra[],
                     double divergence_spectra[], const eckit::Configuration& config) const {
    get()->dirtrans(nb_fields, wind_fields, vorticity_spectra, divergence_spectra, options(config));
}

}  // namespace trans
}  // namespace atlas
