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

// file deepcode ignore CppMemoryLeak: static pointers for global registry are OK and will be cleaned up at end

#include "atlas/field/State.h"

#include <iomanip>
#include <map>
#include <memory>
#include <string>

#include "eckit/thread/AutoLock.h"
#include "eckit/thread/Mutex.h"

#include "atlas/field/Field.h"
#include "atlas/grid/Grid.h"
#include "atlas/mesh/Mesh.h"
#include "atlas/runtime/Exception.h"
#include "atlas/runtime/Log.h"

namespace atlas {
namespace field {

namespace {

static eckit::Mutex* local_mutex                        = nullptr;
static std::map<std::string, StateGeneratorFactory*>* m = nullptr;
static pthread_once_t once                              = PTHREAD_ONCE_INIT;

static void init() {
    local_mutex = new eckit::Mutex();
    m           = new std::map<std::string, StateGeneratorFactory*>();
}

template <typename T>
void load_builder() {
    StateGeneratorBuilder<T>("tmp");
}

void force_link() {
    static struct Link { Link() = default; } link;
    [](const Link&) {}(link);  // disable unused warnings
}

}  // namespace

void State::initialize(const std::string& generator, const eckit::Parametrisation& params) {
    std::unique_ptr<StateGenerator> state_generator(StateGeneratorFactory::build(generator, params));
    state_generator->generate(*this, params);
}

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

State::State() = default;

State::State(const std::string& generator, const eckit::Parametrisation& params) {
    initialize(generator, params);
}

const util::Metadata& State::metadata() const {
    return metadata_;
}

util::Metadata& State::metadata() {
    return metadata_;
}

Field State::add(Field field) {
    ATLAS_ASSERT(field);

    if (field.name().empty()) {
        std::stringstream new_name;
        new_name << "field_" << std::setw(5) << std::setfill('0') << fields_.size();
        ATLAS_ASSERT(!has(new_name.str()));
        field.rename(new_name.str());
    }

    if (has(field.name())) {
        std::stringstream msg;
        msg << "Trying to add field '" << field.name() << "' to State, but State already has a field with this name.";
        throw_AssertionFailed(msg.str(), Here());
    }
    fields_[field.name()] = field;
    return field;
}

const Field& State::field(const std::string& name) const {
    if (!has(name)) {
        std::ostringstream msg;
        msg << "Trying to access field `" << name << "' in State, but no field with this name is present in State.";
        throw_AssertionFailed(msg.str(), Here());
    }
    return fields_.find(name)->second;
}

Field& State::field(const std::string& name) {
    return const_cast<Field&>(static_cast<const State*>(this)->field(name));
}

const Field& State::field(const idx_t idx) const {
    if (idx >= size()) {
        std::ostringstream msg;
        msg << "Trying to access field in State with index " << idx << ", but there exist only " << size()
            << " fields in State.";
        throw_AssertionFailed(msg.str(), Here());
    }
    FieldMap::const_iterator it = fields_.begin();
    for (idx_t i = 0; i < idx; ++i) {
        ++it;
    }
    return it->second;
}

Field& State::field(const idx_t idx) {
    return const_cast<Field&>(static_cast<const State*>(this)->field(idx));
}

std::vector<std::string> State::field_names() const {
    std::vector<std::string> ret;
    if (fields_.size()) {
        ret.reserve(fields_.size());
    }

    for (FieldMap::const_iterator it = fields_.begin(); it != fields_.end(); ++it) {
        ret.push_back(it->first);
    }
    return ret;
}

void State::remove(const std::string& name) {
    if (fields_.find(name) == fields_.end()) {
        std::stringstream msg;
        msg << "Trying to remove field '" << name << "' from State, but it is not present in State.";
        throw_AssertionFailed(msg.str(), Here());
    }
    fields_.erase(name);
}

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

StateGenerator::StateGenerator(const eckit::Parametrisation&) {}

StateGenerator::~StateGenerator() = default;

StateGenerator* StateGeneratorFactory::build(const std::string& name, const eckit::Parametrisation& param) {
    pthread_once(&once, init);

    eckit::AutoLock<eckit::Mutex> lock(local_mutex);

    force_link();

    std::map<std::string, StateGeneratorFactory*>::const_iterator j = m->find(name);

    Log::debug() << "Looking for StateGeneratorFactory [" << name << "]" << std::endl;

    if (j == m->end()) {
        Log::error() << "No StateGeneratorFactory for [" << name << "]" << std::endl;
        Log::error() << "StateFactories are:" << std::endl;
        for (j = m->begin(); j != m->end(); ++j) {
            Log::error() << "   " << (*j).first << std::endl;
        }
        throw_Exception(std::string("No StateGeneratorFactory called ") + name, Here());
    }

    return (*j).second->make(param);
}

void StateGeneratorFactory::list(std::ostream& out) {
    pthread_once(&once, init);

    eckit::AutoLock<eckit::Mutex> lock(local_mutex);

    force_link();

    const char* sep = "";
    for (std::map<std::string, StateGeneratorFactory*>::const_iterator j = m->begin(); j != m->end(); ++j) {
        out << sep << (*j).first;
        sep = ", ";
    }
}

bool StateGeneratorFactory::has(const std::string& name) {
    pthread_once(&once, init);

    eckit::AutoLock<eckit::Mutex> lock(local_mutex);

    force_link();

    return (m->find(name) != m->end());
}

StateGeneratorFactory::StateGeneratorFactory(const std::string& name): name_(name) {
    pthread_once(&once, init);

    eckit::AutoLock<eckit::Mutex> lock(local_mutex);

    ATLAS_ASSERT(m->find(name) == m->end());
    (*m)[name] = this;
}

StateGeneratorFactory::~StateGeneratorFactory() {
    eckit::AutoLock<eckit::Mutex> lock(local_mutex);
    m->erase(name_);
}

//-----------------------------------------------------------------------------
// C wrapper interfaces to C++ routines

extern "C" {

State* atlas__State__new() {
    return new State;
}

void atlas__State__initialize(State* This, const char* generator, const eckit::Parametrisation* params) {
    ATLAS_ASSERT(This != nullptr, "Reason: Use of uninitialised atlas_State");
    This->initialize(std::string(generator), *params);
}

void atlas__State__delete(State* This) {
    ATLAS_ASSERT(This != nullptr, "Reason: Use of uninitialised atlas_State");
    delete This;
}

void atlas__State__add(State* This, FieldImpl* field) {
    ATLAS_ASSERT(This != nullptr, "Reason: Use of uninitialised atlas_State");
    This->add(field);
}

void atlas__State__remove(State* This, const char* name) {
    ATLAS_ASSERT(This != nullptr, "Reason: Use of uninitialised atlas_State");
    This->remove(name);
}

int atlas__State__has(State* This, const char* name) {
    ATLAS_ASSERT(This != nullptr, "Reason: Use of uninitialised atlas_State");
    return This->has(name);
}

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

FieldImpl* atlas__State__field_by_index(State* This, idx_t index) {
    ATLAS_ASSERT(This != nullptr, "Reason: Use of uninitialised atlas_State");
    return This->field(index).get();
}

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

util::Metadata* atlas__State__metadata(State* This) {
    ATLAS_ASSERT(This != nullptr, "Reason: Use of uninitialised atlas_State");
    return &This->metadata();
}
}
//-----------------------------------------------------------------------------

}  // namespace field
}  // namespace atlas
