#ifndef OSRM_EXTRACTOR_NODE_DATA_CONTAINER_HPP
#define OSRM_EXTRACTOR_NODE_DATA_CONTAINER_HPP

#include "extractor/class_data.hpp"
#include "extractor/travel_mode.hpp"

#include "storage/io_fwd.hpp"
#include "storage/shared_memory_ownership.hpp"

#include "util/permutation.hpp"
#include "util/typedefs.hpp"
#include "util/vector_view.hpp"

namespace osrm
{
namespace extractor
{
namespace detail
{
template <storage::Ownership Ownership> class EdgeBasedNodeDataContainerImpl;
}

namespace serialization
{
template <storage::Ownership Ownership>
void read(storage::io::FileReader &reader,
          detail::EdgeBasedNodeDataContainerImpl<Ownership> &ebn_data);

template <storage::Ownership Ownership>
void write(storage::io::FileWriter &writer,
           const detail::EdgeBasedNodeDataContainerImpl<Ownership> &ebn_data);
}

namespace detail
{
template <storage::Ownership Ownership> class EdgeBasedNodeDataContainerImpl
{
    template <typename T> using Vector = util::ViewOrVector<T, Ownership>;
    using TravelMode = extractor::TravelMode;

  public:
    EdgeBasedNodeDataContainerImpl() = default;

    EdgeBasedNodeDataContainerImpl(std::size_t size)
        : geometry_ids(size), name_ids(size), component_ids(size), travel_modes(size), classes(size)
    {
    }

    EdgeBasedNodeDataContainerImpl(Vector<GeometryID> geometry_ids,
                                   Vector<NameID> name_ids,
                                   Vector<ComponentID> component_ids,
                                   Vector<TravelMode> travel_modes,
                                   Vector<ClassData> classes)
        : geometry_ids(std::move(geometry_ids)), name_ids(std::move(name_ids)),
          component_ids(std::move(component_ids)), travel_modes(std::move(travel_modes)),
          classes(std::move(classes))
    {
    }

    GeometryID GetGeometryID(const NodeID node_id) const { return geometry_ids[node_id]; }

    TravelMode GetTravelMode(const NodeID node_id) const { return travel_modes[node_id]; }

    NameID GetNameID(const NodeID node_id) const { return name_ids[node_id]; }

    ComponentID GetComponentID(const NodeID node_id) const { return component_ids[node_id]; }

    ClassData GetClassData(const NodeID node_id) const { return classes[node_id]; }

    // Used by EdgeBasedGraphFactory to fill data structure
    template <typename = std::enable_if<Ownership == storage::Ownership::Container>>
    void SetData(NodeID node_id,
                 GeometryID geometry_id,
                 NameID name_id,
                 TravelMode travel_mode,
                 ClassData class_data)
    {
        geometry_ids[node_id] = geometry_id;
        name_ids[node_id] = name_id;
        travel_modes[node_id] = travel_mode;
        classes[node_id] = class_data;
    }

    // Used by EdgeBasedGraphFactory to fill data structure
    template <typename = std::enable_if<Ownership == storage::Ownership::Container>>
    void SetComponentID(NodeID node_id, ComponentID component_id)
    {
        component_ids[node_id] = component_id;
    }

    friend void serialization::read<Ownership>(storage::io::FileReader &reader,
                                               EdgeBasedNodeDataContainerImpl &ebn_data_container);
    friend void
    serialization::write<Ownership>(storage::io::FileWriter &writer,
                                    const EdgeBasedNodeDataContainerImpl &ebn_data_container);

    template <typename = std::enable_if<Ownership == storage::Ownership::Container>>
    void Renumber(const std::vector<std::uint32_t> &permutation)
    {
        util::inplacePermutation(geometry_ids.begin(), geometry_ids.end(), permutation);
        util::inplacePermutation(name_ids.begin(), name_ids.end(), permutation);
        util::inplacePermutation(component_ids.begin(), component_ids.end(), permutation);
        util::inplacePermutation(travel_modes.begin(), travel_modes.end(), permutation);
        util::inplacePermutation(classes.begin(), classes.end(), permutation);
    }

    // all containers have the exact same size
    std::size_t Size() const
    {
        BOOST_ASSERT(geometry_ids.size() == name_ids.size());
        BOOST_ASSERT(geometry_ids.size() == component_ids.size());
        BOOST_ASSERT(geometry_ids.size() == travel_modes.size());
        BOOST_ASSERT(geometry_ids.size() == classes.size());
        return geometry_ids.size();
    }

  private:
    Vector<GeometryID> geometry_ids;
    Vector<NameID> name_ids;
    Vector<ComponentID> component_ids;
    Vector<TravelMode> travel_modes;
    Vector<ClassData> classes;
};
}

using EdgeBasedNodeDataExternalContainer =
    detail::EdgeBasedNodeDataContainerImpl<storage::Ownership::External>;
using EdgeBasedNodeDataContainer =
    detail::EdgeBasedNodeDataContainerImpl<storage::Ownership::Container>;
using EdgeBasedNodeDataView = detail::EdgeBasedNodeDataContainerImpl<storage::Ownership::View>;
}
}

#endif