/*
 * BSD 2-Clause License
 *
 * Copyright (c) 2020, Agnieszka Cicha-Cisek
 * Copyright (c) 2020, Patryk Cisek
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include <boost/log/trivial.hpp>

#include <QDBusConnection>
#include <QDBusConnectionInterface>
#include <QDBusInterface>
#include <QImage>
#include <QUuid>

#include "screenshotter.h"
#include "waylandscreenshotexception.h"

Screenshotter::Screenshotter(const QWindow *windowHandle,
                             QObject *parent)
    : QObject(parent),
      qrCodeDetectionErrorTimer(),
      scanner(), windowHandle(windowHandle),
      screen(QGuiApplication::primaryScreen()),
      errorMessage()
{
    if (const QWindow *window = windowHandle) {
        screen = window->screen();
    }

    connect(&scanner, &zbar::QZBar::decodedText,
            this, &Screenshotter::onQrCodeDecoded);

    connect(&qrCodeDetectionErrorTimer, &QTimer::timeout,
            this, &Screenshotter::qrCodeDetectionError);
}

void Screenshotter::takeScreenshot()
{
    BOOST_LOG_TRIVIAL(info) << "Taking a screenshot.";

    // 1st try Wayland. If it won't work, fall back to X11.
    QImage screenshot;
    try {
        screenshot = takeWaylandScreenshot();
    } catch(const WaylandScreenshotException &) {
        screenshot = takeX11Screenshot();
    }

    // Now start the watchdog timer -- in case we can't figure out
    // the Qr-Code, error signal will be emitted.
    qrCodeDetectionErrorTimer.setSingleShot(true);
    qrCodeDetectionErrorTimer.start(2000);

    // And now try scanning the QR-Code. If it won't work.
    scanner.scanImage(screenshot);
}

QImage Screenshotter::takeX11Screenshot()
{
    BOOST_LOG_TRIVIAL(debug) << "Trying to take screenshot the X11 way.";

    auto screenshotPixmap = screen->grabWindow(0);
    QImage screenshot = screenshotPixmap.toImage();
    screenshotSize = screenshot.size();

    return screenshot;
}

QImage Screenshotter::takeWaylandScreenshot()
{
    BOOST_LOG_TRIVIAL(info) << "Trying to take screenshot the Wayland way.";
    auto bus = QDBusConnection::sessionBus();

    QDBusReply<QStringList> reply = bus.interface()->registeredServiceNames();
    if (!reply.isValid()) {
        throw WaylandScreenshotException("D-Bus is not responding.");
    }

    const auto dBusServiceNames = reply.value();
    const QString GNOME_SHELL_SCREENSHOT_SERVICE = "org.gnome.Shell.Screenshot";

    // If   X11 is being used, GNOME's Shell would not be exposing its services.
    if (!dBusServiceNames.contains(GNOME_SHELL_SCREENSHOT_SERVICE)) {
        throw WaylandScreenshotException("GNOME Shell Screenshot service not found.");
    }

    const QString GNOME_SHELL_SCREENSHOT_OBJ_PATH = "/org/gnome/Shell/Screenshot";
    const QString GNOME_SHELL_SCREENSHOT_OBJ_NAME = "Screenshot";
    const QString GNOME_SHELL_SCREENSHOT_IFACE_NAME = "org.gnome.Shell.Screenshot";
    QDBusInterface iface(GNOME_SHELL_SCREENSHOT_SERVICE,
                         GNOME_SHELL_SCREENSHOT_OBJ_PATH,
                         GNOME_SHELL_SCREENSHOT_IFACE_NAME,
                         bus);

    QString tempFilePath = "/tmp/"
            + QUuid::createUuid().toString(QUuid::WithoutBraces)
            + ".png";
    BOOST_LOG_TRIVIAL(debug) << "Saving screenshot to " << tempFilePath.toStdString();

    iface.call(GNOME_SHELL_SCREENSHOT_OBJ_NAME, false, false, tempFilePath);

    QImage screenshot(tempFilePath);
    QFile tempFile(tempFilePath);
    BOOST_LOG_TRIVIAL(debug) << "Removing temporary screenshot file: "
                             << tempFilePath.toStdString();
    if (!tempFile.remove()) {
        BOOST_LOG_TRIVIAL(error) << "Failed to delete temporary screenshot file: "
                                 << tempFilePath.toStdString();
        errorMessage.showMessage("Failed to delete temporary screenshot file! Please delete it"
                                 " manually:\n" + tempFilePath);
    }

    return screenshot;
}

void Screenshotter::onQrCodeDecoded(const QString &decodedText)
{
    BOOST_LOG_TRIVIAL(info) << "Decoded Qr-Code.";
    BOOST_LOG_TRIVIAL(trace) << "The decoded text: " << decodedText.toStdString();

    // First stop the watchdog.
    qrCodeDetectionErrorTimer.stop();

    // Emit the new screenshot.
    emit qrCodeDecoded(decodedText);
}
