#pragma once

#include <maps/wikimap/mapspro/services/mrc/libs/object/include/object.h>

#include <maps/libs/ymapsdf/include/rd.h>

#include <maps/libs/geolib/include/polyline.h>
#include <yandex/maps/wiki/common/rd/lane.h>

#include <vector>

namespace maps::mrc::object {

enum class ForTrucks {
    No = 0,
    Yes
};

class RoadElement: public ObjectWithGeom<geolib3::Polyline2> {

public:
    static constexpr int NO_SPEED_LIMIT = -1;

    using Direction = ymapsdf::rd::Direction;
    using AccessId = ymapsdf::rd::AccessId;
    using FunctionalClass = ymapsdf::rd::FunctionalClass;
    using FormOfWay = ymapsdf::rd::FormOfWay;
    using Lane = wiki::common::Lane;
    using Lanes = wiki::common::Lanes;

    using ObjectWithGeom::ObjectWithGeom;

    TId startJunctionId() const { return startJunctionId_; }
    TId endJunctionId() const { return endJunctionId_; }

    Direction direction() const { return direction_; }
    AccessId accessId() const { return accessId_; }
    FunctionalClass fc() const { return fc_; }
    FormOfWay fow() const { return fow_; }
    int startZLevel() const { return startZLevel_; }
    int endZLevel() const { return endZLevel_; }
    int speedLimit() const { return speedLimit_; }
    int speedLimitF() const { return speedLimitF_; }
    int speedLimitT() const { return speedLimitT_; }
    int speedLimitTruckF() const { return speedLimitTruckF_; }
    int speedLimitTruckT() const { return speedLimitTruckT_; }
    int speedLimit(ForTrucks forTruck, Direction direction) const {
        int speedLimit = RoadElement::NO_SPEED_LIMIT;
        if (ForTrucks::Yes == forTruck) {
            if (direction == ymapsdf::rd::Direction::Forward) {
                speedLimit = speedLimitTruckF_;
            } else {
                speedLimit = speedLimitTruckT_;
            }
        }
        if (RoadElement::NO_SPEED_LIMIT == speedLimit) {
            if (direction == ymapsdf::rd::Direction::Forward) {
                speedLimit = speedLimitF_;
            } else {
                speedLimit = speedLimitT_;
            }
        }
        if (RoadElement::NO_SPEED_LIMIT == speedLimit) {
            speedLimit = speedLimit_;
        }
        return speedLimit;
    }

    bool backTransportLane() const { return backTransportLane_; }
    bool underConstruction() const { return underConstruction_; }

    bool isAccessibleToVehicles() const { return !!(accessId_ & AccessId::Vehicles); }
    bool isAccessibleToCars() const { return !!(accessId_ & AccessId::Car); }

    const Lanes& fLanes() const { return fLanes_; }
    const Lanes& tLanes() const { return tLanes_; }

    const Lanes& lanes(Direction direction) const;

    const TIds& trafficLightIds() const { return trafficLightIds_; }

    const TIds& speedBumpIds() const { return speedBumpIds_; }

    RoadElement& startJunctionId(TId value)
    {
        startJunctionId_ = value;
        return *this;
    }

    RoadElement& endJunctionId(TId value)
    {
        endJunctionId_ = value;
        return *this;
    }

    RoadElement& direction(Direction value)
    {
        direction_ = value;
        return *this;
    }

    RoadElement& accessId(AccessId value)
    {
        accessId_ = value;
        return *this;
    }

    RoadElement& fc(FunctionalClass value)
    {
        fc_ = value;
        return *this;
    }

    RoadElement& fow(FormOfWay value)
    {
        fow_ = value;
        return *this;
    }

    RoadElement& startZLevel(int value)
    {
        startZLevel_ = value;
        return *this;
    }

    RoadElement& endZLevel(int value)
    {
        endZLevel_ = value;
        return *this;
    }

    RoadElement& speedLimit(int value)
    {
        speedLimit_ = value;
        return *this;
    }

    RoadElement& speedLimitF(int value)
    {
        speedLimitF_ = value;
        return *this;
    }

    RoadElement& speedLimitT(int value)
    {
        speedLimitT_ = value;
        return *this;
    }

    RoadElement& speedLimitTruckF(int value)
    {
        speedLimitTruckF_ = value;
        return *this;
    }

    RoadElement& speedLimitTruckT(int value)
    {
        speedLimitTruckT_ = value;
        return *this;
    }

    RoadElement& backTransportLane(bool value)
    {
        backTransportLane_ = value;
        return *this;
    }

    RoadElement& underConstruction(bool value)
    {
        underConstruction_ = value;
        return *this;
    }

    RoadElement& fLanes(Lanes value)
    {
        fLanes_ = std::move(value);
        return *this;
    }

    RoadElement& tLanes(Lanes value)
    {
        tLanes_ = std::move(value);
        return *this;
    }

    RoadElement& trafficLightIds(TIds value)
    {
        trafficLightIds_ = std::move(value);
        return *this;
    }

    RoadElement& speedBumpIds(TIds value)
    {
        speedBumpIds_ = std::move(value);
        return *this;
    }

    TId oppositeJunction(TId junctionId) const;

    bool hasJunction(TId junctionId) const;

    int zLevel(TId junctionId) const;

    Direction directionTo(TId junctionId) const;

    // In meters
    double length() const;
    double fastLength() const;

private:
    TId startJunctionId_{NO_OBJECT_ID};
    TId endJunctionId_{NO_OBJECT_ID};

    Direction direction_{};
    AccessId accessId_{};
    FunctionalClass fc_{};
    FormOfWay fow_{};
    int startZLevel_{};
    int endZLevel_{};
    int speedLimit_{NO_SPEED_LIMIT};
    int speedLimitF_{NO_SPEED_LIMIT};
    int speedLimitT_{NO_SPEED_LIMIT};
    int speedLimitTruckF_{NO_SPEED_LIMIT};
    int speedLimitTruckT_{NO_SPEED_LIMIT};
    bool backTransportLane_{};
    bool underConstruction_{};

    /// @see https://doc.yandex-team.ru/ymaps/ymapsdf/ymapsdf-ref/concepts/road_lane.html
    Lanes fLanes_{}; // when moving against the direction of digitization
    Lanes tLanes_{}; // when moving in the direction of digitization

    TIds trafficLightIds_{};
    TIds speedBumpIds_{};
};

using RoadElements = std::vector<RoadElement>;


class RoadJunction: public ObjectWithGeom<geolib3::Point2> {

public:
    using ObjectWithGeom::ObjectWithGeom;

    const TIds& conditionIds() const { return conditionIds_; }
    const TIds& elementIds() const { return elementIds_; }
    TId trafficLightId() const { return trafficLightId_; }
    TId speedBumpId() const { return speedBumpId_; }

    RoadJunction& conditionIds(TIds value)
    {
        conditionIds_ = std::move(value);
        return *this;
    }

    RoadJunction& elementIds(TIds value)
    {
        elementIds_ = std::move(value);
        return *this;
    }

    RoadJunction& trafficLightId(TId value)
    {
        trafficLightId_ = value;
        return *this;
    }

    RoadJunction& speedBumpId(TId value)
    {
        speedBumpId_ = value;
        return *this;
    }

private:
    TIds conditionIds_{};
    TIds elementIds_{};
    TId trafficLightId_{NO_OBJECT_ID};
    TId speedBumpId_{NO_OBJECT_ID};
};

using RoadJunctions = std::vector<RoadJunction>;

} // namespace maps::mrc::object
