#include <yandex/maps/wiki/common/rd/lane.h>
#include <maps/libs/json/include/value.h>

#include <util/system/compiler.h>
#include <boost/algorithm/string.hpp>

namespace maps::wiki::common {

namespace {

const std::string DIR_CODE_LEFT180 = "L180";
const std::string DIR_CODE_LEFT135 = "L135";
const std::string DIR_CODE_LEFT90 = "L90";
const std::string DIR_CODE_LEFT45 = "L45";
const std::string DIR_CODE_STRAIGHT_AHEAD = "S";
const std::string DIR_CODE_RIGHT45 = "R45";
const std::string DIR_CODE_RIGHT90 = "R90";
const std::string DIR_CODE_RIGHT135 = "R135";
const std::string DIR_CODE_RIGHT180 = "R180";
const std::string DIR_CODE_LEFT_FROM_RIGHT = "LR";
const std::string DIR_CODE_RIGHT_FROM_LEFT = "RL";
const std::string DIR_CODE_LEFT_SHIFT = "SL";
const std::string DIR_CODE_RIGHT_SHIFT = "SR";
const std::string DIR_CODE_NONE = "N";

const std::map<std::string, LaneDirection> DIR_CODE_TO_DIRECTION {
    {DIR_CODE_LEFT180, LaneDirection::Left180},
    {DIR_CODE_LEFT135, LaneDirection::Left135},
    {DIR_CODE_LEFT90, LaneDirection::Left90},
    {DIR_CODE_LEFT45, LaneDirection::Left45},
    {DIR_CODE_STRAIGHT_AHEAD, LaneDirection::StraightAhead},
    {DIR_CODE_RIGHT45, LaneDirection::Right45},
    {DIR_CODE_RIGHT90, LaneDirection::Right90},
    {DIR_CODE_RIGHT135, LaneDirection::Right135},
    {DIR_CODE_RIGHT180, LaneDirection::Right180},
    {DIR_CODE_LEFT_FROM_RIGHT, LaneDirection::LeftFromRight},
    {DIR_CODE_RIGHT_FROM_LEFT, LaneDirection::RightFromLeft},
    {DIR_CODE_LEFT_SHIFT, LaneDirection::LeftShift},
    {DIR_CODE_RIGHT_SHIFT, LaneDirection::RightShift},
    {DIR_CODE_NONE, LaneDirection::None}
};

const std::map<LaneDirection, std::string> DIRECTION_TO_CODE {
    {LaneDirection::Left180, DIR_CODE_LEFT180},
    {LaneDirection::Left135, DIR_CODE_LEFT135},
    {LaneDirection::Left90, DIR_CODE_LEFT90},
    {LaneDirection::Left45, DIR_CODE_LEFT45},
    {LaneDirection::StraightAhead, DIR_CODE_STRAIGHT_AHEAD},
    {LaneDirection::Right45, DIR_CODE_RIGHT45},
    {LaneDirection::Right90, DIR_CODE_RIGHT90},
    {LaneDirection::Right135, DIR_CODE_RIGHT135},
    {LaneDirection::Right180, DIR_CODE_RIGHT180},
    {LaneDirection::LeftFromRight, DIR_CODE_LEFT_FROM_RIGHT},
    {LaneDirection::RightFromLeft, DIR_CODE_RIGHT_FROM_LEFT},
    {LaneDirection::LeftShift, DIR_CODE_LEFT_SHIFT},
    {LaneDirection::RightShift, DIR_CODE_RIGHT_SHIFT},
    {LaneDirection::None, DIR_CODE_NONE}
};

const std::map<LaneKind, std::string> KIND_TO_STRING {
    {LaneKind::Auto, "auto"},
    {LaneKind::Bus, "bus"},
    {LaneKind::Tram, "tram"},
    {LaneKind::Bike, "bike"}
};

const std::map<std::string, LaneKind> STRING_TO_KIND {
    {"auto", LaneKind::Auto},
    {"bus", LaneKind::Bus},
    {"tram", LaneKind::Tram},
    {"bike", LaneKind::Bike}
};

} // namespace

#define REQUIRE_LANES(X, Y) \
    if (Y_LIKELY(X)) {} else \
        throw LanesException() << Y; // NOLINT


Lane::Lane(size_t directionsId, LaneKind kind)
    : directions_(directionsFromId(directionsId))
    , kind_(kind)
{
}

bool
Lane::contains(const LaneDirection& direction) const
{
    return
        std::find(directions_.begin(), directions_.end(), direction)
        != directions_.end();
}

const std::vector<LaneDirection>& Lane::directions() const { return directions_; }

LaneKind Lane::kind() const {  return kind_; }

Lane::Lane(const std::string& laneString)
{
    std::vector<std::string> directionsAndKind;
    boost::split(directionsAndKind, laneString, boost::is_any_of(STR_LANE_KIND_DELIM));

    REQUIRE_LANES(directionsAndKind.size() == 2,
        "Badly formated lane attribute " << laneString);

    kind_ = laneKindFromString(directionsAndKind[1]);

    std::vector<std::string> directions;
    boost::split(directions, directionsAndKind[0], boost::is_any_of(STR_LANE_DIRECTIONS_DELIM));

    for (const auto& dir : directions) {
        REQUIRE_LANES(dir != DIR_CODE_NONE || directions.size() == 1,
            "Direction code N can't be used with others");
        directions_.push_back(laneDirectionFromString(dir));
    }
}

std::string
Lane::toString() const
{
    std::string ret;
    for (size_t dirNum  = 0; dirNum < directions_.size(); ++dirNum) {
        if (dirNum) {
            ret += STR_LANE_DIRECTIONS_DELIM;
        }
        ret += common::toString(directions_[dirNum]);
    }
    ret += STR_LANE_KIND_DELIM;
    ret += common::toString(kind_);
    return ret;
}

size_t
Lane::directionsId() const
{
    size_t id = 0;
    for (const auto dir : directions_) {
        id |= static_cast<size_t>(dir);
    }
    return id;
}

Lanes
lanesFromString(const std::string& laneAttrValue)
{
    Lanes lanes;

    if (laneAttrValue.empty()) {
        return lanes;
    }

    std::vector<std::string> lanesStrings;
    boost::split(lanesStrings, laneAttrValue, boost::is_any_of(STR_LANE_DELIM));
    for (const auto& laneString : lanesStrings) {
        lanes.emplace_back(laneString);
    }
    return lanes;
}

std::string
toString(const LaneDirection& direction)
{
    auto it = DIRECTION_TO_CODE.find(direction);
    ASSERT(it != DIRECTION_TO_CODE.end());
    return it->second;
}

LaneDirection
laneDirectionFromString(const std::string& directionStr)
{
    auto it = DIR_CODE_TO_DIRECTION.find(directionStr);
    REQUIRE_LANES(it != DIR_CODE_TO_DIRECTION.end(),
        "Lane direction code: " << directionStr << " unknown");
    return it->second;
}

LaneKind
laneKindFromString(const std::string& kindString)
{
    auto it =  STRING_TO_KIND.find(kindString);
    REQUIRE_LANES(it != STRING_TO_KIND.end(),
        "Uknown kind string:" << kindString);
    return it->second;
}

std::string
toString(const LaneKind& kind)
{
    auto it = KIND_TO_STRING.find(kind);
    ASSERT(it != KIND_TO_STRING.end());
    return it->second;
}

std::string
toString(const Lanes& lanes)
{
    std::string ret;
    for (const auto& lane : lanes) {
        if (!ret.empty()) {
            ret += STR_LANE_DELIM;
        }
        ret += lane.toString();
    }
    return ret;
}

std::vector<LaneDirection>
directionsFromId(size_t directionsId)
{
    if (!directionsId) {
        return {LaneDirection::None};
    }
    std::vector<LaneDirection> ret;
    for (size_t i = 1;  i <= static_cast<size_t>(LaneDirection::Max); i <<= 1) {
        if (directionsId & i) {
            ret.push_back(static_cast<LaneDirection>(i));
        }
    }
    return ret;
}

} // namespace maps::wiki::common
