/* This file is part of the KDE project
   Copyright (C) 2021 Aleix Pol Gonzalez <aleixpol@kde.org>

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public
   License as published by the Free Software Foundation; either
   version 3 of the License, or (at your option) any later version.
*/

#include <QApplication>
#include <QCommandLineParser>
#include <QCommandLineOption>
#include <QDebug>
#include <QTimer>
#include <KNotification>
#include <KLocalizedString>
#include <KWindowSystem>
#include <KAboutData>
#include "sockethelpers.h"
#include "krfb_version.h"
#include "rfbserver.h"
#include <signal.h>
#include "rfbservermanager.h"

class VirtualMonitorRfbClient : public RfbClient
{
public:
    explicit VirtualMonitorRfbClient(rfbClientPtr client, QObject *parent = nullptr)
        : RfbClient(client, parent)
    {}
};

class PendingVirtualMonitorRfbClient : public PendingRfbClient
{
public:
    explicit PendingVirtualMonitorRfbClient(rfbClientPtr client, QObject *parent = nullptr)
        : PendingRfbClient(client, parent)
    {}
    ~PendingVirtualMonitorRfbClient() override {}

    static QByteArray password;

protected:
    void processNewClient() override {
        qDebug() << "new client!";
        const QString host = peerAddress(m_rfbClient->sock) + QLatin1Char(':') + QString::number(peerPort(m_rfbClient->sock));

        KNotification::event(QStringLiteral("NewConnectionAutoAccepted"),
                            i18n("Creating a Virtual Monitor from %1", host));
    }
    bool checkPassword(const QByteArray & encryptedPassword) override {
        bool b = vncAuthCheckPassword(password, encryptedPassword);
        if (b) {
            QTimer::singleShot(0, this, [this] {
                accept(new VirtualMonitorRfbClient(m_rfbClient, parent()));
            });
        }
        return b;
    }
};

QByteArray PendingVirtualMonitorRfbClient::password;

class VirtualMonitorRfbServer : public RfbServer
{
public:
    PendingRfbClient *newClient(rfbClientPtr client) override {
        qDebug() << "new client request";
        return new PendingVirtualMonitorRfbClient(client, this);
    }
};

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    KLocalizedString::setApplicationDomain("krfb");

    KAboutData aboutData(QStringLiteral("krfb-virtualmonitor"),
                         i18n("Remote Virtual Monitor"),
                         QStringLiteral(KRFB_VERSION_STRING),
                         i18n("Offer a Virtual Monitor that can be accessed remotely"),
                         KAboutLicense::GPL,
                         i18n("(c) 2009-2010, Collabora Ltd.\n"
                               "(c) 2007, Alessandro Praduroux\n"
                               "(c) 2001-2003, Tim Jansen\n"
                               "(c) 2001, Johannes E. Schindelin\n"
                               "(c) 2000-2001, Const Kaplinsky\n"
                               "(c) 2000, Tridia Corporation\n"
                               "(c) 1999, AT&T Laboratories Boston\n"));
    aboutData.addAuthor(QStringLiteral("Aleix Pol i Gonzalez"), i18n("Virtual Monitor implementation"), QStringLiteral("aleixpol@kde.org"));
    aboutData.addAuthor(i18n("George Kiagiadakis"), QString(), QStringLiteral("george.kiagiadakis@collabora.co.uk"));
    aboutData.addAuthor(i18n("Alessandro Praduroux"), i18n("KDE4 porting"), QStringLiteral("pradu@pradu.it"));
    aboutData.addAuthor(i18n("Tim Jansen"), i18n("Original author"), QStringLiteral("tim@tjansen.de"));
    aboutData.addCredit(i18n("Johannes E. Schindelin"),
                        i18n("libvncserver"));
    aboutData.addCredit(i18n("Const Kaplinsky"),
                        i18n("TightVNC encoder"));
    aboutData.addCredit(i18n("Tridia Corporation"),
                        i18n("ZLib encoder"));
    aboutData.addCredit(i18n("AT&T Laboratories Boston"),
                        i18n("original VNC encoders and "
                              "protocol design"));
    KAboutData::setApplicationData(aboutData);

    QCommandLineParser parser;
    aboutData.setupCommandLine(&parser);
    const QCommandLineOption resolutionOption({ QStringLiteral("resolution") }, i18n("Logical resolution of the new monitor"), i18n("resolution"));
    parser.addOption(resolutionOption);
    const QCommandLineOption nameOption({ QStringLiteral("name") }, i18n("Name of the monitor"), i18n("name"));
    parser.addOption(nameOption);
    const QCommandLineOption passwordOption({ QStringLiteral("password") }, i18n("Password for the client to connect to it"), i18n("password"));
    parser.addOption(passwordOption);
    const QCommandLineOption scaleOption({ QStringLiteral("scale") }, i18n("The device-pixel-ratio of the device, the scaling factor"), i18n("dpr"), QStringLiteral("1"));
    parser.addOption(scaleOption);
    const QCommandLineOption portOption({ QStringLiteral("port") }, i18n("The port we will be listening to"), i18n("number"), QStringLiteral("9999"));
    parser.addOption(portOption);

    parser.process(app);
    aboutData.processCommandLine(&parser);

    app.setQuitOnLastWindowClosed(false);

    if (!KWindowSystem::isPlatformWayland()) {
        qCritical() << "Virtual Monitors are only supported on Wayland";
        return 1;
    }
    if (!parser.isSet(nameOption)) {
        qCritical() << "error: please define --name";
        return 2;
    } else {
        if (!parser.isSet(passwordOption)) {
            qCritical() << "error: please define --password";
            return 3;
        }
        if (!parser.isSet(resolutionOption)) {
            qCritical() << "error: please define --resolution";
            return 4;
        }
    }
    if (!parser.isSet(portOption)) {
        qCritical() << "error: please define --port";
        return 5;
    }
    const QString res = parser.value(resolutionOption);
    const auto resSplit = res.split(QLatin1Char('x'));
    if (resSplit.size() != 2) {
        qCritical() << "error: the resolution should be formatted as WIDTHxHEIGHT (e.g. --resolution 1920x1080)";
        return 6;
    }


    if (parser.isSet(nameOption)) {
        RfbServerManager::s_pluginArgs = {
            { QStringLiteral("name"), parser.value(nameOption) },
            { QStringLiteral("resolution"), QSize(resSplit[0].toInt(), resSplit[1].toInt()) },
            { QStringLiteral("scale"), parser.value(scaleOption).toDouble() },
        };
    }

    VirtualMonitorRfbServer server;
    server.setPasswordRequired(true);
    server.setListeningPort(parser.value(portOption).toInt());
    PendingVirtualMonitorRfbClient::password = parser.value(passwordOption).toUtf8();

    sigset_t sigs;
    sigemptyset(&sigs);
    sigaddset(&sigs, SIGPIPE);
    sigprocmask(SIG_BLOCK, &sigs, nullptr);
    if (!server.start()) {
        return 1;
    }

    return app.exec();
}
