#include "serialize.h"
#include "configuration.h"
#include "record_common.h"
#include "field_common.h"
#include <maps/wikimap/mapspro/tools/ymapsdf-conversion/json2ymapsdf/lib/tds/schema.h>
#include <maps/wikimap/mapspro/tools/ymapsdf-conversion/json2ymapsdf/lib/common/helpers.h>

#include <maps/libs/json/include/value.h>
#include <maps/libs/json/include/builder.h>

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

namespace {

// revisionapi attributes (no change)
const std::string JSON_FIELD_ATTRIBUTES = "attributes";
const std::string JSON_FIELD_RELATIONS_EXPORT_MODE = "relations_export_mode";
const std::string JSON_FIELD_INVERT_DIRECTIONS_FOR_ROLES = "invert_direction_for_roles";
const std::string JSON_FIELD_CATEGORIES = "categories";
const std::string JSON_FIELD_RELATIONS = "relations";
const std::string JSON_FIELD_CATEGORY = "category";
const std::string JSON_FIELD_MASTER = "master";
const std::string JSON_FIELD_SLAVE = "slave";
const std::string JSON_FIELD_ROLE = "role";

// custom attributes
const std::string JSON_FIELD_DIRECTION = "direction";
const std::string JSON_FIELD_TRANSFORMERS = "transformers";
const std::string JSON_FIELD_CONST_ATTRIBUTES = "const attributes";
const std::string JSON_FIELD_YMAPSDF_DESTINATION = "ymapsdf destination";

using CategoriesSet = std::set<const tds::schema::Category*, PtrLess<tds::schema::Category>>;
using RelationsSet = std::set<const tds::schema::Relation*, PtrLess<tds::schema::Relation>>;

CategoriesSet
collectCategories()
{
    CategoriesSet categories;
    for (const auto& kv : objectTransformers()) {
        if (!kv.second->skip()) {
            categories.insert(kv.first);
        }
    }
    return categories;
}

RelationsSet
collectRelations()
{
    RelationsSet relations;
    for (const auto& kv : linkTransformers()) {
        if (!kv.second->skip()) {
            relations.insert(kv.first);
        }
    }
    return relations;
}


template <typename Range>
void
serializeTransformers(json::ObjectBuilder& builder, const Range& range)
{
    if (!log8::isEnabled(log8::Level::DEBUG)) {
        return;
    }
    builder[JSON_FIELD_TRANSFORMERS] = [&](json::ObjectBuilder builder) {
        for (auto it = range.first; it != range.second; ++it) {
            it->second->serialize(builder);
        }
    };
}

} // namespace

void
serializeJson(std::ostream& os, const ymapsdf::schema::Schema& ymapsdfSchema)
{
    json::Builder builder(os);
    auto relationsMode = tds::schema::relationsMode();
    builder << [&](json::ObjectBuilder builder) {

        builder[JSON_FIELD_ATTRIBUTES] = [&](json::ObjectBuilder builder) {
            builder[JSON_FIELD_RELATIONS_EXPORT_MODE] =
                boost::lexical_cast<std::string>(relationsMode.mainDirection());
            if (!relationsMode.invertedRoles().empty()) {
                builder[JSON_FIELD_INVERT_DIRECTIONS_FOR_ROLES] =
                    boost::lexical_cast<std::string>(relationsMode.invertedRoles());
            };
        };

        builder[JSON_FIELD_CATEGORIES] = [&](json::ArrayBuilder builder) {
            for (const auto* category: collectCategories()) {
                builder << [&](json::ObjectBuilder builder) {
                    builder[JSON_FIELD_CATEGORY] = category->name();
                    serializeTransformers(builder, objectTransformers().equal_range(category));
                };
            }
        };

        builder[JSON_FIELD_RELATIONS] = [&](json::ArrayBuilder builder) {
            for (const auto* relation: collectRelations()) {
                builder << [&](json::ObjectBuilder builder) {
                    builder[JSON_FIELD_MASTER] = relation->master().name();
                    builder[JSON_FIELD_ROLE] = relation->role();
                    builder[JSON_FIELD_SLAVE] = relation->slave().name();
                    builder[JSON_FIELD_DIRECTION] = boost::lexical_cast<std::string>(relation->direction());
                    builder[JSON_FIELD_YMAPSDF_DESTINATION] =
                        relation->ymapsdfRelation()
                        ? relation->ymapsdfRelation()->name()
                        : "";
                    builder[JSON_FIELD_ATTRIBUTES] = [&](json::ArrayBuilder builder) {
                        for (const auto& attribute: relation->attributes()) {
                            builder << attribute;
                        };
                    };
                    builder[JSON_FIELD_CONST_ATTRIBUTES] = [&](json::ObjectBuilder builder) {
                        for (const auto& [attribute, value]: relation->constAttributes()) {
                            builder[attribute] = value;
                        };
                    };

                    serializeTransformers(builder, linkTransformers().equal_range(relation));
                };
            }
        };

        if (log8::isEnabled(log8::Level::DEBUG)) {
            ymapsdfSchema.serialize(builder);
        }

    };
}

void
RecordTransformer::serialize(maps::json::ObjectBuilder& builder)
{
    builder[name()] = [&](json::ArrayBuilder builder) {
        for (const auto& column: columns()) {
            builder << column->table().name() + "." + column->name();
        }
    };
}


void
CommonTransformer::serialize(maps::json::ObjectBuilder& builder)
{
    builder[name()] = [&](json::ObjectBuilder builder) {
        for (const auto& ft: fieldTransformers_) {
            ft->serialize(builder);
        }
    };
}

void
FieldTransformer::serialize(maps::json::ObjectBuilder& builder) const
{
    builder[name()] = [&](json::ObjectBuilder builder) {
        if (!attributes().empty()) {
            builder["from"] = [&](json::ArrayBuilder builder) {
                for (const auto& attr: attributes()) {
                     builder <<  attr;
                }
            };
        }
        builder["to"] = [&](json::ArrayBuilder builder) {
            for (const auto& column: columns()) {
                builder <<  column->table().name() + "." + column->name();
            }
        };
    };
}

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

