#include "request_data.h"

#include "json_parser.h"
#include <maps/wikimap/mapspro/services/editor/src/objects_cache.h>
#include <maps/wikimap/mapspro/services/editor/src/revisions_facade.h>

#include <yandex/maps/wiki/revision/exception.h>

namespace maps {
namespace wiki {

namespace {
const std::string REQUEST_OBJECTS_UPDATE_XPATH = "//wmp:editor/wmp:request-objects-update";
const std::string REQUEST_OBJECTS_UPDATE_CONTEXT = REQUEST_OBJECTS_UPDATE_XPATH + "/wmp:context";
const std::string REQUEST_OBJECTS_UPDATE_OBJECTS = REQUEST_OBJECTS_UPDATE_XPATH + "/wmp:objects/wmp:object";
}

void
RequestData::parse(common::FormatType format, const std::string& body, ObjectsCache& cache)
{
    try {
        if (format == common::FormatType::JSON) {
            parseJson(body, cache);
        } else if (format == common::FormatType::XML) {
            parseXml(body, cache);
        } else {
            REQUIRE(false, "Unknown format type " << (int) format);
        }
    } catch (const maps::wiki::LogicException& ex) {
        throw;
    } catch (const maps::wiki::revision::ConflictsFoundException& ex) {
        throw;
    } catch (const maps::Exception& ex) {
        THROW_WIKI_LOGIC_ERROR(ERR_BAD_REQUEST,
            " Can't parse request json body:" << ex.what());
    } catch (...) {
        throw;
    }
}

void
RequestData::parseJson(const std::string& json, ObjectsCache& cache)
{
    auto jsonObject = json::Value::fromString(json);

    auto parseGeoObject = [&](const json::Value& objValue, RevisionIds& revisionIds) {
        ASSERT(objValue.hasField(STR_ID));
        oids_.push_back(boost::lexical_cast<TOid>(objValue[STR_ID].toString()));
        bool hasRevsion = objValue.hasField(STR_REVISION_ID);
        ASSERT(versionCheckPolicy_ == VersionCheckPolicy::Optional || hasRevsion);
        if (hasRevsion) {
            revisionIds.insert(JsonParser::readObjectRevision(objValue));
        }
    };

    RevisionIds revisionIds;
    if (jsonObject.hasField(STR_GEO_OBJECT)) {
        parseGeoObject(jsonObject[STR_GEO_OBJECT], revisionIds);
    } else {
        ASSERT(jsonObject.hasField(STR_GEO_OBJECTS));
        ASSERT(jsonObject[STR_GEO_OBJECTS].isArray());
        for (const auto& objValue : jsonObject[STR_GEO_OBJECTS]) {
            parseGeoObject(objValue, revisionIds);
        }
    }
    cache.revisionsFacade().gateway().checkConflicts(revisionIds);

    ASSERT(jsonObject.hasField(STR_CONTEXT));
    parseContextDataJson(jsonObject[STR_CONTEXT], cache);
}

void
RequestData::parseXml(const std::string& xml, ObjectsCache& cache)
{
    xml3::Doc doc = xml3::Doc::fromString(xml);
    registerNamespaces(doc);
    auto objectNodes = doc.nodes(REQUEST_OBJECTS_UPDATE_OBJECTS);

    RevisionIds revisionIds;
    for (size_t i = 0; i < objectNodes.size(); ++i) {
        oids_.push_back(objectNodes[i].attr<TOid>(STR_ID));

        auto revIdNode = objectNodes[i].firstElementChild(STR_REVISION);
        if (!revIdNode.isNull()) {
            revisionIds.insert(revIdNode.attr<TRevisionId>(STR_ID));
        }
    }
    cache.revisionsFacade().gateway().checkConflicts(revisionIds);

    parseContextDataXml(doc.node(REQUEST_OBJECTS_UPDATE_CONTEXT), cache);
}

} // namespace wiki
} // namespace maps
