/*
 * Copyright (C) 2022 ~ 2024 Deepin Technology Co., Ltd.
 *
 * Author:     xupeidong <xupeidong@uniontech.com>
 *
 * Maintainer: xupeidong <xupeidong@uniontech.com>
 *
 * 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
 * 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, see <http://www.gnu.org/licenses/>.
 */
#include "deepinwatermark.h"

#include <QApplication>
#include <QDesktopWidget>
#include <QDebug>
#include <QPaintEvent>
#include <QScreen>
#include <QDBusConnection>
#include <QDBusMessage>
#include <QDBusInterface>
#include <QDBusReply>
#include <QFileInfo>
#include <QDBusConnectionInterface>
#include <QDateTime>
#include <QX11Info>
#include <QTimer>
#include <QThread>
#include <QSettings>
#include <QStandardPaths>

const int TIMEINTERVAL=1500;
const QString WHITE_CONFIG = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + "/kwatermark";

DeepinWatermark::DeepinWatermark()
    : m_showTimer(new QTimer(this))
{
    // 监听kwin compositor setup信号
    QDBusConnection::sessionBus().connect("org.kde.KWin", "/Compositor", "org.kde.kwin.Compositing", "compositingSetup", this, SLOT(compositingSetup()));
    // 监听kwin切换2D和3D的信号
    QDBusConnection::sessionBus().connect("org.kde.KWin", "/Compositor", "org.kde.kwin.Compositing", "compositingToggled", this, SLOT(compositingToggled(bool)));
    QDBusInterface kwinInterface("org.kde.KWin", "/Compositor", "org.kde.kwin.Compositing");
    m_isCompositor = kwinInterface.property("active").toBool();

    // 监听屏幕分辨率变化
    connect(QGuiApplication::primaryScreen(), &QScreen::geometryChanged, this, &DeepinWatermark::desktopResize);
    // 监听屏幕模式变化
    connect(QGuiApplication::primaryScreen(), &QScreen::virtualGeometryChanged, this, &DeepinWatermark::displayMode);

    // 监听控制中心显示相关的属性
    QDBusConnection::sessionBus().connect("com.deepin.daemon.Display", "/com/deepin/daemon/Display", "org.freedesktop.DBus.Properties", "PropertiesChanged", "sa{sv}as", this, SLOT(displayProperty(QString, QVariantMap, QStringList)));

    QDBusInterface interfaceRequire("org.desktopspec.ConfigManager", "/", "org.desktopspec.ConfigManager", QDBusConnection::systemBus());
    QDBusPendingReply<QDBusObjectPath> reply = interfaceRequire.call("acquireManager", "org.kde.kwin", "org.deepin.displayjack.watermark.display", "");
    reply.waitForFinished();

    if (!reply.isError()) {
        QDBusInterface interfaceValue("org.desktopspec.ConfigManager", reply.value().path(), "org.desktopspec.ConfigManager.Manager", QDBusConnection::systemBus());
        QDBusReply<QVariant> replyValue = interfaceValue.call("value", "watermarkDisplay");
        QString strValue = replyValue.value().toString();
        if (strValue == "Enabled" || strValue == "enabled") {
            m_isAvailabeWaterMark = true;
        } else {
            m_isAvailabeWaterMark = false;
        }
    } else {
        qWarning()<<"reply.error: "<<reply.error();
    }

    initConfig();
    m_showTimer->setSingleShot(true);
    m_showTimer->setInterval(TIMEINTERVAL);
    connect(m_showTimer, &QTimer::timeout, this, [this] {
        resetWaterMarkArea();
    });
    m_showTimer->start();
}

DeepinWatermark::~DeepinWatermark()
{
    m_showTimer->stop();
    releaseWatermarks();
}

void DeepinWatermark::releaseWatermarks()
{
    qDeleteAll(m_watermarkList);

    m_watermarkList.clear();

    if (m_watermark) {
        delete m_watermark;
        m_watermark = nullptr;
    }
}

void DeepinWatermark::resetWaterMarkArea()
{
    if (!m_isAvailabeWaterMark)
        return;
    m_isReset = true;

    int x = 0;
    int y = 0;
    int width = 0;
    int height = 0;
    // NOTE: The code design is terrible when the compositor is off
    // but kwin no longer supports disabling compositor in the control center anyway
    // Need to optimize code in the future
    if (m_isCompositor) {
        if (!m_watermark)
            m_watermark = new Watermark(m_isCompositor);
        x = QGuiApplication::primaryScreen()->virtualGeometry().x();
        y = QGuiApplication::primaryScreen()->virtualGeometry().y();
        width = QGuiApplication::primaryScreen()->virtualGeometry().width();
        height = QGuiApplication::primaryScreen()->virtualGeometry().height();
        m_watermark->setWaterMarkGeometry(x, y, width, height);
    } else {
        for (int var = 0; var < QGuiApplication::screens().count(); ++var) {
            x = QGuiApplication::screens().at(var)->geometry().x();
            y = QGuiApplication::screens().at(var)->geometry().y();
            width = QGuiApplication::screens().at(var)->geometry().width();
            height = QGuiApplication::screens().at(var)->geometry().height();
            if (var == m_watermarkList.size())
                m_watermarkList.append(new Watermark(m_isCompositor));
            Watermark *watermark = m_watermarkList.at(var);
            watermark->setWaterMarkGeometry(x, y, width, height);
        }
    }
}

bool DeepinWatermark::watermarkStatus() const
{
    if (m_watermark) {
        return m_watermark->watermarkStatus();
    } else {
        for (auto watermark : m_watermarkList) {
            return watermark->watermarkStatus();
        }
    }
    return false;
}

void DeepinWatermark::clearConfig()
{
    if (m_watermark) {
        m_watermark->clearConfig();
    } else {
        for (auto watermark : m_watermarkList) {
            watermark->clearConfig();
            return;
        }
    }
}

void DeepinWatermark::compositingSetup()
{
    if (m_watermark) {
        m_watermark->compositingSetup();
    } else {
        for (auto watermark : m_watermarkList) {
            watermark->compositingSetup();
        }
    }
}

void DeepinWatermark::compositingToggled(bool active)
{
    m_isCompositor = active;

    if (m_watermark) {
        m_watermark->setCompositorActive(active);
        if (!m_watermark->watermarkOpen()) {
            return;
        }
    } else {
        std::for_each(m_watermarkList.begin(), m_watermarkList.end(),
            [active] (Watermark *watermark) { watermark->setCompositorActive(active); }
        );
        for (auto watermark : m_watermarkList) {
            if (!watermark->watermarkOpen()) {
                return;
            }
        }
    }

    if (m_isReset) {
        releaseWatermarks();
        m_showTimer->start(1000);
    }
}

void DeepinWatermark::setScreenWatermark(const QString &strPolicy)
{
    if (!m_isAvailabeWaterMark)
        return;

    if (!m_isReset)
        resetWaterMarkArea();
    if (m_watermark) {
        m_watermark->setScreenWatermark(strPolicy);
    } else {
        for (auto watermark : m_watermarkList) {
            watermark->setScreenWatermark(strPolicy);
        }
    }
}

bool DeepinWatermark::isAccessible(const uint pid)
{
    return isValidInvoker(pid);
}

void DeepinWatermark::initConfig()
{
    QFile file(WHITE_CONFIG);
    if (file.exists()) {
        readConfig();
    } else {
        if (file.open(QIODevice::ReadOnly | QIODevice::WriteOnly)) {
            writeConfig();
            file.close();
        }
    }
}

void DeepinWatermark::readConfig()
{
    QSettings settings(WHITE_CONFIG, QSettings::IniFormat);
    settings.setIniCodec("UTF8");
    QStringList whitelist = settings.value("whitelist/names").toString().split(" ");
    for (auto str : whitelist) {
        m_whiteProcess.insert(str);
    }
    // fix: 152445
    if (!m_whiteProcess.contains("udcp-session"))
        m_whiteProcess.insert("udcp-session");
}

void DeepinWatermark::writeConfig()
{
    QSettings settings(WHITE_CONFIG, QSettings::IniFormat);
    settings.setIniCodec("UTF8");
    settings.beginGroup("whitelist");
    if (!settings.contains("names")) {
        settings.setValue("names", QVariant("kwin_x11 kwin_wayland dcmc-session dcmc-guard udcp-backup-restore"));
    }
    settings.endGroup();
}

bool DeepinWatermark::isValidInvoker(const uint &pid)
{
    QFileInfo fileInfo(QString("/proc/%1/exe").arg(pid));
    if (!fileInfo.exists()) {
        return false;
    }
    readConfig();
    QString invokerPath = fileInfo.canonicalFilePath().split("/").last();

    return m_whiteProcess.contains(invokerPath);
}

void DeepinWatermark::desktopResize(QRect rect)
{
    Q_UNUSED(rect);
    if (m_isReset) {
        releaseWatermarks();
        m_showTimer->start(1000);
    }
}

void DeepinWatermark::displayMode(QRect rect)
{
    Q_UNUSED(rect);
    if (m_isReset) {
        releaseWatermarks();
        m_showTimer->start(1000);
    }
}

void DeepinWatermark::displayProperty(QString type, QVariantMap map, QStringList list)
{
    Q_UNUSED(type);
    Q_UNUSED(list);

    if (map.contains("Brightness") ||
        map.contains("ColorTemperatureMode") ||
        map.contains("ColorTemperatureManual") ||
        map.contains("Touchscreens") ||
        map.contains("TouchscreensV2"))
        return;

    if (m_watermark) {
        if (m_watermark->lockFrontStatus())
            return;
    } else {
        if (!m_watermarkList.isEmpty() && m_watermarkList.front()->lockFrontStatus())
            return;
    }

    if (m_isReset) {
        releaseWatermarks();
        m_showTimer->start(1000);
    }
}