#include "field_common.h"

#include "field_access_id.h"
#include "field_generate_id.h"
#include "field_locale.h"
#include "field_rubric.h"

#include <memory>
#include <utility>

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

namespace {
const std::string LOCALE_LANG = "lang";
const std::string LOCALE_EXTLANG = "extlang";
const std::string LOCALE_SCRIPT = "script";
const std::string LOCALE_REGION = "region";
const std::string LOCALE_VARIANT = "variant";

} // namespace

SingleFieldTransformer::SingleFieldTransformer(const ymapsdf::schema::Column* column)
    : column_(column)
{
    ASSERT(column != nullptr);
}

SingleFieldTransformer::SingleFieldTransformer(
        const ymapsdf::schema::Schema& ymapsdfSchema,
        const std::string& tableName,
        const xml3::Node& fieldNode)
    : column_(&ymapsdfSchema.table(tableName)[config::fieldName(fieldNode)])
{ }

CopyField::CopyField(const ymapsdf::schema::Column* column, std::string attrName)
    : SingleFieldTransformer(column)
    , tdsAttrName_(std::move(attrName))
{ }

CopyField::CopyField(
        const ymapsdf::schema::Schema& ymapsdfSchema,
        const std::string& tableName,
        const xml3::Node& fieldNode)
    : SingleFieldTransformer(ymapsdfSchema, tableName, fieldNode)
    , tdsAttrName_(config::attrName(fieldNode))
{ }

void
CopyField::transform(const tds::Item& object, ymapsdf::Record& record) const
{
    record[column()->id()] = object[tdsAttrName_];
}

ConstField::ConstField(const ymapsdf::schema::Column* column, std::string value)
    : SingleFieldTransformer(column)
    , value_(std::move(value))
{ }

ConstField::ConstField(
        const ymapsdf::schema::Schema& ymapsdfSchema,
        const std::string& tableName,
        const xml3::Node& fieldNode)
    : SingleFieldTransformer(ymapsdfSchema, tableName, fieldNode)
    , value_(config::value(fieldNode))
{ }

void
ConstField::transform(const tds::Item&, ymapsdf::Record& record) const
{
    record[column()->id()] = value_;
}

ConditionalField::ConditionalField(const ymapsdf::schema::Schema& ymapsdfSchema, const std::string& tableName, const xml3::Node& fieldNode)
    : SingleFieldTransformer(ymapsdfSchema, tableName, fieldNode)
{
    auto updateName = [&name = name_](const std::string& newName) {
        if (name.empty()) {
            name = newName;
        } else if (name != newName) {
            name = CONDITIONAL_MULTI_FIELD;
        }
    };

    auto ifNodes = config::ifNodes(fieldNode);

    for (size_t i = 0; i < ifNodes.size(); ++i) {
        const auto& ifNode = ifNodes[i];
        const auto condition = std::make_shared<Condition>(ifNode);

        const auto copyAttr = config::attrName(ifNode);
        const auto assignValue = config::value(ifNode);
        const auto algorithm = config::algorithmName(ifNode);

        std::shared_ptr<FieldTransformer> transformer;
        if (algorithm == GENERATE_ID_FIELD) {
            transformer = std::make_shared<GenerateIdField>(column(), ifNode);
            updateName(CONDITIONAL_PREFIX + GENERATE_ID_FIELD);
        } else if (algorithm == LOCALE_FIELD) {
            transformer = std::make_shared<LocaleField>(ymapsdfSchema, column()->table().name(), ifNode);
            updateName(CONDITIONAL_PREFIX + LOCALE_FIELD);
        } else if (algorithm == RUBRIC_FIELD) {
            transformer = std::make_shared<RubricField>(column());
            updateName(CONDITIONAL_PREFIX + RUBRIC_FIELD);
        } else if (!copyAttr.empty()) {
            transformer = std::make_shared<CopyField>(column(), copyAttr);
            updateName(CONDITIONAL_PREFIX + COPY_FIELD);
        } else if (algorithm == ACCESS_ID) {
            transformer = std::make_shared<AccessIdField>(column());
            updateName(CONDITIONAL_PREFIX + ACCESS_ID);
        } else {
            transformer = std::make_shared<ConstField>(column(), assignValue);
            updateName(CONDITIONAL_PREFIX + CONST_FIELD);
        }
        conditionalTransformers_.push_back({condition, transformer});
    }

    const auto condition = std::make_shared<Condition>();
    const auto transformer = std::make_shared<AutoDefaultField>(column());
    conditionalTransformers_.push_back({condition, transformer});
}

void
ConditionalField::transform(const tds::Item& item, ymapsdf::Record& record) const
{
    for(const auto& ct: conditionalTransformers_) {
        if (ct.condition->validFor(item)) {
            return ct.transformer->transform(item, record);
        }
    }
}

std::set<std::string>
ConditionalField::attributes() const
{
    std::set<std::string> attrs;
    for(const auto& ct: conditionalTransformers_) {
        for (const auto& attr: ct.transformer->attributes()) {
            attrs.insert(attr);
        }
    }
    return attrs;
}

namespace {

template<class Transformer>
std::shared_ptr<FieldTransformer>
makeTransformer(const ymapsdf::schema::Schema& ymapsdfSchema, const std::string& tblName, const xml3::Node& node)
{
    return std::make_shared<Transformer>(ymapsdfSchema, tblName, node);
}

std::shared_ptr<FieldTransformer>
makeCommonTransformer(const ymapsdf::schema::Schema& ymapsdfSchema, const std::string& tblName, const xml3::Node& node)
{
    auto ifNodes = config::ifNodes(node);
    auto attr = config::attrName(node);
    auto value = config::value(node);

    if (ifNodes.size() != 0) {
        return std::make_shared<ConditionalField>(ymapsdfSchema, tblName, node);
    }
    if (!attr.empty()) {
        return std::make_shared<CopyField>(ymapsdfSchema, tblName, node);
    }
    REQUIRE(!value.empty(), "Invalid common field transformer");
    return std::make_shared<ConstField>(ymapsdfSchema, tblName, node);
}

} // namespace

std::shared_ptr<FieldTransformer>
FieldTransformer::create(const ymapsdf::schema::Schema& ymapsdfSchema, const std::string& tblName, const xml3::Node& node)
{
    using makeTransformerFunc = std::shared_ptr<FieldTransformer>(const ymapsdf::schema::Schema&, const std::string&, const xml3::Node&);

    const static std::map<std::string, makeTransformerFunc&> transformerBuilders = {
        {COMMON_FIELD, makeCommonTransformer},
        {GENERATE_ID_FIELD, makeTransformer<GenerateIdField>},
        {LOCALE_FIELD, makeTransformer<LocaleField>},
        {RUBRIC_FIELD, makeTransformer<RubricField>},
        {ACCESS_ID, makeTransformer<AccessIdField>},
    };

    std::string transformerName = config::fieldTransformerName(node);
    auto it = transformerBuilders.find(transformerName);
    REQUIRE(it != transformerBuilders.end(),
        "Unknown field transformer for algorithm: " << transformerName);
    return (it->second)(ymapsdfSchema, tblName, node);
}

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