#include "save_object_parser.h"

#include "json_parser.h"
#include "xml_parser.h"
#include "common.h"

namespace maps {
namespace wiki {

namespace {
template <class Derived, class CommonParser>
class ParserImpl
{
public:
    ParserImpl(
            ObjectsUpdateDataCollection& objects,
            ObjectsEditContextsPtrMap& editContexts)
        : objects_(objects)
        , editContexts_(editContexts)
    {}

protected:
    UniqueId
    readObject(
        const typename CommonParser::ValueType& objValue,
        bool isPrimary,
        AttributeErrorPolicy attributeErrorPolicy = AttributeErrorPolicy::Fail)
    {
        auto categoryId = CommonParser::readObjectCategory(objValue);
        StringMultiMap attributes;
        try {
            attributes = CommonParser::readObjectAttributes(objValue);
        } catch (const maps::Exception& ex) {
            if (attributeErrorPolicy != AttributeErrorPolicy::EmptyResult) {
                throw;
            }
            WARN() << "Parse attributes:" << ex;
        }
        const auto newId = objects_.add(
            isPrimary,
            CommonParser::readObjectRevision(objValue),
            CommonParser::readObjectUUID(objValue),
            categoryId,
            CommonParser::readObjectGeometry(objValue),
            std::move(attributes),
            CommonParser::readObjectTableAttributes(objValue),
            CommonParser::readObjectRichContent(objValue));

        Derived* derived = static_cast<Derived*>(this);
        if (!categoryId.empty()) {//not reference
            objects_.updateObjectRelations(
                newId,
                derived->readObjectRelativesInfosDiff(STR_SLAVES, objValue),
                derived->readObjectRelativesInfosDiff(STR_MASTERS, objValue));
        }

        auto objectEditContextPtr = CommonParser::readObjectEditContext(objValue);
        if (objectEditContextPtr) {
            editContexts_.insert({newId, objectEditContextPtr});
        }
        return newId;
    }

    ObjectsUpdateDataCollection& objects_;
    ObjectsEditContextsPtrMap& editContexts_;
};

/// JSON parser impl

class JsonSaveObjectParser : public ParserImpl<JsonSaveObjectParser, JsonParser>
{
public:
    JsonSaveObjectParser(
            ObjectsUpdateDataCollection& objects,
            ObjectsEditContextsPtrMap& editContexts)
        : ParserImpl<JsonSaveObjectParser, JsonParser>(objects, editContexts)
    {}

    void operator () (
        const std::string& requestBody,
        AttributeErrorPolicy attributeErrorPolicy);

private:
    friend class ParserImpl<JsonSaveObjectParser, JsonParser>;

    RelativesDiffByRoleMap readObjectRelativesInfosDiff(
        const std::string& relativesFieldName, const json::Value& jsonObject);
};

RelativesDiffByRoleMap
JsonSaveObjectParser::readObjectRelativesInfosDiff(
    const std::string& relativesFieldName, const json::Value& jsonObject)
{
    if (!jsonObject.hasField(relativesFieldName)) {
        return {};
    }
    auto jsonRelatives = jsonObject[relativesFieldName];

    RelativesDiffByRoleMap result;
    const auto& roles = jsonRelatives.fields();
    for (const auto& role : roles) {
        UniqueIdSet added;
        UniqueIdSet changed;
        UniqueIdSet removed;
        if (!jsonRelatives[role].hasField(STR_DIFF)) {
            continue;
        }
        const auto& diffField = jsonRelatives[role][STR_DIFF];
        if (diffField.hasField(STR_ADDED)) {
            for (const auto& relativeData : diffField[STR_ADDED]) {
                added.insert(readObject(relativeData, /* bool isPrimary = */ false));
            }
        }
        if (diffField.hasField(STR_CHANGED)) {
            for (const auto& relativeData : diffField[STR_CHANGED]) {
                changed.insert(readObject(relativeData, /* bool isPrimary = */ false));
            }
        }
        if (diffField.hasField(STR_REMOVED)) {
            for (const auto& relativeData : diffField[STR_REMOVED]) {
                removed.insert(readObject(relativeData, /* bool isPrimary = */ false));
            }
        }
        if (added.empty() && changed.empty() && removed.empty()) {
            continue;
        }
        result.insert(
            {role,
             RelativesDiff(std::move(added), std::move(changed), std::move(removed))});
    }

    return result;
}

void
JsonSaveObjectParser::operator () (
    const std::string& jsonString,
    AttributeErrorPolicy attributeErrorPolicy )
{
    auto jsonValueObjects = json::Value::fromString(jsonString);
    if (!jsonValueObjects.hasField(STR_GEO_OBJECTS)) {
        readObject(jsonValueObjects, /* bool isPrimary = */ true, attributeErrorPolicy);
    } else {
        ASSERT(jsonValueObjects[STR_GEO_OBJECTS].isArray());
        for (const auto& objValue : jsonValueObjects[STR_GEO_OBJECTS]) {
            readObject(objValue, /* bool isPrimary = */ true, attributeErrorPolicy);
        }
        if (jsonValueObjects[STR_GEO_OBJECTS].size() > 1 &&
            std::all_of(objects_.begin(), objects_.end(),
            [&](const auto& objectUpdateData) {
                return objects_.isPrimary(objectUpdateData.id()) &&
                    objectUpdateData.slavesDiff().empty() &&
                    objectUpdateData.mastersDiff().empty();
            }))
        {
            objects_.setMultisave();
        }
    }
    DEBUG() << BOOST_CURRENT_FUNCTION << " Read objects: " << objects_.size();
}


/// XML parser impl

class XmlSaveObjectParser : public ParserImpl<XmlSaveObjectParser, XmlParser>
{
public:
    XmlSaveObjectParser(
            ObjectsUpdateDataCollection& objects,
            ObjectsEditContextsPtrMap& editContexts)
        : ParserImpl<XmlSaveObjectParser, XmlParser>(objects, editContexts)
    {}

    void operator () (
        const std::string& requestBody,
        AttributeErrorPolicy attributeErrorPolicy);

private:
    friend class ParserImpl<XmlSaveObjectParser, XmlParser>;

    typedef std::map<std::string, UniqueIdSet> RelativesSetByRoleMap;

    RelativesSetByRoleMap readRelatives(const maps::xml3::Node& relativesNode);
    RelativesDiffByRoleMap readObjectRelativesInfosDiff(
        const std::string& relativesNodeName, const maps::xml3::Node& objNode);
};


XmlSaveObjectParser::RelativesSetByRoleMap
XmlSaveObjectParser::readRelatives(const maps::xml3::Node& relativesNode)
{
    RelativesSetByRoleMap result;
    if (relativesNode.isNull()) {
        return result;
    }
    xml3::Node roleNode = relativesNode.firstElementChild(STR_ROLE);
    while (!roleNode.isNull()) {
        std::string roleId = roleNode.attr<std::string>(STR_ID);
        xml3::Node relativeNode = roleNode.firstElementChild(STR_OBJECT);
        while (!relativeNode.isNull()) {
            const UniqueId id = readObject(relativeNode, /* bool isPrimary = */ false);
            DEBUG() << "Read relative " << id << " for role " << roleId;
            result[roleId].insert(id);
            relativeNode = relativeNode.nextElementSibling(STR_OBJECT);
        }
        roleNode = roleNode.nextElementSibling(STR_ROLE);
    }
    return result;
}

RelativesDiffByRoleMap
XmlSaveObjectParser::readObjectRelativesInfosDiff(
    const std::string& relativesNodeName, const maps::xml3::Node& objNode)
{
    maps::xml3::Node relativesNode = objNode.firstElementChild(relativesNodeName);
    if (relativesNode.isNull()) {
        return {};
    }

    xml3::Node diffNode = relativesNode.firstElementChild(STR_DIFF);
    if (diffNode.isNull()) {
        return RelativesDiffByRoleMap();
    }

    RelativesSetByRoleMap addedByRoles =
        readRelatives(diffNode.firstElementChild(STR_ADDED));
    RelativesSetByRoleMap removedByRoles =
        readRelatives(diffNode.firstElementChild(STR_REMOVED));

    std::set<std::string> roleIds;

    for (const auto& diffPair : addedByRoles) {
        roleIds.insert(diffPair.first);
    }
    for (const auto& diffPair : removedByRoles) {
        roleIds.insert(diffPair.first);
    }

    RelativesDiffByRoleMap result;
    for (const auto& roleId : roleIds) {
        auto addedIt = addedByRoles.find(roleId);
        bool noAdded = addedIt == addedByRoles.end() || addedIt->second.empty();
        auto removedIt = removedByRoles.find(roleId);
        bool noRemoved = removedIt == removedByRoles.end() || removedIt->second.empty();
        if (noAdded && noRemoved) {
            continue;
        }
        result.insert({
            roleId,
            RelativesDiff(
                noAdded ? UniqueIdSet() : addedIt->second,
                {},
                noRemoved ? UniqueIdSet() : removedIt->second)});
    }

    return result;
}

void
XmlSaveObjectParser::operator () (
    const std::string& xmlData,
    AttributeErrorPolicy attributeErrorPolicy)
{
    xml3::Doc doc = xml3::Doc::fromString(xmlData);
    registerNamespaces(doc);
    xml3::Nodes nodes = XmlParser::objectNodes(doc);
    for (size_t i = 0; i < nodes.size(); ++i) {
        readObject(nodes[i], /* bool isPrimary = */ true, attributeErrorPolicy);
    }
    DEBUG() << BOOST_CURRENT_FUNCTION << " Read " << nodes.size();

}

} // namespace

void
SaveObjectParser::parse(
    common::FormatType formatType,
    const std::string& requestBody,
    AttributeErrorPolicy attributeErrorPolicy)
{
    if (formatType == common::FormatType::XML) {
        XmlSaveObjectParser(objects_, editContexts_)(requestBody, attributeErrorPolicy);
    } else if (formatType == common::FormatType::JSON) {
        JsonSaveObjectParser(objects_, editContexts_)(requestBody, attributeErrorPolicy);
    } else {
        REQUIRE(false, "Unknown format type " << (int)formatType);
    }
}

} // namespace wiki
} // namespace maps
