#include "route.h"

#include <maps/libs/geolib/include/serialization.h>
#include <maps/libs/geolib/include/distance.h>

#include <boost/algorithm/string.hpp>

#include <sstream>
#include <iostream>
#include <math.h>

namespace maps {
namespace wiki {
namespace ft_fix {

bool
RouteDescriptor::operator < (const RouteDescriptor& r) const
{
    return type < r.type
        || (type == r.type && name < r.name);
}

bool
RouteDescriptor::operator == (const RouteDescriptor& r) const
{
    return type == r.type && name == r.name;
}

bool
Route::operator < (const Route& r) const
{
    return descr < r.descr
        || (descr == r.descr && stopIds.size() < r.stopIds.size())
        || (descr == r.descr && stopIds.size() == r.stopIds.size()
            && stopIds < r.stopIds);
}

RoutesData::RoutesData(std::ifstream& ifs)
{
    std::string line;
    while (std::getline(ifs, line)) {
        std::vector<std::string> tokens;
        boost::split(tokens, line, boost::is_any_of("\t"));

        auto stopId = std::stod(tokens[0]);
        auto shape = geolib3::WKT::read<geolib3::Point2>(tokens[1]);

        stops[stopId] = std::make_shared<Stop>(stopId, shape);

        parseRoutes(stopId, RouteType::Bus, tokens[2]);
        parseRoutes(stopId, RouteType::Trolley, tokens[3]);
        parseRoutes(stopId, RouteType::Tram, tokens[4]);
        parseRoutes(stopId, RouteType::Minibus, tokens[5]);
    }
}

void
RoutesData::parseRoutes(StopId id, RouteType type, const std::string& line)
{
    if (line.empty()) {
        return;
    }
    std::vector<std::string> names;
    boost::split(names, line, boost::is_any_of("|"));
    for(auto name: names) {
        boost::trim(name);
        if (name.empty()) {
            std::cout << "EMPTY NAME at stopid=" << id << std::endl;
            continue;
        }
        RouteDescriptor rd = {type, name};
        stopsByRoute[rd].push_back(id);
    }
}

Routes
RoutesData::clusterizeRoute(const RouteDescriptor& routeDescr, StopIds stopIds, double thresholdMeters) const
{
    Routes routes;

    while (!stopIds.empty()) {
        StopIds selected;
        std::set<StopId> processed;
        selected.push_back(stopIds.front());
        stopIds.pop_front();

        while (!selected.empty()) {
            auto curStop = selected.front();
            selected.pop_front();

            auto newStops = collectNeighbors(curStop, stopIds, thresholdMeters);
            selected.splice(selected.end(), newStops);

            processed.insert(curStop);
        }

        StopIds routeStops(processed.begin(), processed.end());
        Route route;
        route.descr = routeDescr;
        route.stopIds = std::move(routeStops);
        routes.insert(route);
    }
    return routes;
}

Routes
RoutesData::clusterize(double thresholdMeters) const
{
    Routes routes;
    for (auto& kv: stopsByRoute) {
        Routes curRoutes = clusterizeRoute(kv.first, kv.second, thresholdMeters);
        routes.insert(curRoutes.begin(), curRoutes.end());
    }
    return routes;
}

StopIds
RoutesData::collectNeighbors(StopId curStop, StopIds& rStops, double thresholdMeters) const
{
    StopIds collected;
    auto curPoint = stops.at(curStop)->shape;

    for(auto it = rStops.begin(); it != rStops.end(); ) {
        if(isNeighbors(curPoint, stops.at(*it)->shape, thresholdMeters)) {
            collected.push_back(*it);
            it = rStops.erase(it);
        } else {
            ++it;
        }
    }

    return collected;
}

namespace {
double distance(
    const geolib3::Point2& point1,
    const geolib3::Point2& point2,
    double thresholdMeters)
{
    static const double northestTownLatitude = 78.2; // Longyearbyen, Norway. Population of 2075
    static const double equatorLength = 40075696;
    static const double pi = std::atan(1)*4;
    static const double maxMetersInGradus = equatorLength / 360.0 * std::cos(northestTownLatitude * pi / 180.0);

    const double thresholdGradus = thresholdMeters * maxMetersInGradus;
    if (geolib3::distance(point1, point2) > thresholdGradus) {
        return thresholdMeters;
    }

    auto dist =  geolib3::fastGeoDistance(point1, point2);
    return (dist > thresholdMeters)
        ? thresholdMeters
        : dist;
}

} // namespace

bool
RoutesData::isNeighbors(
    const geolib3::Point2& point1,
    const geolib3::Point2& point2,
    double thresholdMeters) const
{
    return distance(point1, point2, thresholdMeters) < thresholdMeters;
}

} // namespace ft_fix
} // namespace wiki
} // namespace maps
