#include "transport.h"
#include "utils.h"
#include "magic_string.h"

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

#include <yandex/maps/wiki/common/misc.h>

#include <boost/algorithm/string.hpp>

namespace maps {
namespace wiki {
namespace diffalert {
namespace {

bool isMajorFtType(const Object& obj)
{
    if (common::isIn(
            obj.categoryId(),
            {
                cat::TRANSPORT_METRO_STATION,
                cat::TRANSPORT_METRO_EXIT,
                cat::TRANSPORT_AIRPORT_TERMINAL,
                cat::TRANSPORT_TERMINAL,
            })) {
        return true;
    }

    auto ftType = getFtType(obj);
    return common::isIn(
            ftType,
            {
                FtType::TransportWaterwaySeaport,
                FtType::TransportWaterwayRiverport,

                FtType::TransportAirport,
                FtType::TransportAirportDomestic,
            });
}

bool isTransport(const std::string& categoryId)
{
    return boost::algorithm::starts_with(categoryId, cat::TRANSPORT_PREFIX);
}

} // namespace

void checkMajorTransport(const DiffContext& d, MessageReporter& messages)
{
    if (!isAny(d, isMajorFtType)) {
        return;
    }

    if (!d.oldObject()) {
        messages.report({0, 3}, "major-transport-created");
    } else if (!d.newObject()) {
        messages.report({0, 3}, "major-transport-deleted");
    } else {
        if (d.geomChanged()) {
            messages.report({0, 3}, "major-transport-geom-changed",
                            Message::Scope::GeomDiff);
        }
        if (namesChanged(d)) {
            messages.report({0, 3}, "major-transport-names-changed");
        }
    }
}

void checkTransportRailway(const DiffContext& diff, MessageReporter& messages)
{
    if (diff.categoryId() != cat::TRANSPORT_RAILWAY_STATION) {
        return;
    }

    if (!diff.oldObject()) {
        messages.report({0, 1}, "railway-station-created");
    } else if (!diff.newObject()) {
        messages.report({0, 1}, "railway-station-deleted");
    } else {
        if (diff.geomChanged()) {
            messages.report({0, 1}, "railway-station-geom-changed", Message::Scope::GeomDiff);
        }
        if (namesChanged(diff)) {
            messages.report({0, 1}, "railway-station-names-changed");
        }

        auto oldFtType = getFtType(*diff.oldObject());
        auto newFtType = getFtType(*diff.newObject());

        if (oldFtType != newFtType) {
            if (common::isIn(FtType::TransportRailwayTerminal, {newFtType, oldFtType})) {
                messages.report({0, 4}, "railway-station-type-changed");
            } else {
                messages.report({0, 2}, "railway-station-type-changed");
            }
        }
    }
}

void checkTransportTypeChange(const DiffContext& d, MessageReporter& messages)
{
    if (!(isTransport(d.categoryId())
          && d.oldObject() && d.newObject()
          && d.attrsChanged())) {
        return;
    }

    auto oldFtType = getFtType(*d.oldObject());
    auto newFtType = getFtType(*d.newObject());
    if (oldFtType == newFtType) {
        return;
    }

    if (isAny(d, isMajorFtType)) {
        messages.report({0, 3}, "major-transport-type-changed");
    }

    auto isTransportTerminalType = [](FtType ftType)
    {
        return common::isIn(
                ftType,
                {
                    FtType::TransportWaterwaySeaport,
                    FtType::TransportWaterwayRiverport,
                });
    };
    if (isTransportTerminalType(oldFtType) && !isTransportTerminalType(newFtType)) {
        messages.report({2, 1}, "transport-terminal-type-turn-off");
    }
}

void checkTransportAssignChange(const DiffContext& diff, MessageReporter& messages)
{
    if (!(isTransport(diff.categoryId()) && diff.newObject())) {
        return;
    }

    const Priority airportPriority{0, 3};
    const Priority metroPriority{1, 3};

    auto hasAssignRelation = [&diff](const Relations& relations) {
        for (const auto& relation: relations) {
            if (relation.slaveId == diff.objectId() && relation.role == role::ASSIGNED) {
                return true;
            }
        }
        return false;
    };

    const bool isAssignChanged = hasAssignRelation(diff.relationsAdded()) ||
        hasAssignRelation(diff.relationsDeleted());

    if (diff.categoryId() == cat::TRANSPORT_METRO_STATION && isAssignChanged) {
        messages.report(metroPriority, "transport-metro-station-assign-to-line-changed");
        return;
    }
    if ((diff.categoryId() == cat::TRANSPORT_METRO_EXIT) && isAssignChanged) {
        messages.report(metroPriority, "transport-metro-exit-assign-to-station-changed");
        return;
    }

    if (diff.categoryId() == cat::TRANSPORT_AIRPORT_TERMINAL && isAssignChanged) {
        messages.report(airportPriority, "transport-airport-terminal-assign-to-airport-changed");
        return;
    }
    if (diff.categoryId() == cat::TRANSPORT_AIRPORT) {
        if (hasRelationWithRole(diff.relationsAdded(), role::URBAN_AREAL_ASSIGNED) ||
                hasRelationWithRole(diff.relationsDeleted(), role::URBAN_AREAL_ASSIGNED))
        {
            messages.report(airportPriority, "transport-airport-assign-to-urban-areal-changed");
        }
    }
}

void checkMetroChange(const DiffContext& diff, MessageReporter& messages) {
    if (diff.categoryId() != cat::TRANSPORT_METRO_LINE) {
        return;
    }

    auto getPriority = [](const Object& object) {
        const bool low = common::isIn(
            getFtType(object), {
                FtType::TransportMetroCableLine,
                FtType::TransportMetroFunicularLine
            }
        );

        return low ? Priority{1, 2} : Priority{0, 2};
    };

    if (!diff.oldObject()) {
        messages.report(getPriority(*diff.newObject()), "transport-metro-line-created");
    } else if (!diff.newObject()) {
        messages.report(getPriority(*diff.oldObject()), "transport-metro-line-deleted");
    } else {
        auto hasOperatorRelation = [&diff](const Relations& relations) {
            for (const auto& relation: relations) {
                if (relation.slaveId == diff.objectId() && relation.role == role::ASSIGNED) {
                    return true;
                }
            }
            return false;
        };

        const Priority priority = std::min(
            getPriority(*diff.newObject()),
            getPriority(*diff.oldObject())
        );

        if (hasOperatorRelation(diff.relationsAdded()) ||
                hasOperatorRelation(diff.relationsDeleted()))
        {
            messages.report(priority, "transport-metro-line-operator-changed");
        }

        if (diff.geomChanged()) {
            messages.report(priority, "transport-metro-line-geom-changed", Message::Scope::GeomDiff);
        }
        if (diff.attrsChanged() || diff.tableAttrsChanged()) {
            messages.report(priority, "transport-metro-line-attrs-changed");
        }
    }
}

void checkAirfieldChange(const DiffContext& diff, MessageReporter& messages)
{
    if (diff.categoryId() != cat::TRANSPORT_AIRPORT) {
        return;
    }

    auto needReport = [](const Object& object) {
        return getDispClass(object) != DISP_CLASS_IGNORED
            && getFtType(object) == FtType::TransportAirportAirfield;
    };

    const Priority priority{0, 1};

    if (!diff.oldObject()) {
        if (needReport(*diff.newObject())) {
            messages.report(priority, "transport-airfield-created");
        }
    } else if (diff.newObject()) {
        if (!needReport(*diff.oldObject()) && !needReport(*diff.newObject())) {
            return;
        }
        if (diff.geomChanged()) {
            messages.report(priority, "transport-airfield-geom-changed", Message::Scope::GeomDiff);
        }
        if (diff.attrsChanged() || diff.tableAttrsChanged()) {
            messages.report(priority, "transport-airfield-attrs-changed");
        }
    }
}

} // namespace diffalert
} // namespace wiki
} // namespace maps
