#include "json_helpers.h"

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

#include <fstream>
#include <sstream>

namespace maps {
namespace wiki {
namespace importer {

namespace {

const std::string ATTRIBUTES = "attributes";
const std::string GEOMETRY = "geometry";
const std::string RELATIONS = "relations";
const std::string SLAVE = "slave";
const std::string MASTER = "master";
const std::string OBJECTS = "objects";

void relationAttributes2json(
    const ObjectPtr& master,
    const ObjectPtr& slave,
    const std::string& roleId,
    json::ObjectBuilder& builder)
{
    builder[ATTRIBUTES] = [&](json::ObjectBuilder builder) {
        builder[rel::MASTER] = master->category().id();
        builder[rel::ROLE] = roleId;
        builder[rel::SLAVE] = slave->category().id();
    };
}

void object2json(
    const ObjectPtr& object,
    json::ObjectBuilder& builder)
{
    builder[std::to_string(object->dbId())] = [&](json::ObjectBuilder builder) {
        builder[ATTRIBUTES] = [&](json::ObjectBuilder builder) {
            for (const auto& attr: object->attributesToAdd()) {
                builder[attr.first] = attr.second;
            }
        };

        if (!object->wkb().empty()) {
            auto geometry = geolib3::WKB::read<geolib3::SimpleGeometryVariant>(object->wkb());
            builder[GEOMETRY] = geolib3::geojson(geometry);
        }

        if (!object->slaveRelations().empty() || !object->masterRelations().empty()) {
            builder[RELATIONS] = [&](json::ArrayBuilder builder) {
                for (const auto& relation : object->slaveRelations()) {
                    const auto& roleId = relation.roleId;
                    const auto& slave = relation.relatedObject;

                    builder << [&](json::ObjectBuilder builder) {
                        relationAttributes2json(object, slave, roleId, builder);
                        builder[SLAVE] = std::to_string(slave->dbId());
                    };
                }

                for (const auto& relation : object->masterRelations()) {
                    const auto& roleId = relation.roleId;
                    const auto& master = relation.relatedObject;

                    builder << [&](json::ObjectBuilder builder) {
                        relationAttributes2json(master, object, roleId, builder);
                        builder[MASTER] = std::to_string(master->dbId());
                    };
                }
            };
        }
    };
}

} // namespace

void objects2json(
    const Objects& objects,
    std::ostream& output,
    const StringMap& globalAttrs,
    MessageReporter& messageReporter)
{
    json::Builder builder(output);
    builder << [&](json::ObjectBuilder builder) {
        builder[ATTRIBUTES] = [&](json::ObjectBuilder builder) {
            for (const auto& attr: globalAttrs) {
                builder[attr.first] = attr.second;
            }
        };
        builder[OBJECTS] = [&](json::ObjectBuilder builder) {
            for (const auto& object : objects) {
                try {
                    object2json(object, builder);
                } catch (const std::exception& e) {
                    messageReporter.error(object->id()) << e.what();
                }
            }
        };
    };
}

} // importer
} // wiki
} // maps
