// SPDX-License-Identifier: LGPL-2.1-or-later
//
// SPDX-FileCopyrightText: 2014 Dennis Nienhüser <nienhueser@kde.org>
//

/**
 * Does an animated camera flight using a given route loaded from
 * a .kml file and writes an .avi (DIVX) video of it.
 */

#include <marble/GeoDataCoordinates.h>
#include <marble/GeoDataFlyTo.h>
#include <marble/GeoDataLineString.h>
#include <marble/GeoDataLookAt.h>
#include <marble/GeoDataPlaylist.h>
#include <marble/GeoDataTour.h>
#include <marble/MarbleMath.h>
#include <marble/MarbleModel.h>
#include <marble/MarbleWidget.h>
#include <marble/RenderPlugin.h>
#include <marble/Route.h>
#include <marble/RoutingManager.h>
#include <marble/RoutingModel.h>
#include <marble/TourPlayback.h>

#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>

#include <QApplication>
#include <QDebug>
#include <QThread>

#include <cstdio>

using namespace Marble;
using namespace cv;

class Waiter : private QThread
{
public:
    using QThread::msleep;
};

namespace
{
// Some stuff you might want to change

// The map theme in use
QString const mapTheme = "earth/openstreetmap/openstreetmap.dgml";

// Enabled plugins. Everything else will be disabled
QStringList const features = QStringList() << "stars"
                                           << "atmosphere";

// Frames per second
int const fps = 30;

// Target video file name
std::string const videoFile = "marble-tour-preview.avi";

// Video resolution
Size frameSize(1280, 720);

// Camera velocity in km/h
double const velocity = 200.0;
}

GeoDataTour *createTour(const Route &route)
{
    GeoDataTour *tour = new GeoDataTour;
    tour->setPlaylist(new GeoDataPlaylist);
    GeoDataLineString path = route.path();
    if (path.size() < 1) {
        return tour;
    }

    // Extract tour points at about all 500 meters
    GeoDataCoordinates last = path.at(0);
    for (int i = 1; i < path.size(); ++i) {
        GeoDataCoordinates coordinates = path.at(i);
        double const distance = EARTH_RADIUS * last.sphericalDistanceTo(coordinates);
        if (i > 1 && distance < 500) {
            // Ignore waypoints that are quite close
            continue;
        }
        last = coordinates;

        // Create a point in the tour from the given route point
        GeoDataLookAt *lookat = new GeoDataLookAt;
        coordinates.setAltitude(800);
        lookat->setCoordinates(coordinates);
        lookat->setRange(800);
        GeoDataFlyTo *flyto = new GeoDataFlyTo;
        double const duration = qBound(0.2, distance / velocity / 3.6, 10.0);
        flyto->setDuration(duration);
        flyto->setView(lookat);
        flyto->setFlyToMode(GeoDataFlyTo::Smooth);
        tour->playlist()->addPrimitive(flyto);
    }

    return tour;
}

void animatedFlight(MarbleWidget *mapWidget, GeoDataTour *tour)
{
    mapWidget->resize(frameSize.width, frameSize.height);
    TourPlayback *playback = new TourPlayback;
    playback->setMarbleWidget(mapWidget);
    playback->setTour(tour);
    QObject::connect(playback, SIGNAL(centerOn(GeoDataCoordinates)), mapWidget, SLOT(centerOn(GeoDataCoordinates)));

    double const shift = 1.0 / fps;
    double const duration = playback->duration();

    VideoWriter videoWriter(videoFile, cv::VideoWriter::fourcc('D', 'I', 'V', 'X'), fps, frameSize);
    Mat buffer;
    buffer.create(frameSize, CV_8UC3);
    for (double position = 0.0; position <= duration; position += shift) {
        printf("[%i%% done]\r", cvRound((100.0 * position) / duration));
        fflush(stdout);

        playback->seek(position);
        QImage screenshot = mapWidget->grab().toImage().convertToFormat(QImage::Format_RGB888);
        Mat converter(frameSize, CV_8UC3);
        converter.data = screenshot.bits();
        cvtColor(converter, buffer, COLOR_RGB2BGR);
        videoWriter.write(buffer);
    }

    for (int i = 0; i < fps; ++i) {
        videoWriter.write(buffer); // one second stand-still at end
    }
    printf("Wrote %s\n", videoFile.c_str());
}

int main(int argc, char **argv)
{
    QApplication app(argc, argv);
    if (app.arguments().size() < 2) {
        qDebug() << "Usage: " << app.applicationName() << " /path/to/route.kml";
        qDebug() << "You can create a suitable route.kml file with Marble.";
        return 0;
    }

    MarbleWidget *mapWidget = new MarbleWidget;
    mapWidget->setMapThemeId(mapTheme);
    const auto plugins = mapWidget->renderPlugins();
    for (RenderPlugin *plugin : plugins) {
        if (!features.contains(plugin->nameId())) {
            plugin->setEnabled(false);
        }
    }

    mapWidget->model()->routingManager()->loadRoute(argv[1]);
    Route const route = mapWidget->model()->routingManager()->routingModel()->route();
    if (route.size() == 0) {
        qDebug() << "Failed to open route " << argv[1];
        return 1;
    }
    GeoDataCoordinates start = route.path().at(0);
    start.setLongitude(start.longitude() + 1e-6);
    mapWidget->centerOn(start);
    mapWidget->setDistance(0.8);
    animatedFlight(mapWidget, createTour(route));
    return 0;
}
