#include "route_generator.h"

#include "path_search.h"
#include "split_path.h"

namespace maps::mrc::gen_targets {

namespace {
const double BIG_TASK_LENGTH = 10000; // meters
}

// the order of the possible turns
bool PossibleTurn::operator>(const PossibleTurn& other) const
{
    // while the edgeAfterTurn (edge C) have unvisited target
    // inputs, we can ignore that possible turn, because we can
    // visit that edge from another edge. Otherwise we should
    // apply possible turn, because we can't reach edge C from
    // another edges.

    if (fabs(overheadRatio - other.overheadRatio) > 0.00001) {
        return overheadRatio > other.overheadRatio;
    }

    if (unvisitedInputs == 0 && other.unvisitedInputs != 0) {
        return false;
    }
    if (other.unvisitedInputs == 0 && unvisitedInputs != 0) {
        return true;
    }

    if (fabs(unrecommendedManeuverPenaltyChange
             - other.unrecommendedManeuverPenaltyChange)
        > 0.1) {
        return unrecommendedManeuverPenaltyChange
            > other.unrecommendedManeuverPenaltyChange;
    }


    // trying to not applying possible turns on big loop. We will
    // use loops junctions to split route into small tasks. It is
    // hard to do on junctions with big loops
    if (loopLength > BIG_TASK_LENGTH && other.loopLength < BIG_TASK_LENGTH) {
        return true;
    }
    if (loopLength < BIG_TASK_LENGTH && other.loopLength > BIG_TASK_LENGTH) {
        return false;
    }

    // E.g. if maneuver A->B is a U turn and maneuver A-C is a
    // straight line, the possible turn is good
    // if maneuver A->B is a straight line and maneuver A-C is a
    // U turn, the possible turn is not good
    return maneuverPenaltyChange > other.maneuverPenaltyChange;
}

RouteGenerator::RouteGenerator(const RoadNetworkData& roadNetwork,
                               EdgeId initEdge)
    : roadNetwork_(roadNetwork)
    , nextFreeLoopId_(1)
    , targetsLength_(0)
    , generatedLength_(0)
{
    initialize();
    createInitLoop(initEdge);
    generate();
}

std::list<LoopEdge>::iterator RouteGenerator::nextEdgeInLoop(std::list<LoopEdge>::iterator loopIt)
{
    auto next = std::next(loopIt);
    if (next == loop_.end()) {
        return loop_.begin();
    } else {
        return next;
    }
}

void RouteGenerator::initialize()
{
    for (auto edge : roadNetwork_.edges()) {
        if (edge.second.isTarget) {
            targetsLength_ += edge.second.length;
            int unvisitedInputs = 0;
            for (EdgeId inEdgeId : edge.second.inEdges) {
                if (roadNetwork_.edge(inEdgeId).isTarget) {
                    unvisitedInputs++;
                }
            }
            unvisitedEdgeInputs_[edge.first] = unvisitedInputs;
        }
    }
}

void RouteGenerator::createInitLoop(EdgeId initEdgeId)
{
    PathSearch search(roadNetwork_, initEdgeId,
                      std::unordered_set<EdgeId>{initEdgeId},
                      std::unordered_set<EdgeId>{});
    std::vector<EdgeId> loopPath = search.getResult();

    LoopId loopId = nextFreeLoopId_++;
    for (EdgeId pathEdge : loopPath) {
        loop_.push_back(LoopEdge{pathEdge, loopId});
    }

    loopLength_[loopId] = getPathLength(roadNetwork_, loopPath);

    for (auto loopIt = loop_.begin(); loopIt != loop_.end(); ++loopIt) {
        handleNewEdgeInLoop(loopIt);
    }
}

void RouteGenerator::generate()
{
    while (queue_.size()) {
        PossibleTurn possibleTurn = queue_.top();
        queue_.pop();
        if (possibleTurnIsActual(possibleTurn)) {
            applyPossibleTurn(possibleTurn);
        }
    }
}

// after creating the possibleTurn current loop may be changed and
// "edge C" of the possible turn may be already visited
bool RouteGenerator::possibleTurnIsActual(const PossibleTurn& possibleTurn)
{
    if (visitedEdges_.count(possibleTurn.edgeAfterTurn)
        || possibleTurn.unvisitedInputs != unvisitedEdgeInputs_[possibleTurn.edgeAfterTurn]
        || possibleTurn.curNextEdgeinLoop != nextEdgeInLoop(possibleTurn.edgeBeforeTurn)->edgeId) {
        return false;
    } else {
        return true;
    }
}

void RouteGenerator::applyPossibleTurn(PossibleTurn possibleTurn)
{
    PathSearch search(roadNetwork_, possibleTurn.edgeAfterTurn,
                      std::unordered_set<EdgeId>{nextEdgeInLoop(possibleTurn.edgeBeforeTurn)->edgeId},
                      visitedEdges_);

    std::vector<EdgeId> path = search.getResult();
    path.pop_back();
    double overheadRatio = getPathOverheadRatio(possibleTurn.edgeAfterTurn, path);

    // Possible turns are sorted by overhead. If the real path
    // overhead if bigger than possibleTurn.overhead, possibleTurn
    // should be returned to the queue with its actual overhead.
    // (It is a rare situation due to graph minimal overhead optimization)
    if (overheadRatio * 0.99 > possibleTurn.overheadRatio) {
        possibleTurn.overheadRatio = overheadRatio;
        queue_.push(possibleTurn);
        return;
    }

    auto edgeAfterBreak = std::next(possibleTurn.edgeBeforeTurn);
    LoopId loopId = nextFreeLoopId_++;
    loopLength_[loopId] = getPathLength(roadNetwork_, path);
    loop_.insert(edgeAfterBreak, LoopEdge{possibleTurn.edgeAfterTurn, loopId});
    for (EdgeId pathEdge : path) {
        loop_.insert(edgeAfterBreak, LoopEdge{pathEdge, loopId});
    }

    for (auto it = nextEdgeInLoop(possibleTurn.edgeBeforeTurn);
         it != edgeAfterBreak; ++it) {
        handleNewEdgeInLoop(it);
    }
}

double RouteGenerator::getPathOverheadRatio(
    EdgeId initEdge, const std::vector<EdgeId>& path) {
    // initEdge is always non-overhead, because the first edge of each
    // added loop is always an unvisited target
    Seconds overallTime = roadNetwork_.edge(initEdge).time;
    Seconds overheadTime = 0;
    for (EdgeId pathEdge : path) {
        if (!roadNetwork_.edge(pathEdge).isTarget
            || visitedEdges_.count(pathEdge)) {
            overheadTime += roadNetwork_.edge(pathEdge).time;
        }
        overallTime += roadNetwork_.edge(pathEdge).time;
    }
    return overheadTime / overallTime;
}

void RouteGenerator::handleNewEdgeInLoop(std::list<LoopEdge>::iterator loopIt)
{
    EdgeId edgeId = loopIt->edgeId;
    const Edge& edge = roadNetwork_.edge(edgeId);
    if (visitedEdges_.count(edgeId)) {
        return;
    }
    visitedEdges_.insert(edgeId);
    if (edge.isTarget) {
        generatedLength_ += edge.length;
    }
    for (EdgeId outEdgeId : edge.outEdges) {
        if (!roadNetwork_.edge(outEdgeId).isTarget
            || visitedEdges_.count(outEdgeId)) {
            continue;
        }
        if (edge.isTarget) {
            unvisitedEdgeInputs_[outEdgeId]--;
            // maybe there are possible turns from another edges
            // of the current loop to outEdgeId edge. We need to
            // refresh them
            recalculateTurns(outEdgeId);
        }

        // create new possible turn from loopIt edge to outEdgeId edge
        addPossibleTurn(loopIt, outEdgeId);
        visitedEdgeInputs_[outEdgeId].push_back(loopIt);
    }
}

// recalculate all the possible turns from the current loop to the
// edgeAfterTurn edge
void RouteGenerator::recalculateTurns(EdgeId edgeAfterTurn)
{
    for (auto loopIt : visitedEdgeInputs_[edgeAfterTurn]) {
        addPossibleTurn(loopIt, edgeAfterTurn);
    }
}

void RouteGenerator::addPossibleTurn(
    std::list<LoopEdge>::iterator edgeBeforeTurn,
    EdgeId edgeAfterTurn)
{
        PossibleTurn possibleTurn;
        possibleTurn.edgeBeforeTurn = edgeBeforeTurn;
        possibleTurn.curNextEdgeinLoop = nextEdgeInLoop(edgeBeforeTurn)->edgeId;
        possibleTurn.edgeAfterTurn = edgeAfterTurn;
        possibleTurn.maneuverPenaltyChange
            = roadNetwork_.getManeuverPenalty(possibleTurn.edgeBeforeTurn->edgeId,
                                              edgeAfterTurn)
            - roadNetwork_.getManeuverPenalty(possibleTurn.edgeBeforeTurn->edgeId,
                                              possibleTurn.curNextEdgeinLoop);
        possibleTurn.unrecommendedManeuverPenaltyChange
            = roadNetwork_.getUnrecommendedManeuversPenalty(
                possibleTurn.edgeBeforeTurn->edgeId,
                edgeAfterTurn)
            - roadNetwork_.getUnrecommendedManeuversPenalty(
                possibleTurn.edgeBeforeTurn->edgeId,
                possibleTurn.curNextEdgeinLoop);

        possibleTurn.unvisitedInputs = unvisitedEdgeInputs_[edgeAfterTurn];
        possibleTurn.loopLength = loopLength_[edgeBeforeTurn->loopId];
        possibleTurn.overheadRatio = 0; // overhead will be evaluated
                                        // in applyPossibleTurn()
        queue_.push(possibleTurn);
}

// each edge of the result route has a loopId. It is the id of the
// possible loop that was created on each step 4
LoopsPath RouteGenerator::getResult() {
    LoopsPath result;
    for (auto loopIt = loop_.begin(); loopIt != loop_.end(); ++loopIt) {
        result.push_back(*loopIt);
    }
    return result;
}

} // namespace maps::mrc::gen_targets
