// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only

#include <QtCore/qjsondocument.h>
#include <QtCore/qjsonobject.h>
#include <QtCore/qjsonarray.h>
#include <QtCore/qmetaobject.h>
#include <QtLocation/private/qgeorouteparser_p.h>
#include <QtLocation/private/qgeoroute_p.h>
#include <QtLocation/qgeoroutesegment.h>
#include <QtLocation/qgeomaneuver.h>
#include <qgeoroutereplyohosmapkit.h>
#include <qgeoroutingmanagerengineohosmapkit.h>
#include <qohosmapkitcommon.h>

QT_BEGIN_NAMESPACE

namespace {

QHash<QString, QGeoManeuver::InstructionDirection> actionsToDirectionsMap = {
    {"turn-slight-left", QGeoManeuver::DirectionLightLeft},
    {"turn-sharp-left", QGeoManeuver::DirectionHardLeft},
    {"uturn-left", QGeoManeuver::DirectionUTurnLeft},
    {"turn-left", QGeoManeuver::DirectionLeft},
    {"turn-slight-right", QGeoManeuver::DirectionLightRight},
    {"turn-sharp-right", QGeoManeuver::DirectionHardRight},
    {"uturn-right", QGeoManeuver::DirectionUTurnRight},
    {"turn-right", QGeoManeuver::DirectionRight},
    {"straight", QGeoManeuver::DirectionForward},
    {"ramp-left", QGeoManeuver::DirectionBearLeft},
    {"ramp-right", QGeoManeuver::DirectionBearRight},
    {"fork-left", QGeoManeuver::DirectionBearLeft},
    {"fork-right", QGeoManeuver::DirectionBearRight},
    {"roundabout-left", QGeoManeuver::DirectionLeft},
    {"roundabout-right", QGeoManeuver::DirectionRight},
};

QList<QGeoCoordinate> parsePolyline(const QJsonArray &polyArray)
{
    QList<QGeoCoordinate> coords;

    coords.reserve(polyArray.size());
    for (const auto &coordVal : polyArray)
        coords.append(OhosMapKit::CoordinateJson::tryConvertToQGeoCoordinate(coordVal.toObject()));

    return coords;
}

QGeoManeuver parseManeuver(const QJsonObject &stepObj)
{
    QGeoManeuver maneuver;

    maneuver.setInstructionText(stepObj.value("instruction").toString());
    maneuver.setPosition(
        OhosMapKit::CoordinateJson::tryConvertToQGeoCoordinate(stepObj.value("startLocation").toObject()));
    maneuver.setDistanceToNextInstruction(stepObj.value("distance").toDouble());
    maneuver.setTimeToNextInstruction(stepObj.value("duration").toDouble());

    auto action = stepObj.value("action").toString();
    maneuver.setDirection(actionsToDirectionsMap.value(action, QGeoManeuver::NoDirection));

    return maneuver;
}

QGeoRouteSegment parseStep(const QJsonObject &stepObj)
{
    QGeoRouteSegment segment;

    segment.setDistance(stepObj.value("distance").toDouble());
    segment.setTravelTime(stepObj.value("duration").toDouble());
    segment.setPath(parsePolyline(stepObj.value("polyline").toArray()));
    segment.setManeuver(parseManeuver(stepObj));

    return segment;
}

QGeoRoute parseRoute(const QJsonObject &routeObj)
{
    QGeoRoute route;

    route.setBounds(
        OhosMapKit::CoordinateBoundsJson::tryConvertToQGeoRectangle(routeObj.value("bounds").toObject()));

    double totalDistance = 0.0;
    double totalTimeSeconds = 0.0;
    QList<QGeoCoordinate> fullRoutePath;
    QList<QGeoRouteSegment> routeSegments;

    auto pathsArray = routeObj.value("paths").toArray();
    for (const auto &pathVal : pathsArray) {
        auto pathObj = pathVal.toObject();
        totalDistance += pathObj.value("distance").toDouble();
        totalTimeSeconds += pathObj.value("duration").toDouble();

        auto stepsArray = pathObj.value("steps").toArray();
        for (const auto &stepVal : stepsArray) {
            routeSegments.push_back(parseStep(stepVal.toObject()));

            auto segmentPath = routeSegments.last().path();
            if (!segmentPath.isEmpty()) {
                if (!fullRoutePath.isEmpty() && fullRoutePath.last() == segmentPath.first())
                    segmentPath.pop_front();

                fullRoutePath += segmentPath;
            }
        }
    }

    route.setDistance(totalDistance);
    route.setTravelTime(totalTimeSeconds);
    route.setPath(fullRoutePath);
    if (!routeSegments.isEmpty()) {
        auto currentRouteSegment = routeSegments.last();
        for (int i = routeSegments.size() - 2; i >= 0; --i) {
            auto tmpRouteSegment = routeSegments[i];
            tmpRouteSegment.setNextRouteSegment(currentRouteSegment);
            currentRouteSegment = tmpRouteSegment;
        }
        route.setFirstRouteSegment(currentRouteSegment);
    }

    return route;
}

QList<QGeoRoute> parseRouteResponse(const QJsonObject &responseBodyJsonObj)
{
    QList<QGeoRoute> routes;

    auto routesArray = responseBodyJsonObj.value("routes").toArray();
    for (const auto &routeVal : routesArray)
        routes.append(parseRoute(routeVal.toObject()));

    return routes;
}

class QGeoRouteReplyOhosMapKit : public QGeoRouteReply
{
public:
    QGeoRouteReplyOhosMapKit(
        QNetworkReply *reply, const QGeoRouteRequest &request, QObject *parent);

private:
    void networkReplyFinished();
    void networkReplyError(QNetworkReply::NetworkError error);
};

QGeoRouteReplyOhosMapKit::QGeoRouteReplyOhosMapKit(
    QNetworkReply *reply, const QGeoRouteRequest &request, QObject *parent)
    : QGeoRouteReply(request, parent)
{
    if (reply == nullptr) {
        setError(QGeoRouteReply::UnknownError, QStringLiteral("Null reply"));
        return;
    }

    connect(reply, &QNetworkReply::finished, this, &QGeoRouteReplyOhosMapKit::networkReplyFinished);
    connect(reply, &QNetworkReply::errorOccurred, this, &QGeoRouteReplyOhosMapKit::networkReplyError);
    connect(this, &QGeoRouteReply::aborted, reply, &QNetworkReply::abort);
    connect(this, &QObject::destroyed, reply, &QObject::deleteLater);
}

void QGeoRouteReplyOhosMapKit::networkReplyFinished()
{
    auto *reply = static_cast<QNetworkReply *>(sender());
    reply->deleteLater();

    if (reply->error() != QNetworkReply::NoError) {
        setError(QGeoRouteReply::UnknownError, OhosMapKit::msgReplyError(reply->errorString()));
        return;
    }

    auto responseBodyJsonDoc = QJsonDocument::fromJson(reply->readAll());
    if (!responseBodyJsonDoc.isObject()) {
        setError(QGeoRouteReply::ParseError, tr("Response parse error"));
        return;
    }

    setRoutes(parseRouteResponse(responseBodyJsonDoc.object()));

    setFinished(true);
}

void QGeoRouteReplyOhosMapKit::networkReplyError(QNetworkReply::NetworkError error)
{
    Q_UNUSED(error)
    auto *reply = static_cast<QNetworkReply *>(sender());
    reply->deleteLater();
    setError(QGeoRouteReply::CommunicationError, reply->errorString());
}

}

QGeoRouteReply *makeQGeoRouteReplyOhosMapKit(
    QNetworkReply *reply, const QGeoRouteRequest &request, QObject *parent)
{
    return new QGeoRouteReplyOhosMapKit(reply, request, parent);
}

QT_END_NAMESPACE
