#include "magic_string.h"
#include "utils.h"
#include "../message_reporter.h"

#include <yandex/maps/wiki/diffalert/object.h>
#include <maps/libs/common/include/enum_bitset.h>

#include <string>
#include <unordered_set>

namespace maps::wiki::diffalert {
namespace {
const Priority HIGH_PRIORITY {1, 0};
const Priority LOW_PRIORITY {2, 0};

bool
isImportantRoad(const Object& rdEl)
{
    const int fc = rdEl.attr(attr::FC).as<int>();
    return 1 <= fc && fc <= 4;
}

bool
isImportantRoadConnectedTo(Object& junction, Snapshot& snapshot)
{
    const auto masters = junction.loadMasterRelations();
    TIds rdElIds;
    for (const auto& relation : masters) {
        if (relation.role == role::START || relation.role == role::END) {
            rdElIds.insert(relation.masterId);
        }
    }
    if (rdElIds.empty()) {
        return false;
    }
    const auto rdEls = snapshot.objectsByIds(rdElIds);
    for (const auto& rdEl : rdEls) {
        if (isImportantRoad(*rdEl)) {
            return true;
        }
    }
    return false;
}

bool
isImportantCondDs(Object& condDs, Snapshot& snapshot)
{
    const auto slaves = condDs.loadSlaveRelations();
    TIds rdElIds;
    for (const auto& relation : slaves) {
        const auto& role = relation.role;
        const auto slaveId = relation.slaveId;
        if (role == role::VIA) {
            const auto jc = snapshot.objectsByIds({slaveId});
            ASSERT(jc.size());
            if (isImportantRoadConnectedTo(*jc[0], snapshot)) {
                return true;
            }
        } else if (role == role::FROM || role == role::TO) {
            rdElIds.insert(slaveId);
        }
    }
    const auto rdEls = snapshot.objectsByIds(rdElIds);
    for (const auto& rdEl : rdEls) {
        if (isImportantRoad(*rdEl)) {
            return true;
        }
    }
    return false;
}

enum class CondDsChangeType: unsigned
{
    Created,
    Deleted,
    Geometry,
    Attributes,
    EnumBitsetFlagsEnd
};

bool
hasBoundCondDs(Object& object, Snapshot& snapshot)
{
    const auto masterRelations = object.loadMasterRelations();
    TIds masterIds;
    for (const auto& relation : masterRelations) {
        if (relation.role == role::FROM ||
            relation.role == role::VIA ||
            relation.role == role::TO)
        {
            masterIds.insert(relation.masterId);
        }
    }
    const auto masters = snapshot.objectsByIds(masterIds);
    for (const auto& master : masters) {
        if (master->categoryId() == cat::COND_DS) {
            return true;
        }
    }
    return false;
}

bool
hasBoundCondDs(const DiffContext& diff)
{
    return
        (diff.newObject() && hasBoundCondDs(*diff.newObject(), diff.newSnapshot())) ||
        (diff.oldObject() && hasBoundCondDs(*diff.oldObject(), diff.oldSnapshot()));
}

maps::common::EnumBitset<CondDsChangeType>
condDsChangeType(const DiffContext& diff)
{
    if (diff.categoryId() == cat::COND_DS) {
        if (!diff.oldObject()) {
            return {CondDsChangeType::Created};
        }
        if (!diff.newObject()) {
            return {CondDsChangeType::Deleted};
        }
        maps::common::EnumBitset<CondDsChangeType> changed;
        if (diff.attrsChanged() || diff.tableAttrsChanged()) {
            changed.set(CondDsChangeType::Attributes);
        }
        if (diff.relationsChanged()) {
            changed.set(CondDsChangeType::Geometry);
        }
        return changed;
    }
    if (diff.geomChanged() &&
        (diff.categoryId() == cat::RD_JC ||
            diff.categoryId() == cat::RD_EL) &&
        hasBoundCondDs(diff))
    {
        return {CondDsChangeType::Geometry};
    }
    return {};
}
} // namespace

void checkCondDsCreated(const DiffContext& diff, MessageReporter& messages)
{
    if (!condDsChangeType(diff).isSet(CondDsChangeType::Created)) {
        return;
    }
    messages.report(
        isImportantCondDs(*diff.newObject(), diff.newSnapshot())
            ? HIGH_PRIORITY
            : LOW_PRIORITY,
        "cond-ds-created");
}

void checkCondDsDeleted(const DiffContext& diff, MessageReporter& messages)
{
    if (!condDsChangeType(diff).isSet(CondDsChangeType::Deleted)) {
        return;
    }
    messages.report(
        isImportantCondDs(*diff.oldObject(), diff.oldSnapshot())
            ? HIGH_PRIORITY
            : LOW_PRIORITY,
        "cond-ds-deleted");
}

void checkCondDsAttributesModified(const DiffContext& diff, MessageReporter& messages)
{
    if (!condDsChangeType(diff).isSet(CondDsChangeType::Attributes)) {
        return;
    }
    messages.report(
        isImportantCondDs(*diff.newObject(), diff.newSnapshot()) ||
            isImportantCondDs(*diff.oldObject(), diff.oldSnapshot())
            ? HIGH_PRIORITY
            : LOW_PRIORITY,
        "cond-ds-attributes-modified");
}

void checkCondDsGeometryModified(const DiffContext& diff, MessageReporter& messages)
{
    if (!condDsChangeType(diff).isSet(CondDsChangeType::Geometry)) {
        return;
    }
    messages.report(LOW_PRIORITY, "cond-ds-geometry-modified");
}

} // namespace diffalertmaps::wiki::diffalert
