#ifndef OSRM_EXTRACTOR_CLASSIFICATION_DATA_HPP_
#define OSRM_EXTRACTOR_CLASSIFICATION_DATA_HPP_

#include <cmath>
#include <cstdint>
#include <string>
#include <unordered_map>

#include <osmium/osm.hpp>

namespace osrm
{
namespace extractor
{
namespace guidance
{

// Priorities are used to distinguish between how likely a turn is in comparison to a different
// road. The priorities here are used to distinguish between obvious turns (e.g. following a primary
// road next to a residential one is obvious). The decision what is obvious is described in the
// guidance constants.
namespace RoadPriorityClass
{
typedef std::uint8_t Enum;
// Top priority Road
const constexpr Enum MOTORWAY = 0;
// Second highest priority
const constexpr Enum TRUNK = 2;
// Main roads
const constexpr Enum PRIMARY = 4;
const constexpr Enum SECONDARY = 6;
const constexpr Enum TERTIARY = 8;
// Residential Categories
const constexpr Enum MAIN_RESIDENTIAL = 10;
const constexpr Enum SIDE_RESIDENTIAL = 11;
// Link Category
const constexpr Enum LINK_ROAD = 14;
// Bike Accessible
const constexpr Enum BIKE_PATH = 16;
// Walk Accessible
const constexpr Enum FOOT_PATH = 18;
// Link types are usually not considered in forks, unless amongst each other.
// a road simply offered for connectivity. Will be ignored in forks/other decisions. Always
// considered non-obvious to continue on
const constexpr Enum CONNECTIVITY = 31;
} // namespace Road Class

#pragma pack(push, 1)
class RoadClassification
{
    // a class that behaves like a motorway (separated directions)
    std::uint8_t motorway_class : 1;
    // all types of link classes
    std::uint8_t link_class : 1;
    // a low priority class is a pure connectivity way. It can be ignored in multiple decisions
    // (e.g. fork on a primary vs service will not happen)
    std::uint8_t may_be_ignored : 1;
    // the road priority is used as an indicator for forks. If the roads are of similar priority
    // (difference <=1), we can see the road as a fork. Else one of the road classes is seen as
    // obvious choice
    RoadPriorityClass::Enum road_priority_class : 5;
    // the number of lanes in the road
    std::uint8_t number_of_lanes;

  public:
    // default construction
    RoadClassification()
        : motorway_class(0), link_class(0), may_be_ignored(1),
          road_priority_class(RoadPriorityClass::CONNECTIVITY), number_of_lanes(0)
    {
    }

    RoadClassification(bool motorway_class,
                       bool link_class,
                       bool may_be_ignored,
                       RoadPriorityClass::Enum road_priority_class,
                       std::uint8_t number_of_lanes)
        : motorway_class(motorway_class), link_class(link_class), may_be_ignored(may_be_ignored),
          road_priority_class(road_priority_class), number_of_lanes(number_of_lanes)
    {
    }

    bool IsMotorwayClass() const { return (0 != motorway_class) && (0 == link_class); }
    void SetMotorwayFlag(const bool new_value) { motorway_class = new_value; }

    bool IsRampClass() const { return (0 != motorway_class) && (0 != link_class); }

    bool IsLinkClass() const { return (0 != link_class); }
    void SetLinkClass(const bool new_value) { link_class = new_value; }

    bool IsLowPriorityRoadClass() const { return (0 != may_be_ignored); }
    void SetLowPriorityFlag(const bool new_value) { may_be_ignored = new_value; }

    std::uint8_t GetNumberOfLanes() const { return number_of_lanes; }
    void SetNumberOfLanes(const std::uint8_t new_value) { number_of_lanes = new_value; }

    std::uint32_t GetPriority() const { return static_cast<std::uint32_t>(road_priority_class); }

    RoadPriorityClass::Enum GetClass() const { return road_priority_class; }
    void SetClass(const RoadPriorityClass::Enum new_value) { road_priority_class = new_value; }

    bool operator==(const RoadClassification &other) const
    {
        return motorway_class == other.motorway_class && link_class == other.link_class &&
               may_be_ignored == other.may_be_ignored &&
               road_priority_class == other.road_priority_class;
    }

    bool operator!=(const RoadClassification &other) const { return !(*this == other); }

    std::string ToString() const
    {
        return std::string() + (motorway_class ? "motorway" : "normal") +
               (link_class ? "_link" : "") + (may_be_ignored ? " ignorable " : " important ") +
               std::to_string(road_priority_class);
    }
};
#pragma pack(pop)

static_assert(
    sizeof(RoadClassification) == 2,
    "Road Classification should fit two bytes. Increasing this has a severe impact on memory.");

inline bool canBeSeenAsFork(const RoadClassification first, const RoadClassification second)
{
    return std::abs(static_cast<int>(first.GetPriority()) -
                    static_cast<int>(second.GetPriority())) <= 1;
}
} // namespace guidance
} // namespace extractor
} // namespace osrm

#endif // OSRM_EXTRACTOR_CLASSIFICATION_DATA_HPP_
