#include "map/track_statistics.hpp"

#include "geometry/mercator.hpp"

#include "platform/distance.hpp"
#include "platform/duration.hpp"

namespace
{
double constexpr kInvalidTimestamp = std::numeric_limits<double>::lowest();
geometry::PointWithAltitude const kInvalidPoint = {m2::PointD::Zero(), geometry::kInvalidAltitude};
}  // namespace

TrackStatistics::TrackStatistics()
  : m_length(0)
  , m_duration(0)
  , m_ascent(0)
  , m_descent(0)
  , m_minElevation(geometry::kDefaultAltitudeMeters)
  , m_maxElevation(geometry::kDefaultAltitudeMeters)
  , m_previousPoint(kInvalidPoint)
  , m_previousTimestamp(kInvalidTimestamp)
{}

TrackStatistics::TrackStatistics(kml::MultiGeometry const & geometry) : TrackStatistics()
{
  for (auto const & line : geometry.m_lines)
    AddPoints(line);
  if (geometry.HasTimestamps())
  {
    for (size_t i = 0; i < geometry.m_timestamps.size(); ++i)
    {
      ASSERT(geometry.HasTimestampsFor(i), ());
      AddTimestamps(geometry.m_timestamps[i]);
    }
  }
}

void TrackStatistics::AddGpsInfoPoint(location::GpsInfo const & point)
{
  geometry::PointWithAltitude const pointWithAltitude(mercator::FromLatLon(point.m_latitude, point.m_longitude),
                                                      point.m_altitude);

  /// @todo point.HasAltitude() ?
  auto const altitude = geometry::Altitude(point.m_altitude);
  if (HasNoPoints())
  {
    m_minElevation = altitude;
    m_maxElevation = altitude;
    m_previousPoint = pointWithAltitude;
    m_previousTimestamp = point.m_timestamp;
    return;
  }

  m_minElevation = std::min(m_minElevation, altitude);
  m_maxElevation = std::max(m_maxElevation, altitude);

  auto const deltaAltitude = altitude - m_previousPoint.GetAltitude();
  if (deltaAltitude > 0)
    m_ascent += deltaAltitude;
  else
    m_descent -= deltaAltitude;
  m_length += mercator::DistanceOnEarth(m_previousPoint.GetPoint(), pointWithAltitude.GetPoint());
  m_duration += point.m_timestamp - m_previousTimestamp;

  m_previousPoint = pointWithAltitude;
  m_previousTimestamp = point.m_timestamp;
}

void TrackStatistics::AddPoints(Points const & points)
{
  if (points.empty())
    return;

  bool const hasNoPoints = HasNoPoints();
  auto const & firstPoint = points[0];
  auto const altitude = firstPoint.GetAltitude();

  m_minElevation = hasNoPoints ? altitude : std::min(m_minElevation, altitude);
  m_maxElevation = hasNoPoints ? altitude : std::max(m_maxElevation, altitude);
  m_previousPoint = firstPoint;

  for (size_t i = 1; i < points.size(); ++i)
  {
    auto const & point = points[i];
    auto const pointAltitude = point.GetAltitude();

    m_minElevation = std::min(m_minElevation, pointAltitude);
    m_maxElevation = std::max(m_maxElevation, pointAltitude);

    auto const deltaAltitude = pointAltitude - m_previousPoint.GetAltitude();
    if (deltaAltitude > 0)
      m_ascent += deltaAltitude;
    else
      m_descent -= deltaAltitude;
    m_length += mercator::DistanceOnEarth(m_previousPoint.GetPoint(), point.GetPoint());

    m_previousPoint = point;
  }
}

void TrackStatistics::AddTimestamps(Timestamps const & timestamps)
{
  if (timestamps.empty())
    return;
  m_duration += timestamps.back() - timestamps.front();
  m_previousTimestamp = timestamps.back();
}

bool TrackStatistics::HasNoPoints() const
{
  return m_previousPoint == kInvalidPoint;
}

std::string TrackStatistics::GetFormattedLength() const
{
  return platform::Distance::CreateFormatted(m_length).ToString();
}

std::string TrackStatistics::GetFormattedDuration() const
{
  return platform::Duration(static_cast<int>(m_duration)).GetPlatformLocalizedString();
}

std::string TrackStatistics::GetFormattedAscent() const
{
  return platform::Distance::FormatAltitude(m_ascent);
}

std::string TrackStatistics::GetFormattedDescent() const
{
  return platform::Distance::FormatAltitude(m_descent);
}

std::string TrackStatistics::GetFormattedMinElevation() const
{
  return platform::Distance::FormatAltitude(m_minElevation);
}

std::string TrackStatistics::GetFormattedMaxElevation() const
{
  return platform::Distance::FormatAltitude(m_maxElevation);
}
