#include "route_condition.h"

#include "util.h"

#include <maps/wikimap/mapspro/services/editor/src/magic_strings.h>
#include <maps/wikimap/mapspro/services/editor/src/configs/categories_strings.h>

#include <maps/libs/common/include/exception.h>

#include <map>
#include <utility>


namespace maps {
namespace wiki {

namespace {

struct ConditionRelations {
    TOid fromElementId;
    TOid viaJunctionId;
    std::map<size_t, TOid> toElementIds;
};

} // namespace

const int RouteCondition::FROM_SEQ_NUM = -1;

RouteCondition::RouteCondition(
        const revision::ObjectRevision& revision,
        TOid fromElementId,
        TOid viaJunctionId,
        std::vector<TOid> toElementIds)
    : RouteObject(revision)
    , fromElementId_(fromElementId)
    , viaJunctionId_(viaJunctionId)
    , toElementIds_(std::move(toElementIds))
{
    REQUIRE(
        fromElementId_,
        "Condition " << id() << " has no 'from' element"
    );

    REQUIRE(
        viaJunctionId_,
        "Condition " << id() << " has no 'via' junction"
    );
}

TOid RouteCondition::fromElementId() const { return fromElementId_; }

TOid RouteCondition::viaJunctionId() const { return viaJunctionId_; }

const std::vector<TOid>& RouteCondition::toElementIds() const { return toElementIds_; }

std::vector<RouteCondition> RouteCondition::load(revision::Snapshot& snapshot, const TOIds& ids)
{
    std::map<TOid, ConditionRelations> idToRelations;
    for (const auto& relationRevision: snapshot.loadSlaveRelations(ids)) {
        const auto attrs = common::AttrsWrap::extract(relationRevision);
        const auto& relation = extractRelation(relationRevision);

        const std::string roleId = attrs[ATTR_REL_ROLE];
        const TOid conditionId = relation.masterObjectId();
        const TOid slaveId = relation.slaveObjectId();

        auto& relations = idToRelations[conditionId];
        if (roleId == ROLE_FROM) {
            REQUIRE(
                !relations.fromElementId,
                "multiple elements with role '" << ROLE_FROM << "': "
                    << relations.fromElementId << ", " << slaveId
            );
            relations.fromElementId = slaveId;
        } else if (roleId == ROLE_VIA) {
            REQUIRE(
                !relations.viaJunctionId,
                "multiple junctions with role '" << ROLE_VIA << "': "
                    << relations.viaJunctionId << ", " << slaveId
            );
            relations.viaJunctionId = slaveId;
        } else if (roleId == ROLE_TO) {
            const size_t seqNum = attrs[ATTR_REL_SEQ_NUM];
            REQUIRE(
                !relations.toElementIds[seqNum],
                "multiple elements with role '" << ROLE_TO <<  "' and sequence number "
                    << seqNum << ": " << relations.toElementIds[seqNum] << ", " << slaveId
            );
            relations.toElementIds[seqNum] = slaveId;
        }
    }

    std::vector<RouteCondition> conditions;
    for (auto pair: snapshot.objectRevisions(ids)) {
        const TOid id = pair.first;
        const auto& revision = pair.second;
        const auto& relations = idToRelations[id];

        std::vector<TOid> toElementIds;
        for (size_t seqNum = 0; seqNum < relations.toElementIds.size(); ++seqNum) {
            const auto it = relations.toElementIds.find(seqNum);
            REQUIRE(
                it != relations.toElementIds.end(),
                "Condition " << id << " has no element with role '" << ROLE_TO << "'"
                    " and sequence number " << seqNum
            );
            toElementIds.push_back(it->second);
        }
        conditions.emplace_back(
            revision,
            relations.fromElementId,
            relations.viaJunctionId,
            std::move(toElementIds)
        );
    }

    return conditions;
}

int RouteCondition::lastSeqNum() const
{
    return static_cast<int>(toElementIds().size()) - 1;
}

void RouteCondition::checkSeqNum(int seqNum) const
{
    REQUIRE(
        -1 <= seqNum && seqNum <= lastSeqNum(),
        "Invalid sequence number"
    );
}

bool RouteCondition::isLastSeqNum(int seqNum) const
{
    checkSeqNum(seqNum);
    return lastSeqNum() == seqNum;
}

bool RouteCondition::isFromSeqNum(int seqNum) const {
    checkSeqNum(seqNum);
    return seqNum == FROM_SEQ_NUM;
}

TOid RouteCondition::at(int seqNum) const
{
    checkSeqNum(seqNum);
    return seqNum == FROM_SEQ_NUM ? fromElementId() : toElementIds().at(seqNum);
}

ConditionPath RouteCondition::pathTo(int toSeqNum) const
{
    checkSeqNum(toSeqNum);

    ConditionPath path;
    for (int seqNum = FROM_SEQ_NUM; seqNum <= toSeqNum; ++seqNum) {
        path.push_back(at(seqNum));
    }

    return path;
}

} // namespace wiki
} // namespace maps
