/*
 * This file is part of Remote Support Desktop
 * https://gitlab.das-netzwerkteam.de/RemoteWebApp/remote-support-desktop
 * Copyright 2020 Daniel Teichmann <daniel.teichmann@das-netzwerkteam.de>
 * Copyright 2020 Mike Gabriel <mike.gabriel@das-netzwerktea.de>
 * SPDX-License-Identifier: GPL-2.0-or-later
 *
 * 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 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the
 * Free Software Foundation, Inc.,
 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

#pragma once

#include "session.h"

Session::Session(QObject *parent, MainQMLAdaptor* main_gui) : QObject(parent) {
    _initDBus();

    _main_gui = main_gui;

    // QML -> MainQMLAdaptor::handleConnectButtonClick --onConnectButtonClick--> this::handleConnectButtonClick
    QObject::connect(_main_gui,
                         SIGNAL(onConnectButtonClick(bool)),
                         this,
                         SLOT(handleConnectButtonClick(bool)));

    // session::setPin --pinChanged--> MainQMLAdaptor::setPin --pinChanged--> QML
    QObject::connect(this,
                         SIGNAL(pinChanged(QString)),
                         main_gui,
                         SLOT(setPin(QString)));
    // session::setURL --urlChanged--> MainQMLAdaptor::setURL --urlChanged--> QML
    QObject::connect(this,
                         SIGNAL(urlChanged(QString)),
                         main_gui,
                         SLOT(setURL(QString)));

    // QML -> MainQMLAdaptor::onCloseHandler --onCloseSignal--> session::onCloseHandler
    QObject::connect(main_gui,
                         SIGNAL(onCloseSignal()),
                         this,
                         SLOT(onCloseHandler()));

    this->init_vars();
}

void Session::statusTimerEvent() {
    qDebug() << "Status timer event triggered";
    this->refresh_status_request_dbus(this->getId());
}

void Session::init_vars() {
    setPin("-----");
    setId(-1);
    setURL(tr("Not available yet"));
    setStatus("unknown");

    _main_gui->setConnectButtonChecked(false);
    _main_gui->setConnectButtonEnabled(true);
    _main_gui->setStatusIndicator(false);

    _minimizedBefore = false;
}

QString Session::getStatus() {
    return _status;
}

QString Session::getURL() {
    return _url;
}

int Session::getId() {
    return _id;
}

QString Session::getPin() {
    return _pin;
}

bool Session::isSessionAliveOrRunning(QString status) {
    if (status == "running" || status == "active") {
        return true;
    } else {
        return false;
    }
}

void Session::minimizeWindow() {
    if (!_minimizedBefore) {
        qDebug() << "Minimizing window now...";
        emit _main_gui->minimizeWindow();
        _minimizedBefore = true;
    }
}

void Session::setStatus(QString status) {
    _status = status;

    QString guiString = tr("Unknown state of service");
    _main_gui->setStatusIndicator(false);

    qDebug() << "setStatus(): Setting status to " << status;

    if (status == "running") {
        /* Session is running but no one is connected yet */
        guiString = tr("Remote Support session is ready to be connected to");
        _main_gui->setStatusIndicator(true, QColor(255, 255, 0, 127));
    } else if (status == "dead") {
        /* Session died */
        guiString = tr("Remote Support session was stopped ungracefully");

        // Clear current variables
        this->init_vars();
        _main_gui->setStatusIndicator(true, QColor(255, 0, 0, 127));

        emit _main_gui->showWindow();

        _main_gui->showToast(tr("Remote Support session was stopped ungracefully"), 5000);
    } else if (status == "stopped") {
        /* Session is stopped */
        guiString = tr("Remote Support session was stopped");

        // Clear current variables
        this->init_vars();

        emit _main_gui->showWindow();

        _main_gui->showToast(tr("Remote Support session was stopped"), 5000);
    } else if (status == "active") {
        /* Partner is connected */
        QTimer::singleShot(1000, this, &Session::minimizeWindow);
        guiString = tr("Your partner is connected to the Remote Support session");
        _main_gui->setStatusIndicator(true, QColor(0, 255, 0, 127));

    } else if (status == "waiting_start_request_answer") {
        /* When pressing on start button display following message while waiting */
        guiString = tr("Trying to reach session service...");
    } else if (status == "start_session_error") {
        /* Session couldn't be started */
        guiString = tr("Remote Support session couldn't be started!");
        _main_gui->setStatusIndicator(true, QColor(255, 0, 0, 127));

        _main_gui->showToast(tr("An error occured while trying to start a session!"), 5000);
    } else if (status == "start_session_success") {
        /* Session successfully started */
        guiString = tr("Remote Support session successfully started!");
        _main_gui->setStatusIndicator(true, QColor(255, 255, 0, 127));

        _main_gui->showToast(tr("Session was started successfully"));
    } else if (status == "status_session_error") {
        /* Session's status couldn't be refreshed */
        _main_gui->showToast(tr("Session status could not be refreshed!"), 5000);
        guiString = tr("Session status could not be refreshed!") + "\n" + tr("remote support partner could still be connected!");
        _main_gui->setStatusIndicator(true, QColor(255, 0, 0, 127));

        emit _main_gui->showWindow();
    } else if (status == "stop_session_error") {
        /* Session couldn't be stopped */
        _main_gui->showToast(tr("Session could not be stopped!"), 5000);
        guiString = tr("Session could not be stopped!") + "\n" + tr("remote support partner could still be connected!");
        _main_gui->setStatusIndicator(true, QColor(255, 0, 0, 127));

        emit _main_gui->showWindow();
    }

    _main_gui->setStatus(guiString);

    emit statusChanged(_status);
}

void Session::setURL(QString url) {
    _url = url;
    emit urlChanged(url);
}

void Session::setId(int id) {
    _id = id;
    emit idChanged(id);
}

void Session::setPin(QString pin) {
    _pin = pin;
    emit pinChanged(pin);
}

void Session::handleConnectButtonClick(bool checked) {
    qDebug() << "-----Connect button handler-----\nCurrent session #" << this->getId();

    // Stopping even if nothing is running
    this->stop_request_dbus(this->getId());

    if (checked) {
        // Start the Session again
        this->start_request_dbus();
    }
    qDebug() << "-----\\Connect button handler-----";
}

void Session::_initDBus() {
    if (!QDBusConnection::sessionBus().isConnected()) {
        qCritical() << "Cannot connect to the D-Bus session bus.";
    }

    // Create DBus object
    _dbus_rwa = new OrgArcticaProjectRWAInterface("org.ArcticaProject.RWA", "/RWA",
                               QDBusConnection::sessionBus(), this->parent());

    qDebug("Initialized DBus object!");
}

void Session::start_request_dbus() {
    qDebug() << "Requesting D-Bus session service to start a new session";

    // Make an asynchrous 'start' call (Response will be sent to 'start_dbus_replied')
    QDBusPendingCall async = _dbus_rwa->asyncCall("start");
    QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(async, this);

    QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)),
                     this, SLOT(start_dbus_replied(QDBusPendingCallWatcher*)));

    // Disable connect button to reduce spam.
    // And don't enable it as long there is no status update.
    // (Or something fails)
    _main_gui->setConnectButtonEnabled(false);
    this->setStatus("waiting_start_request_answer");
}

void Session::start_dbus_replied(QDBusPendingCallWatcher *call) {
    QString result = "";

    QDBusPendingReply<QString> reply = *call;
    if (reply.isError()) {
        qDebug() << "D-Bus 'start' request failed, this was the reply:";
        qDebug() << reply.error();

        // The user should have the oportunity to try again.
        this->init_vars();

        qCritical() << "An error occured while creating a new session!";
        this->setStatus("start_session_error");

        return;
    } else {
        result = reply.argumentAt<0>();
    }
    call->deleteLater();

    qDebug() << "Raw JSON from starting session is:" << result;
    QJsonDocument doc = QJsonDocument::fromJson(result.toUtf8());

    // Get the QJsonObject
    QJsonObject jObject = doc.object();
    QVariantMap mainMap = jObject.toVariantMap();

    // Status of request
    QString request_status = mainMap["status"].toString();
    if (request_status == "success") {
        qDebug() << "Successfully started a Session.";
        this->setStatus("start_session_success");

        // Immediately ask for the status of the session
        QTimer::singleShot(0, this, &Session::statusTimerEvent);
    } else {
        qCritical() << "An error occured while creating a new session!";
        this->init_vars();
        this->setStatus("start_session_error");
        return;
    }

    // Session ID == PID
    int sessionid = mainMap["id"].toInt();
    this->setId(sessionid);

    // URL of remote web app frontend
    QString url = mainMap["url"].toString();
    this->setURL(url);

    // PIN
    QString pin = mainMap["pin"].toString();
    this->setPin(pin);

    qDebug() << "Got session id:" << sessionid;
    qDebug() << "Got url:" << url;
    qDebug() << "Got pin:" << pin;

    emit pinChanged(pin);
    emit urlChanged(url);
    emit idChanged(sessionid);
}

void Session::stop_request_dbus(int pid) {
    if (pid <= 0 ){
        qDebug() << "Won't send a request to D-Bus service to stop a session when session ID <= 0";
        return;
    }

    // Stopping now.
    qDebug() << "Requesting D-Bus session service to stop session #" << pid;

    // Make an asynchrous 'stop' call (Response will be sent to 'stop_dbus_replied')
    QDBusPendingCall async = _dbus_rwa->asyncCall("stop", pid);
    QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(async, this);

    QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)),
                     this, SLOT(stop_dbus_replied(QDBusPendingCallWatcher*)));

    // Clear current variables
    this->init_vars();

    // Disable connect button to reduce spam.
    // And don't enable it as long there is no status update.
    // (Or something fails)
    _main_gui->setConnectButtonEnabled(false);
}

void Session::stop_dbus_replied(QDBusPendingCallWatcher *call) {
    QString result = "";

    QDBusPendingReply<QString> reply = *call;
    if (reply.isError()) {
        qDebug() << "D-Bus 'stop' request failed, this was the reply:";
        qDebug() << reply.error();

        this->init_vars();
        this->setStatus("stop_session_error");

        return;
    } else {
        result = reply.argumentAt<0>();
    }
    call->deleteLater();

    // Get the QJsonObject
    QJsonDocument doc = QJsonDocument::fromJson(result.toUtf8());
    QJsonObject jObject = doc.object();
    QVariantMap mainMap = jObject.toVariantMap();

    QString new_status = mainMap["status"].toString();
    qDebug() << "stop_dbus_replied(): Refreshed status:" << new_status;

    // Clear current variables
    this->init_vars();

    // But the status indicator should display that the Session has stopped
    this->setStatus(new_status);
}

void Session::status_request_dbus(int pid) {
    if (pid <= 0 ){
        qDebug() << "Won't send a request to D-Bus service of a session's status when session ID <= 0";
        return;
    }

    // Requesting status now.
    qDebug() << "Requesting status for session #" << pid;

    // Make an asynchrous 'status' call (Response will be sent to 'status_dbus_replied')
    QDBusPendingCall async = _dbus_rwa->asyncCall("status", pid);
    QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(async, this);

    QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)),
                     this, SLOT(stop_dbus_replied(QDBusPendingCallWatcher*)));
}

void Session::refresh_status_request_dbus(int pid) {
    if (pid <= 0 ){
        qDebug() << "Won't send a request to D-Bus service to refresh the status of a session when session ID <= 0";
        return;
    }

    // Refreshing status
    qDebug() << "Requesting status refresh for session #" << pid;

    // Make an asynchrous 'refresh_status' call (Response will be sent to 'status_dbus_replied')
    QDBusPendingCall async = _dbus_rwa->asyncCall("refresh_status", pid);
    QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(async, this);

    QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)),
                     this, SLOT(status_dbus_replied(QDBusPendingCallWatcher*)));
}

void Session::status_dbus_replied(QDBusPendingCallWatcher *call) {
    QString result = "";

    QDBusPendingReply<QString> reply = *call;
    if (reply.isError()) {
        qDebug() << "D-Bus '(refresh_)status' request failed, this was the reply:";
        qDebug() << reply.error();

        this->init_vars();

        this->setStatus("status_session_error");

        return;
    } else {
        result = reply.argumentAt<0>();
    }
    call->deleteLater();

    // Get the QJsonObject
    QJsonDocument doc = QJsonDocument::fromJson(result.toUtf8());
    QJsonObject jObject = doc.object();
    QVariantMap mainMap = jObject.toVariantMap();
    QString new_status = mainMap["status"].toString();
    qDebug() << "status_dbus_replied(): Refreshed status:" << new_status;

    // Enable (dis)connect button
    _main_gui->setConnectButtonEnabled(true);

    this->setStatus(new_status);

    if (this->isSessionAliveOrRunning(new_status)) {
        // Ask status every 1000 millisecond
        QTimer::singleShot(1000, this, &Session::statusTimerEvent);
    }
}

void Session::onCloseHandler() {
    // To cleanup things here
    this->stop_request_dbus(this->getId());
}
