#include "configuration.h"
#include "transformer.h"
#include "record_common.h"
#include "fix_incomplete.h"
#include <maps/wikimap/mapspro/tools/ymapsdf-conversion/json2ymapsdf/lib/common/config.h>
#include <maps/wikimap/mapspro/tools/ymapsdf-conversion/json2ymapsdf/lib/ymapsdf/schema/pqxx.h>

namespace maps::wiki::json2ymapsdf::transformers {

namespace {
class ConfigurationImpl {
public:
    ConfigurationImpl() = default;
    void configure(const ymapsdf::schema::Schema& ymapsdfSchema);

    const ObjectTransformers& objectTransformers() const { return objectTransformers_; }
    const LinkTransformers& linkTransformers() const { return linkTransformers_; }
    const RecordFixer& fixer(const ymapsdf::schema::Table& table) const;
    const std::vector<SQLFix>& ymapsdfFixers() const { return ymapsdfFixers_; }


private:

    void initLinkTransformers(const ymapsdf::schema::Schema& ymapsdfSchema);
    void initObjectTransformers(const ymapsdf::schema::Schema& ymapsdfSchema);
    void initAutoCopyFields();
    void initAutoDefaultFields();

    void initIncompleteFixers(const ymapsdf::schema::Schema& ymapsdfSchema);
    void initYmapsdfFixers();

    ymapsdf::schema::ColumnSet configuredColumns(const tds::schema::Category& category) const;
    ymapsdf::schema::ColumnSet configuredColumns(const tds::schema::Relation& relation) const;

    void addTransformer(
        const tds::schema::Category& tObj,
        const std::shared_ptr<RecordTransformer>& transformer);

    void addTransformer(
        const tds::schema::Relation& tRel,
        const std::shared_ptr<RecordTransformer>& transformer);

private:
    LinkTransformers linkTransformers_;
    ObjectTransformers objectTransformers_;

    using RecordFixers = std::map<const ymapsdf::schema::Table *, std::shared_ptr<RecordFixer>>;
    RecordFixers fixers_;
    RefuseFixer refuseFixer_;
    std::vector<SQLFix> ymapsdfFixers_;
};

void
ConfigurationImpl::configure(const ymapsdf::schema::Schema& ymapsdfSchema)
{
    initLinkTransformers(ymapsdfSchema);
    initObjectTransformers(ymapsdfSchema);
    initAutoCopyFields();
    initAutoDefaultFields();
    initIncompleteFixers(ymapsdfSchema);
    initYmapsdfFixers();
}

void
ConfigurationImpl::initLinkTransformers(const ymapsdf::schema::Schema& ymapsdfSchema)
{
    for (const auto& kv: tds::schema::relations()) {
        const tds::schema::Relation& tRel = *kv.second;

        if (config::toSkip(tRel)) {
            addTransformer(tRel, RecordTransformer::createSkip(ymapsdfSchema));
            continue;
        }

        if (tRel.ymapsdfRelation() != nullptr) {
            const auto& yRel = *tRel.ymapsdfRelation();
            const auto& defaultTable = yRel.table();
            auto cfgNodes = config::nodes(tRel, defaultTable);
            for (auto cfgNode: cfgNodes) {
                addTransformer(tRel, RecordTransformer::createRelation(ymapsdfSchema, tRel, yRel, cfgNode));
            }
            // create default transformer for relation without config
            if (cfgNodes.empty()) {
                addTransformer(tRel, RecordTransformer::createRelation(ymapsdfSchema, tRel, yRel));
            }
        } else {
            // There is no such relation in ymapsdf, it's exist only in tds. If there is
            // anything in config about it, process it like an object, not a relation
            for (auto cfgNode: config::nodes(tRel)) {
                const ymapsdf::schema::Table* table = config::table(ymapsdfSchema, *cfgNode);
                if (table != nullptr) {
                    addTransformer(tRel,
                        RecordTransformer::create(ymapsdfSchema, table, nullptr, cfgNode));
                } else {
                    WARN() << "Skip unknown relation '" << tRel.name() <<"'";
                }
            }
        }
    }
}

void
ConfigurationImpl::initObjectTransformers(const ymapsdf::schema::Schema& ymapsdfSchema)
{
    for (const auto& kv : tds::schema::categories()) {
        const auto& category = *kv.second;
        if (config::toSkip(category)) {
            addTransformer(category, RecordTransformer::createSkip(ymapsdfSchema));
            continue;
        }

        auto cfgNodes = config::nodes(category);
        for (auto cfgNode: cfgNodes) {
            addTransformer(category,
                RecordTransformer::create(ymapsdfSchema, config::table(ymapsdfSchema, *cfgNode), &category, cfgNode));
        }
        if (cfgNodes.empty()) {
            if (category.tablePtr() != nullptr) {
                addTransformer(category,
                    RecordTransformer::create(ymapsdfSchema, category.tablePtr(), &category));
            } else {
                WARN() << "Skip unknown category '" << category.name() <<"'";
            }
        }
    }
}

void
ConfigurationImpl::initAutoCopyFields()
{
    for (auto& kv: objectTransformers_) {
        const auto& category = *kv.first;
        auto& transformer = *kv.second;
        auto configured = configuredColumns(category);
        transformer.configureAutoCopy(configured);
    }

    for (auto& kv: linkTransformers_) {
        const auto& relation = *kv.first;
        auto& transformer = *kv.second;
        auto configured = configuredColumns(relation);
        transformer.configureAutoCopy(configured);
    }
}

void
ConfigurationImpl::initAutoDefaultFields()
{
    for (auto& kv: objectTransformers_) {
        const auto& category = *kv.first;
        auto& transformer = *kv.second;
        auto configured = configuredColumns(category);
        transformer.configureAutoDefault(configured);
    }

    for (auto& kv: linkTransformers_) {
        const auto& relation = *kv.first;
        auto& transformer = *kv.second;
        auto configured = configuredColumns(relation);
        transformer.configureAutoDefault(configured);
    }
}

void
ConfigurationImpl::initIncompleteFixers(const ymapsdf::schema::Schema& ymapsdfSchema)
{
    for (const auto& node: config::fixIncompleteNodes()) {
        const ymapsdf::schema::Table* tablePtr = &ymapsdfSchema.table(config::tableName(node));
        fixers_.insert({tablePtr, RecordFixer::create(&node)});
    }
}

void
ConfigurationImpl::initYmapsdfFixers()
{
    for (const auto& node: config::fixYmapsdfNodes()) {
        if (config::disabled(node)) {
            continue;
        }

        ymapsdfFixers_.emplace_back(
            SQLFix{
                config::attrName(node),
                config::sql(node)
            }
        );
    }
}

const RecordFixer&
ConfigurationImpl::fixer(const ymapsdf::schema::Table& table) const
{
    auto it = fixers_.find(&table);
    if (it != fixers_.end()) {
        return *it->second;
    }
    return refuseFixer_;
}

ymapsdf::schema::ColumnSet
ConfigurationImpl::configuredColumns(
    const tds::schema::Category& category) const
{
    auto collectColumns = [&] (
        ymapsdf::schema::ColumnSet &configured,
        const RecordTransformer& transformer)
    {
        const auto columns = transformer.columns();
        configured.insert(columns.begin(), columns.end());
    };

    ymapsdf::schema::ColumnSet configured;

    for (const auto& kv: objectTransformers_) {
        if (kv.first == &category) {
            collectColumns(configured, *kv.second);
        }
    }
    for (const auto& kv: linkTransformers_) {
        if (&kv.first->master() == &category
            || &kv.first->slave() == &category)
        {
            collectColumns(configured, *kv.second);
        }
    }

    return configured;
}

ymapsdf::schema::ColumnSet
ConfigurationImpl::configuredColumns(
    const tds::schema::Relation& relation) const
{
    auto configured = configuredColumns(relation.master());
    auto configuredSlave = configuredColumns(relation.slave());
    configured.insert(configuredSlave.begin(), configuredSlave.end());
    return configured;
}

ConfigurationImpl& configurationImpl()
{
    static ConfigurationImpl configurationImpl;
    return configurationImpl;
}

void
ConfigurationImpl::addTransformer(
    const tds::schema::Relation& tRel,
    const std::shared_ptr<RecordTransformer>& transformer)
{
    linkTransformers_.insert({&tRel, transformer});
}

void
ConfigurationImpl::addTransformer(
    const tds::schema::Category& tObj,
    const std::shared_ptr<RecordTransformer>& transformer)
{
    objectTransformers_.insert({&tObj, transformer});
}

} // namespace

void
configure(ymapsdf::schema::Schema& ymapsdfSchema, const Params& params)
{
    auto relationsMode = config::relationsMode(params.transformCfg);
    auto editorCfg = config::getPathEditorCfg(params.transformCfg);

    tds::schema::configure(ymapsdfSchema, editorCfg, relationsMode);
    config::init(params, ymapsdfSchema); //xml config modifies ymapsdf
    tds::schema::attachYmapsdfSchema(ymapsdfSchema);
    configurationImpl().configure(ymapsdfSchema);
}

const ObjectTransformers&
objectTransformers()
{
    return configurationImpl().objectTransformers();
}

const LinkTransformers&
linkTransformers()
{
    return configurationImpl().linkTransformers();
}

const RecordFixer&
fixer(const ymapsdf::schema::Table& table)
{
    return configurationImpl().fixer(table);
}

const std::vector<SQLFix>&
ymapsdfFixers()
{
    return configurationImpl().ymapsdfFixers();
}


} // namespace maps::wiki::json2ymapsdf::transformers
