/****************************************************************************/
// Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
// Copyright (C) 2002-2026 German Aerospace Center (DLR) and others.
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0/
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License 2.0 are satisfied: GNU General Public License, version 2
// or later which is available at
// https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later
/****************************************************************************/
/// @file    ROVehicle.cpp
/// @author  Daniel Krajzewicz
/// @author  Axel Wegener
/// @author  Michael Behrisch
/// @author  Jakob Erdmann
/// @date    Sept 2002
///
// A vehicle as used by router
/****************************************************************************/
#include <config.h>

#include <string>
#include <iostream>
#include <utils/common/StringUtils.h>
#include <utils/common/ToString.h>
#include <utils/common/MsgHandler.h>
#include <utils/geom/GeoConvHelper.h>
#include <utils/vehicle/SUMOVTypeParameter.h>
#include <utils/options/OptionsCont.h>
#include <utils/iodevices/OutputDevice.h>
#include "RORouteDef.h"
#include "RORoute.h"
#include "ROHelper.h"
#include "RONet.h"
#include "ROLane.h"
#include "ROVehicle.h"

#define INVALID_STOP_POS -1

// ===========================================================================
// static members
// ===========================================================================
std::map<ConstROEdgeVector, std::string> ROVehicle::mySavedRoutes;

// ===========================================================================
// method definitions
// ===========================================================================
ROVehicle::ROVehicle(const SUMOVehicleParameter& pars,
                     RORouteDef* route, const SUMOVTypeParameter* type,
                     const RONet* net, MsgHandler* errorHandler):
    RORoutable(pars, type),
    myRoute(route),
    myJumpTime(-1) {
    getParameter().stops.clear();
    if (route != nullptr && route->getFirstRoute() != nullptr) {
        for (StopParVector::const_iterator s = route->getFirstRoute()->getStops().begin(); s != route->getFirstRoute()->getStops().end(); ++s) {
            addStop(*s, net, errorHandler);
        }
    }
    for (StopParVector::const_iterator s = pars.stops.begin(); s != pars.stops.end(); ++s) {
        addStop(*s, net, errorHandler);
    }
}


void
ROVehicle::addStop(const SUMOVehicleParameter::Stop& stopPar, const RONet* net, MsgHandler* errorHandler) {
    const ROEdge* stopEdge = net->getEdge(stopPar.edge);
    assert(stopEdge != 0); // was checked when parsing the stop
    if (stopEdge->prohibits(this)) {
        if (errorHandler != nullptr) {
            errorHandler->inform("Stop edge '" + stopEdge->getID() + "' does not allow vehicle '" + getID() + "'.");
        }
        return;
    }
    // where to insert the stop
    StopParVector::iterator iter = getParameter().stops.begin();
    if (stopPar.index == STOP_INDEX_END || stopPar.index >= static_cast<int>(getParameter().stops.size())) {
        if (getParameter().stops.size() > 0) {
            iter = getParameter().stops.end();
        }
    } else {
        if (stopPar.index == STOP_INDEX_FIT) {
            iter = getParameter().stops.end();
        } else {
            iter += stopPar.index;
        }
    }
    getParameter().stops.insert(iter, stopPar);
    if (stopPar.jump >= 0) {
        if (stopEdge->isInternal()) {
            if (errorHandler != nullptr) {
                errorHandler->inform("Jumps are not supported from internal stop edge '" + stopEdge->getID() + "'.");
            }
        } else {
            if (myJumpTime < 0) {
                myJumpTime = 0;
            }
            myJumpTime += stopPar.jump;
        }
    }
}


ROVehicle::~ROVehicle() {}


const ROEdge*
ROVehicle:: getDepartEdge() const {
    return myRoute->getFirstRoute()->getFirst();
}


void
ROVehicle::computeRoute(const RORouterProvider& provider,
                        const bool removeLoops, MsgHandler* errorHandler) {
    SUMOAbstractRouter<ROEdge, ROVehicle>& router = provider.getVehicleRouter(getVClass());
    std::string noRouteMsg = "The vehicle '" + getID() + "' has no valid route.";
    RORouteDef* const routeDef = getRouteDefinition();
    // check if the route definition is valid
    if (routeDef == nullptr) {
        errorHandler->inform(noRouteMsg);
        myRoutingSuccess = false;
        return;
    }
    routeDef->validateAlternatives(this, errorHandler);
    if (routeDef->getFirstRoute() == nullptr) {
        // everything is invalid and no new routes will be computed
        myRoutingSuccess = false;
        return;
    }
    std::shared_ptr<RORoute> current = routeDef->buildCurrentRoute(router, getDepartureTime(), *this);
    if (current == nullptr || current->size() == 0) {
        if (current == nullptr || !routeDef->discardSilent()) {
            errorHandler->inform(noRouteMsg);
        }
        myRoutingSuccess = false;
        return;
    }
    // check whether we have to evaluate the route for not containing loops
    if (removeLoops) {
        const ROEdge* requiredStart = (getParameter().departPosProcedure == DepartPosDefinition::GIVEN
                                       || getParameter().departLaneProcedure == DepartLaneDefinition::GIVEN ? current->getEdgeVector().front() : 0);
        const ROEdge* requiredEnd = (getParameter().arrivalPosProcedure == ArrivalPosDefinition::GIVEN
                                     || getParameter().arrivalLaneProcedure == ArrivalLaneDefinition::GIVEN ? current->getEdgeVector().back() : 0);
        ConstROEdgeVector mandatory;
        for (auto m : getMandatoryEdges(requiredStart, requiredEnd)) {
            mandatory.push_back(m.edge);
        }
        current->recheckForLoops(mandatory);
        // check whether the route is still valid
        if (current->size() == 0) {
            errorHandler->inform(noRouteMsg + " (after removing loops)");
            myRoutingSuccess = false;
            return;
        }
    }
    if (RONet::getInstance()->getMaxTraveltime() > 0) {
        double costs = router.recomputeCosts(current->getEdgeVector(), this, getDepartureTime());
        if (costs > RONet::getInstance()->getMaxTraveltime()) {
            errorHandler->inform(noRouteMsg + " (traveltime " + time2string(TIME2STEPS(costs)) + " exceeds max-traveltime)");
            myRoutingSuccess = false;
            return;
        }
    }
    // add built route
    std::shared_ptr<RORoute> replaced = routeDef->addAlternative(router, this, current, getDepartureTime(), errorHandler);
    if (replaced != nullptr) {
        if (getParameter().departEdge > 0) {
            updateIndex(replaced, current, getParameterMutable().departEdge);
        }
        if (getParameter().arrivalEdge >= 0) {
            updateIndex(replaced, current, getParameterMutable().arrivalEdge);
        }
    }
    myRoutingSuccess = true;
}


void
ROVehicle::updateIndex(const std::shared_ptr<RORoute> replaced, const std::shared_ptr<RORoute> current, int& attr) {
    const ConstROEdgeVector& oldRoute = replaced->getEdgeVector();
    if ((int)oldRoute.size() > attr) {
        const ROEdge* old = oldRoute[attr];
        int skips = 0;
        for (int i = 0; i < attr; i++) {
            if (oldRoute[i] == old) {
                skips++;
            }
        }
        int i = 0;
        for (const ROEdge* cand : current->getEdgeVector()) {
            if (cand == old) {
                if (skips > 0) {
                    skips--;
                } else {
                    attr = i;
                }
            }
            i++;
        }
    }
}


std::vector<ROVehicle::Mandatory>
ROVehicle::getMandatoryEdges(const ROEdge* requiredStart, const ROEdge* requiredEnd) const {
    const RONet* net = RONet::getInstance();
    std::vector<Mandatory> mandatory;
    if (requiredStart) {
        double departPos = 0;
        if (getParameter().departPosProcedure == DepartPosDefinition::GIVEN) {
            departPos = SUMOVehicleParameter::interpretEdgePos(getParameter().departPos, requiredStart->getLength(), SUMO_ATTR_DEPARTPOS, "vehicle '" + getID() + "'");
        }
        mandatory.push_back(Mandatory(requiredStart, departPos));
    }
    if (getParameter().via.size() != 0) {
        // via takes precedence over stop edges
        for (const std::string& via : getParameter().via) {
            mandatory.push_back(Mandatory(net->getEdge(via), INVALID_STOP_POS));
        }
    } else {
        for (const auto& stop : getParameter().stops) {
            const ROEdge* e = net->getEdge(stop.edge);
            if (e->isInternal()) {
                // the edges before and after the internal edge are mandatory
                const ROEdge* before = e->getNormalBefore();
                const ROEdge* after = e->getNormalAfter();
                mandatory.push_back(Mandatory(before, INVALID_STOP_POS));
                mandatory.push_back(Mandatory(after, INVALID_STOP_POS, stop.jump));
            } else {
                double endPos = SUMOVehicleParameter::interpretEdgePos(stop.endPos, e->getLength(), SUMO_ATTR_ENDPOS, "stop of vehicle '" + getID() + "' on edge '" + e->getID() + "'");
                mandatory.push_back(Mandatory(e, endPos, stop.jump));
            }
        }
    }
    if (requiredEnd) {
        double arrivalPos = INVALID_STOP_POS;
        if (getParameter().arrivalPosProcedure == ArrivalPosDefinition::GIVEN) {
            arrivalPos = SUMOVehicleParameter::interpretEdgePos(getParameter().arrivalPos, requiredEnd->getLength(), SUMO_ATTR_ARRIVALPOS, "vehicle '" + getID() + "'");
        }
        mandatory.push_back(Mandatory(requiredEnd, arrivalPos));
    }
    return mandatory;
}


void
ROVehicle::saveAsXML(OutputDevice& os, OutputDevice* const typeos, bool asAlternatives, OptionsCont& options, int cloneIndex) const {
    if (typeos != nullptr && getType() != nullptr && !getType()->saved) {
        getType()->write(*typeos);
        getType()->saved = true;
    }
    if (getType() != nullptr && !getType()->saved) {
        getType()->write(os);
        getType()->saved = asAlternatives;
    }

    const bool writeTrip = options.exists("write-trips") && options.getBool("write-trips");
    const bool writeGeoTrip = writeTrip && options.getBool("write-trips.geo");
    const bool writeJunctions = writeTrip && options.getBool("write-trips.junctions");
    const bool writeNamedRoute = !asAlternatives && options.getBool("named-routes");
    const bool writeCosts = options.exists("write-costs") && options.getBool("write-costs");
    const bool writeExit = options.exists("exit-times") && options.getBool("exit-times");
    const bool writeLength = options.exists("route-length") && options.getBool("route-length");
    const bool writeFlow = options.exists("keep-flows") && options.getBool("keep-flows") && isPartOfFlow();

    std::string routeID;
    if (writeNamedRoute) {
        ConstROEdgeVector edges = myRoute->getUsedRoute()->getNormalEdges();
        auto it = mySavedRoutes.find(edges);
        if (it == mySavedRoutes.end()) {
            routeID = "r" + toString(mySavedRoutes.size());
            myRoute->getUsedRoute()->writeXMLDefinition(os, this, writeCosts, false, writeExit,
                    writeLength, routeID);
            mySavedRoutes[edges] = routeID;
        } else {
            routeID = it->second;
        }
    }
    const SumoXMLTag tag = writeFlow ? SUMO_TAG_FLOW : (writeTrip ? SUMO_TAG_TRIP : SUMO_TAG_VEHICLE);
    // write the vehicle (new style, with included routes)
    if (cloneIndex == 0) {
        getParameter().write(os, options, tag);
    } else {
        SUMOVehicleParameter p = getParameter();
        // @note id collisions may occur if scale-suffic occurs in other vehicle ids
        p.id += options.getString("scale-suffix") + toString(cloneIndex);
        p.write(os, options, tag);
    }
    // save the route
    if (writeTrip) {
        const ConstROEdgeVector edges = myRoute->getFirstRoute()->getEdgeVector();
        const ROEdge* from = nullptr;
        const ROEdge* to = nullptr;
        if (edges.size() > 0) {
            if (edges.front()->isTazConnector()) {
                if (edges.size() > 1) {
                    from = edges[1];
                    if (from->isTazConnector() && writeJunctions && edges.front()->getSuccessors().size() > 0) {
                        // routing was skipped
                        from = edges.front()->getSuccessors(getVClass()).front();
                    }
                }
            } else {
                from = edges[0];
            }
            if (edges.back()->isTazConnector()) {
                if (edges.size() > 1) {
                    to = edges[edges.size() - 2];
                    if (to->isTazConnector() && writeJunctions && edges.back()->getPredecessors().size() > 0) {
                        // routing was skipped
                        to = edges.back()->getPredecessors().front();
                    }
                }
            } else {
                to = edges[edges.size() - 1];
            }
        }
        if (from != nullptr) {
            if (writeGeoTrip) {
                Position fromPos = from->getLanes()[0]->getShape().positionAtOffset2D(0);
                if (GeoConvHelper::getFinal().usingGeoProjection()) {
                    os.setPrecision(gPrecisionGeo);
                    GeoConvHelper::getFinal().cartesian2geo(fromPos);
                    os.writeAttr(SUMO_ATTR_FROMLONLAT, fromPos);
                    os.setPrecision(gPrecision);
                } else {
                    os.writeAttr(SUMO_ATTR_FROMXY, fromPos);
                }
            } else if (writeJunctions) {
                os.writeAttr(SUMO_ATTR_FROM_JUNCTION, from->getFromJunction()->getID());
            } else {
                os.writeAttr(SUMO_ATTR_FROM, from->getID());
            }
        }
        if (to != nullptr) {
            if (writeGeoTrip) {
                Position toPos = to->getLanes()[0]->getShape().positionAtOffset2D(to->getLanes()[0]->getShape().length2D());
                if (GeoConvHelper::getFinal().usingGeoProjection()) {
                    os.setPrecision(gPrecisionGeo);
                    GeoConvHelper::getFinal().cartesian2geo(toPos);
                    os.writeAttr(SUMO_ATTR_TOLONLAT, toPos);
                    os.setPrecision(gPrecision);
                } else {
                    os.writeAttr(SUMO_ATTR_TOXY, toPos);
                }
            } else if (writeJunctions) {
                os.writeAttr(SUMO_ATTR_TO_JUNCTION, to->getToJunction()->getID());
            } else {
                os.writeAttr(SUMO_ATTR_TO, to->getID());
            }
        }
        if (getParameter().via.size() > 0) {
            std::vector<std::string> viaOut;
            SumoXMLAttr viaAttr = (writeGeoTrip
                                   ? (GeoConvHelper::getFinal().usingGeoProjection() ? SUMO_ATTR_VIALONLAT : SUMO_ATTR_VIAXY)
                                   : (writeJunctions ? SUMO_ATTR_VIAJUNCTIONS : SUMO_ATTR_VIA));
            for (const std::string& viaID : getParameter().via) {
                const ROEdge* viaEdge = RONet::getInstance()->getEdge(viaID);
                if (viaEdge->isTazConnector()) {
                    if (viaEdge->getPredecessors().size() == 0) {
                        continue;
                    }
                    // XXX used edge that was used in route
                    viaEdge = viaEdge->getPredecessors().front();
                }
                assert(viaEdge != nullptr);
                if (writeGeoTrip) {
                    Position viaPos = viaEdge->getLanes()[0]->getShape().positionAtOffset2D(viaEdge->getLanes()[0]->getShape().length2D() / 2);
                    if (GeoConvHelper::getFinal().usingGeoProjection()) {
                        GeoConvHelper::getFinal().cartesian2geo(viaPos);
                        viaOut.push_back(toString(viaPos, gPrecisionGeo));
                    } else {
                        viaOut.push_back(toString(viaPos, gPrecision));
                    }
                } else if (writeJunctions) {
                    viaOut.push_back(viaEdge->getToJunction()->getID());
                } else {
                    viaOut.push_back(viaEdge->getID());
                }
            }
            os.writeAttr(viaAttr, viaOut);
        }
    } else if (writeNamedRoute) {
        os.writeAttr(SUMO_ATTR_ROUTE, routeID);
    } else {
        myRoute->writeXMLDefinition(os, this, asAlternatives, writeExit, writeCosts, writeLength);
    }
    for (StopParVector::const_iterator stop = getParameter().stops.begin(); stop != getParameter().stops.end(); ++stop) {
        stop->write(os);
    }
    getParameter().writeParams(os);
    os.closeTag();
}


/****************************************************************************/
