#include "record_common.h"
#include "field_common.h"
#include "record_polygon.h"
#include "record_line.h"
#include "record_ad_recognition.h"
#include "record_rd_el_lane.h"
#include "record_entrance_level_flat_range.h"

#include <maps/wikimap/mapspro/tools/ymapsdf-conversion/json2ymapsdf/lib/common/config.h>
#include <maps/wikimap/mapspro/tools/ymapsdf-conversion/json2ymapsdf/lib/tds/schema.h>
#include <maps/wikimap/mapspro/tools/ymapsdf-conversion/json2ymapsdf/lib/common/data_error.h>
#include <maps/wikimap/mapspro/tools/ymapsdf-conversion/json2ymapsdf/lib/common/log.h>

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

CommonTransformer::CommonTransformer(
        const ymapsdf::schema::Schema& ymapsdfSchema,
        const ymapsdf::schema::Table* table,
        const xml3::Node* recordNode)
    : RecordTransformer{ymapsdfSchema}
    , table_(table)
    , category_(nullptr)
{
    if (!recordNode) {
        return;
    }

    allowEmpty_ = config::allowEmptySetting(recordNode);

    const auto fields = config::fieldNodes(*recordNode);
    for (size_t i = 0; i < fields.size(); ++i) {
        fieldTransformers_.emplace_back(
            FieldTransformer::create(ymapsdfSchema, table_->name(), fields[i]));
    }

    auto ifNodes = config::ifNodes(*recordNode);
    for (size_t i = 0; i < ifNodes.size(); ++i) {
        orConditions_.emplace_back(std::make_shared<Condition>(ifNodes[i]));
    }
}

ymapsdf::Records
CommonTransformer::transform(const tds::Item& obj) const
{
    ymapsdf::Record record(table_);

    bool skip = !orConditions_.empty();
    for(const auto& condition: orConditions_) {
        if (condition->validFor(obj)) {
            skip = false;
            break;
        }
    }
    if (skip) {
        return { };
    }

   for (const auto &transformer: fieldTransformers_) {
        transformer->transform(obj, record);
    }

    ymapsdf::Records records;
    if (allowEmpty() || !record.empty()) {
        records.push_back(std::move(record));
    }
    return records;
}

ymapsdf::schema::ColumnSet
CommonTransformer::columns() const
{
    ymapsdf::schema::ColumnSet allColumns;
    for (const auto &transformer: fieldTransformers_) {
        auto columns = transformer->columns();
        allColumns.insert(columns.begin(), columns.end());
    }
    return allColumns;
}

void
CommonTransformer::configureAutoDefault(const ymapsdf::schema::ColumnSet& configuredFields)
{
    for (const auto& column: table_->columns()) {
        if (!column.defaultValue().empty() && configuredFields.count(&column) == 0) {
            fieldTransformers_.emplace_back(new AutoDefaultField(&column));
        }
    }
}

void
CommonTransformer::configureAutoCopy(const ymapsdf::schema::ColumnSet& configuredFields)
{
    if (!category_) {
        return;
    }
    for (const auto& column: table_->columns()) {
        // skip already configured in this transformer column
        if (columns().count(&column) > 0) {
            continue;
        }
        // skip column without correspondent attr
        if (!hasAttribute(column.name())) {
            continue;
        }

        // skip column configured somewhere else, except const-attributes
        if (configuredFields.count(&column) > 0 && !hasConstAttribute(column.name())) {
            continue;
        }

        fieldTransformers_.emplace_back(new AutoCopyField(&column, column.name()));
    }
}

bool
CommonTransformer::allowEmpty() const
{
    // allow empty records (with PRIMARY only) for base object->record and relation mapping
    return allowEmpty_ || (category_ != nullptr
        && category_->tablePtr() == table_
        && !table_->primary().isService());
}

CommonRecordTransformer::CommonRecordTransformer(
        const ymapsdf::schema::Schema& ymapsdfSchema,
        const ymapsdf::schema::Table* table,
        const tds::schema::Category* category,
        const xml3::Node* recordNode)
    : CommonTransformer(ymapsdfSchema, table, recordNode)
{
    category_ = category;
    REQUIRE(table, "Unable to create transformer to unknown table");
    for (size_t i = 0; i < table_->size(); ++i) {
        auto column = &(*table_)[i];
        if (column->isPrimary() && !column->isService() && columns().count(column) == 0) {
            fieldTransformers_.emplace_back(new AutoCopyField(column, tds::ATTRIBUTE_OBJECT_ID));
        }
    }
}

bool
CommonRecordTransformer::hasAttribute(const std::string& attr) const
{
    if (!category_) {
        return false;
    }
    return category_->attributes().find(attr) != category_->attributes().end();
}

bool
CommonRecordTransformer::hasConstAttribute(const std::string& attr) const
{
    if (!category_) {
        return false;
    }
    return category_->constAttributes().find(attr) != category_->constAttributes().end();
}

namespace {
const ymapsdf::schema::Table*
tRelTable(const ymapsdf::schema::Schema& ymapsdfSchema, const ymapsdf::schema::Relation& yRel, const xml3::Node* node)
{
    if (node && config::table(ymapsdfSchema, *node)) {
        return config::table(ymapsdfSchema, *node);
    }
    return &yRel.table();
}

const tds::schema::Category*
associatedCategory(
    const tds::schema::Relation& tRel,
    const ymapsdf::schema::Relation& yRel,
    const ymapsdf::schema::Table& table)
{
    if (&table == tRel.master().tablePtr()) {
        return &tRel.master();;
    }
    if (&table == tRel.slave().tablePtr()) {
        return &tRel.slave();;
    }
    //special check for mtr export with multitable names
    if (yRel.master().isPrimary() && !yRel.master().foreignPtr() && !tRel.master().tablePtr()) {
        return &tRel.master();
    }
    if (yRel.slave().isPrimary() && !yRel.slave().foreignPtr() && !tRel.slave().tablePtr()) {
        return &tRel.slave();
    }
    return nullptr;
}

} // namespace

CommonRelationTransformer::CommonRelationTransformer(
        const ymapsdf::schema::Schema& ymapsdfSchema,
        const tds::schema::Relation& tRel,
        const ymapsdf::schema::Relation& yRel,
        const xml3::Node* node)
    : CommonTransformer(ymapsdfSchema, tRelTable(ymapsdfSchema, yRel, node), node)
    , relation_(tRel)
{
    auto addAutoCopyIfMissing = [this] (const ymapsdf::schema::Column* column, const std::string& attrName) {
        if (this->columns().count(column) == 0) {
            this->fieldTransformers_.emplace_back(new AutoCopyField(column, attrName));
        }
    };

    addAutoCopyIfMissing(&yRel.master(), tds::ATTRIBUTE_MASTER_ID);
    addAutoCopyIfMissing(&yRel.slave(), tds::ATTRIBUTE_SLAVE_ID);

    category_ = associatedCategory(tRel, yRel, *table_);
}

bool
CommonRelationTransformer::hasAttribute(const std::string& attr) const
{
    return relation_.attributes().find(attr) != relation_.attributes().end();
}

bool
CommonRelationTransformer::hasConstAttribute(const std::string& attr) const
{
    return relation_.constAttributes().find(attr) != relation_.constAttributes().end();
}

namespace {

using makeTransformerFunc = std::shared_ptr<RecordTransformer>(
    const ymapsdf::schema::Schema&, const ymapsdf::schema::Table*, const tds::schema::Category*, const xml3::Node*);

template<class Transformer>
std::shared_ptr<RecordTransformer>
makeTransformer(
    const ymapsdf::schema::Schema& ymapsdfSchema,
    const ymapsdf::schema::Table* table,
    const tds::schema::Category* category,
    const xml3::Node* node)
{
    return std::make_shared<Transformer>(ymapsdfSchema, table, category, node);
}

} // namespace

std::shared_ptr<RecordTransformer>
RecordTransformer::create(
    const ymapsdf::schema::Schema& ymapsdfSchema,
    const ymapsdf::schema::Table* table,
    const tds::schema::Category* category,
    const xml3::Node* node)
{
    const static std::map<std::string, makeTransformerFunc&> transformerBuilders =
    {
        {SKIP_RECORD, makeTransformer<SkipTransformer>},
        {POLYGON_RECORD, makeTransformer<PolygonTransformer>},
        {LINE_RECORD, makeTransformer<LineTransformer>},
        {AD_RECOGNITION_RECORD, makeTransformer<AdRecognitionTransformer>},
        {RD_EL_LANE_RECORD, makeTransformer<RdElLaneTransformer>},
        {COMMON_RECORD, makeTransformer<CommonRecordTransformer>},
        {COMMON_RELATION, makeTransformer<CommonRecordTransformer>},
        {ENTRANCE_LEVEL_FLAT_RANGE, makeTransformer<EntranceLevelFlatRangeTransformer>},
    };

    std::string transformerName = node
        ? config::recordTransformerName(*node)
        : COMMON_RECORD;
    const auto it = transformerBuilders.find(transformerName);
    REQUIRE(it != transformerBuilders.end(),
        "Unknown algorithm: " << transformerName << " from " << category->name() << " to " << config::tableName(*node));

    return (it->second)(ymapsdfSchema, table, category, node);
}

std::shared_ptr<RecordTransformer>
RecordTransformer::createRelation(
    const ymapsdf::schema::Schema& ymapsdfSchema,
    const tds::schema::Relation& tRel,
    const ymapsdf::schema::Relation& yRel,
    const xml3::Node* node)
{
    return std::make_shared<CommonRelationTransformer>(ymapsdfSchema, tRel, yRel, node);
}

std::shared_ptr<RecordTransformer>
RecordTransformer::createSkip(const ymapsdf::schema::Schema& ymapsdfSchema)
{
    return std::make_shared<SkipTransformer>(ymapsdfSchema);
}

ymapsdf::schema::ColumnSet
RecordTransformer::columns() const
{
    ymapsdf::schema::ColumnSet columns;
    for (const auto& table: tables()) {
        for (const auto& column: table->columns()) {
            columns.insert(&column);
        }
    }
    return columns;
}

ymapsdf::schema::TableSet
RecordTransformer::tables() const
{
    return { };
}

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