#pragma once

#include "transit/transit_schedule.hpp"

#include "geometry/point2d.hpp"

#include "base/macros.hpp"
#include "base/newtype.hpp"
#include "base/visitor.hpp"

#include "std/boost_container_hash.hpp"

#include "defines.hpp"

#include <cstdint>
#include <limits>
#include <string>
#include <tuple>
#include <unordered_map>
#include <unordered_set>
#include <vector>

#include "3party/opening_hours/opening_hours.hpp"

namespace routing
{
inline double constexpr kTransitMaxSpeedKMpH = 400.0;
}  // namespace routing

namespace transit
{
// File names for saving resulting data exported from GTFS.
inline std::string const kTransitFileExtension = std::string(TRANSIT_FILE_EXTENSION);
inline std::string const kNetworksFile = "networks" + kTransitFileExtension;
inline std::string const kRoutesFile = "routes" + kTransitFileExtension;
inline std::string const kLinesFile = "lines" + kTransitFileExtension;
inline std::string const kLinesMetadataFile = "lines_metadata" + kTransitFileExtension;
inline std::string const kShapesFile = "shapes" + kTransitFileExtension;
inline std::string const kStopsFile = "stops" + kTransitFileExtension;
inline std::string const kEdgesFile = "edges" + kTransitFileExtension;
inline std::string const kEdgesTransferFile = "edges_transfer" + kTransitFileExtension;
inline std::string const kTransfersFile = "transfers" + kTransitFileExtension;
inline std::string const kGatesFile = "gates" + kTransitFileExtension;

// Route types shown on the subway layer.
inline std::unordered_set<std::string> const kSubwayLayerTypes{"subway", "train", "light_rail", "monorail"};

// Unique id for transit entities. It is generated by gtfs_converter and is persistent between
// re-runs. Generated based on the unique string hash of the GTFS entity. Lies in the interval
// |routing::FakeFeatureIds::IsTransitFeature()|. If the GTFS entity is renamed or the new GTFS
// feed is added the new id is generated by |IdGenerator::MakeId()|.
using TransitId = uint32_t;
inline TransitId constexpr kInvalidTransitId = std::numeric_limits<TransitId>::max();

// Mapping of language id to text. Language id exists in |StringUtf8Multilang::kLanguages|.
using Translations = std::unordered_map<std::string, std::string>;

struct TimeFromGateToStop
{
  TimeFromGateToStop() = default;
  TimeFromGateToStop(TransitId stopId, uint32_t timeSeconds) : m_stopId(stopId), m_timeSeconds(timeSeconds) {}
  bool operator==(TimeFromGateToStop const & rhs) const
  {
    return std::tie(m_stopId, m_timeSeconds) == std::tie(rhs.m_stopId, rhs.m_timeSeconds);
  }

  DECLARE_VISITOR_AND_DEBUG_PRINT(TimeFromGateToStop, visitor(m_stopId, "stopId"),
                                  visitor(m_timeSeconds, "timeSeconds"))

  TransitId m_stopId = kInvalidTransitId;
  uint32_t m_timeSeconds = 0;
};

using IdList = std::vector<TransitId>;
using IdSet = std::unordered_set<TransitId>;
using TimeTable = std::unordered_map<TransitId, std::vector<TimeInterval>>;
using EdgeWeight = uint32_t;

// Link to the shape: shape id and indexes in the corresponding polyline.
struct ShapeLink
{
  ShapeLink() = default;
  ShapeLink(TransitId id, uint32_t startIndex, uint32_t endIndex)
    : m_shapeId(id)
    , m_startIndex(startIndex)
    , m_endIndex(endIndex)
  {}

  bool operator==(ShapeLink const & rhs) const
  {
    return std::tie(m_shapeId, m_startIndex, m_endIndex) == std::tie(rhs.m_shapeId, rhs.m_startIndex, rhs.m_endIndex);
  }

  bool operator<(ShapeLink const & rhs) const
  {
    return std::tie(m_shapeId, m_startIndex, m_endIndex) < std::tie(rhs.m_shapeId, rhs.m_startIndex, rhs.m_endIndex);
  }

  DECLARE_VISITOR_AND_DEBUG_PRINT(ShapeLink, visitor(m_shapeId, "id"), visitor(m_startIndex, "startIndex"),
                                  visitor(m_endIndex, "endIndex"))

  TransitId m_shapeId = kInvalidTransitId;
  uint32_t m_startIndex = 0;
  uint32_t m_endIndex = 0;
};

struct EdgeId
{
  EdgeId() = default;
  EdgeId(TransitId fromStopId, TransitId toStopId, TransitId lineId)
    : m_fromStopId(fromStopId)
    , m_toStopId(toStopId)
    , m_lineId(lineId)
  {}

  bool operator==(EdgeId const & other) const
  {
    return std::tie(m_fromStopId, m_toStopId, m_lineId) ==
           std::tie(other.m_fromStopId, other.m_toStopId, other.m_lineId);
  }

  TransitId m_fromStopId = 0;
  TransitId m_toStopId = 0;
  TransitId m_lineId = 0;
};

struct EdgeIdHasher
{
  size_t operator()(EdgeId const & key) const
  {
    size_t seed = 0;
    boost::hash_combine(seed, key.m_fromStopId);
    boost::hash_combine(seed, key.m_toStopId);
    boost::hash_combine(seed, key.m_lineId);
    return seed;
  }
};

using IdEdgeSet = std::unordered_set<EdgeId, EdgeIdHasher>;

struct EdgeData
{
  EdgeData() = default;
  EdgeData(ShapeLink const & shapeLink, EdgeWeight const & weight) : m_shapeLink(shapeLink), m_weight(weight) {}

  explicit EdgeData(EdgeWeight const & weight) : m_weight(weight) {}

  ShapeLink m_shapeLink;
  EdgeWeight m_weight = 0;

  // Feature id for cross-mwm transit section. It is used in Segment class as a feature id for
  // transit routing case.
  uint32_t m_featureId = std::numeric_limits<uint32_t>::max();
};

struct LineSegment
{
  LineSegment() = default;
  explicit LineSegment(uint32_t startIdx) : m_startIdx(startIdx) {}
  LineSegment(uint32_t startIdx, uint32_t endIdx) : m_startIdx(startIdx), m_endIdx(endIdx) {}

  bool operator==(LineSegment const & rhs) const { return m_startIdx == rhs.m_startIdx && m_endIdx == rhs.m_endIdx; }

  DECLARE_VISITOR_AND_DEBUG_PRINT(LineSegment, visitor(m_startIdx, "startIdx"), visitor(m_endIdx, "endIdx"))

  uint32_t m_startIdx = 0;
  uint32_t m_endIdx = 0;
};

using LineSegments = std::vector<LineSegment>;

struct LineSegmentOrder
{
  LineSegmentOrder() = default;
  LineSegmentOrder(LineSegment const & lineSegment, int order) : m_segment(lineSegment), m_order(order) {}

  bool operator==(LineSegmentOrder const & rhs) const { return m_order == rhs.m_order && m_segment == rhs.m_segment; }

  DECLARE_VISITOR_AND_DEBUG_PRINT(LineSegmentOrder, visitor(m_segment, "segment"), visitor(m_order, "order"))

  LineSegment m_segment;
  int m_order = 0;
};

using LineSegmentsOrder = std::vector<LineSegmentOrder>;

template <class T, class I>
typename std::vector<T>::const_iterator FindById(std::vector<T> const & container, I const & id, bool exists = true)
{
  auto const it = std::find_if(container.begin(), container.end(), [id](T const & obj) { return obj.GetId() == id; });

  if (exists)
    CHECK(it != container.end(), (id));
  return it;
}

inline std::vector<m2::PointD> GetPolylinePart(std::vector<m2::PointD> const & polyline, size_t startIdx, size_t endIdx)
{
  CHECK_GREATER(endIdx, startIdx, ());
  CHECK_GREATER(polyline.size(), endIdx, ());

  return std::vector<m2::PointD>(polyline.begin() + startIdx, polyline.begin() + endIdx + 1);
}
}  // namespace transit
