/*
 * Copyright (C) 2022, KylinSoft Co., Ltd.
 *
 * 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.
 *
 * 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 <https://www.gnu.org/licenses/>.
 *
 */

#include <glib.h>
#include <gio/gio.h>

#include "menu-main-window.h"
#include "settings.h"
#include "context-menu-manager.h"
#include "security-function-control.h"

#include <QGuiApplication>
#include <QQuickItem>

#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
#include <private/qtx11extras_p.h>
#else
#include <QX11Info>
#endif

#include <QQmlContext>
#include <QPoint>
#include <QPainterPath>
#include <QTimer>
#include <QCursor>
#include <mutex>

#define UKUI_PANEL_SETTING             "org.ukui.panel.settings"
#define UKUI_PANEL_POSITION_KEY        "panelposition"
#define UKUI_PANEL_SIZE_KEY            "panelsize"
#define UKUI_PANEL_TYPE_KEY            "paneltype"
#define UKUI_DATA_ISLAND_POSITION_KEY  "dataislandposition"
#define UKUI_TOPBAR_SIZE_KEY           "topbarsize"
#define UKUI_PANEL_LENGTH_KEY          "panellength"

namespace UkuiMenu {

void WindowModule::defineModule(const char *uri, int versionMajor, int versionMinor)
{
#if QT_VERSION >= QT_VERSION_CHECK(5, 5, 0)
    qmlRegisterRevision<QWindow, 3>(uri, versionMajor, versionMinor);
    qmlRegisterRevision<QQuickWindow, 2>(uri, versionMajor, versionMinor);
#else
#if QT_VERSION >= QT_VERSION_CHECK(5, 3, 0)
    qmlRegisterRevision<QWindow, 2>(uri, versionMajor, versionMinor);
    qmlRegisterRevision<QQuickWindow, 2>(uri, versionMajor, versionMinor);
#else
#if QT_VERSION >= QT_VERSION_CHECK(5, 1, 0)
    qmlRegisterRevision<QWindow, 2>(uri, versionMajor, versionMinor);
    qmlRegisterRevision<QQuickWindow, 1>(uri, versionMajor, versionMinor);
#else
    qmlRegisterRevision<QWindow, 1>(uri, versionMajor, versionMinor);
#endif
#endif
#endif
}

// ====== PanelSettings ====== //
static std::once_flag onceFlag;
static PanelGSettings* g_instance = nullptr;
static GSettings * m_settings = nullptr;
static GSettingsSchema * m_schema = nullptr;
static const char *m_panelLengthKey = "panellength";
PanelGSettings *PanelGSettings::instance()
{
    std::call_once(onceFlag, [ & ] {
        g_instance = new PanelGSettings();
    });
    return g_instance;
}

int PanelGSettings::getPanelLength(QString screenName)
{
    if (!m_settings || !m_schema) return -1;
    if (!isKeysContain(m_panelLengthKey)) return -1;

    QMap<QString, QVariant> map = getPanelLengthMap();
    if (!map.contains(screenName)) {
        return -1;
    }
    return map.value(screenName).toInt();
}

PanelGSettings::~PanelGSettings()
{
    if (m_settings) {
        g_object_unref(m_settings);
    }
    if (m_schema) {
        g_settings_schema_unref(m_schema);
    }
    g_instance = nullptr;
}

PanelGSettings::PanelGSettings(QObject *parent) : QObject(parent)
{
    GSettingsSchemaSource *source;

    source = g_settings_schema_source_get_default();
    m_schema = g_settings_schema_source_lookup(source, "org.ukui.panel.settings", true);

    if (!m_schema) {
        m_settings = nullptr;
        return;
    }

    m_settings = g_settings_new_with_path("org.ukui.panel.settings", "/org/ukui/panel/settings/");
}

bool PanelGSettings::isKeysContain(const char *key)
{
    if (!m_settings || !m_schema) return false;

        gchar **keys = g_settings_schema_list_keys(m_schema);
        if (g_strv_contains(keys, key)) {
            g_strfreev(keys);
            return true;
        } else {
            g_strfreev(keys);
            return false;
        }
}

QMap<QString, QVariant> PanelGSettings::getPanelLengthMap()
{
    GVariant *gvalue = g_settings_get_value(m_settings, m_panelLengthKey);

    GVariantIter iter;
    QMap<QString, QVariant> map;
    const gchar *key;
    size_t str_len;
    GVariant *val = NULL;
    g_variant_iter_init (&iter, gvalue);
    QVariant qvar;

    while (g_variant_iter_next (&iter, "{&sv}", &key, &val)) {
        if (g_variant_is_of_type(val, G_VARIANT_TYPE_UINT32)) {
            qvar = QVariant::fromValue(static_cast<quint32>(g_variant_get_uint32(val)));
            map.insert(key, qvar);
        }
    }

    g_variant_unref(gvalue);

    return map;
}

// ====== WindowGeometryHelper ====== //
WindowGeometryHelper::WindowGeometryHelper(QObject *parent) : QObject(parent)
{
    initPanelSetting();
//    initScreenMonitor();
    updateGeometry();

    connect(MenuSetting::instance(), &MenuSetting::changed, this, [this] (const QString& key) {
        if (key == MENU_WIDTH || key == MENU_HEIGHT) {
            updateGeometry();
        }
    });
}

void WindowGeometryHelper::updateGeometry()
{
    if (!m_primaryScreen) {
        return;
    }

    QRect screenRect = m_primaryScreen->geometry(), normalMaskRect, fullRect;
    int width = MenuSetting::instance()->get(MENU_WIDTH).toInt();
    int height = MenuSetting::instance()->get(MENU_HEIGHT).toInt();
    int margin = MenuSetting::instance()->get(MENU_MARGIN).toInt();

    //上: 1, 下: 0, 左: 2, 右: 3
    switch (m_panelPos) {
        default:
        case 0: {
            fullRect.setTopLeft(screenRect.topLeft());
            fullRect.setSize({screenRect.width(), screenRect.height() - m_panelSize});

            QSize normalSize(qMin<int>(fullRect.width() - margin*2, width), qMin<int>(fullRect.height() - margin*2, height));
            normalMaskRect.setTopLeft({margin, fullRect.height() - normalSize.height() - margin});
            normalMaskRect.setSize(normalSize);
            break;
        }
        case 1: {
            fullRect.setTopLeft({screenRect.x(), screenRect.y() + m_panelSize});
            fullRect.setSize({screenRect.width(), screenRect.height() - m_panelSize});

            normalMaskRect.setTopLeft({margin, margin});
            normalMaskRect.setSize({qMin<int>(fullRect.width() - margin*2, width), qMin<int>(fullRect.height() - margin*2, height)});
            break;
        }
        case 2: {
            fullRect.setTopLeft({screenRect.x() + m_panelSize, screenRect.y()});
            fullRect.setSize({screenRect.width() - m_panelSize, screenRect.height()});

            normalMaskRect.setTopLeft({margin, margin});
            normalMaskRect.setSize({qMin<int>(fullRect.width() - margin*2, width), qMin<int>(fullRect.height() - margin*2, height)});
            break;
        }
        case 3: {
            fullRect.setTopLeft(screenRect.topLeft());
            fullRect.setSize({screenRect.width() - m_panelSize, screenRect.height()});

            QSize normalSize(qMin<int>(fullRect.width() - margin*2, width), qMin<int>(fullRect.height() - margin*2, height));
            normalMaskRect.setTopLeft({fullRect.width() - normalSize.width() - margin, margin});
            normalMaskRect.setSize(normalSize);
            break;
        }
    }

    m_normalGeometry = normalMaskRect;
    m_fullScreenGeometry = fullRect;

    Q_EMIT geometryChanged();
}

void WindowGeometryHelper::initPanelSetting()
{
    if (!MenuSetting::instance()->get(FOLLOW_UKUI_PANEL).toBool()) {
        return;
    }

    const QByteArray id(UKUI_PANEL_SETTING);
    if (QGSettings::isSchemaInstalled(id)) {
        QGSettings *setting = new QGSettings(id, QByteArray(), this);

        QStringList keys = setting->keys();
        if (keys.contains(UKUI_PANEL_POSITION_KEY)) {
            m_panelPos = setting->get(UKUI_PANEL_POSITION_KEY).toInt();
        }
        if (keys.contains(UKUI_PANEL_SIZE_KEY)) {
            m_panelSize = setting->get(UKUI_PANEL_SIZE_KEY).toInt();
        }

        connect(setting, &QGSettings::changed, this, [this, setting] (const QString& key) {
            if (key == UKUI_PANEL_POSITION_KEY || key == UKUI_PANEL_SIZE_KEY) {
                m_panelPos = setting->get(UKUI_PANEL_POSITION_KEY).toInt();
                m_panelSize = setting->get(UKUI_PANEL_SIZE_KEY).toInt();
                updateGeometry();
            }
        });
    }
}

void WindowGeometryHelper::initScreenMonitor()
{
    updatePrimaryScreen(QGuiApplication::primaryScreen());
    connect(qGuiApp, &QGuiApplication::primaryScreenChanged, this, [this] (QScreen *screen) {
        updatePrimaryScreen(screen);
        updateGeometry();
    });
}

void WindowGeometryHelper::updatePrimaryScreen(QScreen *screen)
{
    if (!screen) {
        return;
    }

    if (m_primaryScreen) {
        m_primaryScreen->disconnect(this);
    }

    m_primaryScreen = screen;
    connect(m_primaryScreen, &QScreen::geometryChanged, this, &WindowGeometryHelper::updateGeometry);
}

const QRect &WindowGeometryHelper::fullScreenGeometry()
{
    return m_fullScreenGeometry;
}

const QRect &WindowGeometryHelper::normalGeometry()
{
    return m_normalGeometry;
}

const int WindowGeometryHelper::getPanelPos()
{
    return m_panelPos;
}

//======MenuWindow======//
MenuWindow::MenuWindow(QWindow *parent) : QQuickView(parent)
{
    init();
}

MenuWindow::MenuWindow(QQmlEngine *engine, QWindow *parent)
    : QQuickView(engine, parent)
{
    init();
}

void MenuWindow::init()
{
    initPanelSetting();

    connect(MenuSetting::instance(), &MenuSetting::changed, this, [this] (const QString& key) {
        if (key == MENU_WIDTH || key == MENU_HEIGHT) {
            updateGeometry();
        }
    });
    m_screen = QGuiApplication::primaryScreen();
    setScreen(m_screen);
    connect(m_screen, &QScreen::geometryChanged, this, &MenuWindow::updateGeometry, Qt::UniqueConnection);

    connect(qGuiApp, &QGuiApplication::layoutDirectionChanged, this, &MenuWindow::updateGeometry, Qt::UniqueConnection);
    connect(qGuiApp, &QGuiApplication::screenRemoved, this, [&](QScreen *qScreen) {
        if(qScreen == screen()) {
            activeMenuWindow(false);
        }
    }, Qt::UniqueConnection);

    setTitle(QCoreApplication::applicationName());
    setResizeMode(SizeRootObjectToView);
    setColor("transparent");

    //setWindowState(Qt::WindowMaximized);
    setFlags(flags() | Qt::Window | Qt::FramelessWindowHint);
    m_windowProxy = new UkuiQuick::WindowProxy(this);
    m_windowProxy->setWindowType(UkuiQuick::WindowType::SystemWindow);

    // 访问窗口api
    rootContext()->setContextProperty("mainWindow", this);

    rootContext()->setContextProperty("isLiteMode", GlobalSetting::instance()->get(GlobalSetting::IsLiteMode));
    connect(GlobalSetting::instance(), &GlobalSetting::styleChanged, this , [this] (const GlobalSetting::Key& key) {
        if (key == GlobalSetting::EffectEnabled) {
            Q_EMIT effectEnabledChanged();
        } else if (key == GlobalSetting::Transparency) {
            Q_EMIT transparencyChanged();
        } else if (key == GlobalSetting::IsLiteMode) {
            rootContext()->setContextProperty("isLiteMode", GlobalSetting::instance()->get(key));
        }
    });

    updateGeometry();

    ContextMenuManager::instance()->setMainWindow(this);
}

void MenuWindow::initPanelSetting()
{
    if (!MenuSetting::instance()->get(FOLLOW_UKUI_PANEL).toBool()) {
        return;
    }

    const QByteArray id(UKUI_PANEL_SETTING);
    if (QGSettings::isSchemaInstalled(id)) {
        m_setting = new QGSettings(id, QByteArray(), this);

        QStringList keys = m_setting->keys();
        if (keys.contains(UKUI_PANEL_POSITION_KEY)) {
            m_panelPos = m_setting->get(UKUI_PANEL_POSITION_KEY).toInt();
        }

        if (keys.contains(UKUI_PANEL_SIZE_KEY)) {
            m_panelSize = m_setting->get(UKUI_PANEL_SIZE_KEY).toInt();
        }

        if (keys.contains(UKUI_PANEL_TYPE_KEY)) {
            m_panelType = m_setting->get(UKUI_PANEL_TYPE_KEY).toInt();
        } else {
            m_panelType = 0;
        }

        if (keys.contains(UKUI_DATA_ISLAND_POSITION_KEY)) {
            m_dataIslandPosition = m_setting->get(UKUI_DATA_ISLAND_POSITION_KEY).toInt();
        }

        if (keys.contains(UKUI_TOPBAR_SIZE_KEY)) {
            m_topbarSize = m_setting->get(UKUI_TOPBAR_SIZE_KEY).toInt();
        }

        connect(m_setting, &QGSettings::changed, this, [this] (const QString& key) {
            if (key == UKUI_PANEL_POSITION_KEY) {
                m_panelPos = m_setting->get(UKUI_PANEL_POSITION_KEY).toInt();
                updateGeometry();
            } else if (key == UKUI_PANEL_SIZE_KEY) {
                m_panelSize = m_setting->get(key).toInt();
                updateGeometry();
            } else if (key == UKUI_PANEL_TYPE_KEY) {
                m_panelType = m_setting->get(UKUI_PANEL_TYPE_KEY).toInt();
                updateGeometry();
            } else if (key == UKUI_DATA_ISLAND_POSITION_KEY) {
                m_dataIslandPosition = m_setting->get(UKUI_DATA_ISLAND_POSITION_KEY).toInt();
                updateGeometry();
            } else if (key == UKUI_TOPBAR_SIZE_KEY) {
                m_topbarSize = m_setting->get(UKUI_TOPBAR_SIZE_KEY).toInt();
                updateGeometry();
            } else if (key == UKUI_PANEL_LENGTH_KEY) {
                updateGeometry();
            }
        });
    }
}

void MenuWindow::updateGeometry()
{
    updateCurrentScreenGeometry();

    if (m_fullScreenGeometry.isEmpty()) return;
    setMinimumSize(m_fullScreenGeometry.size());
    setMaximumSize(m_fullScreenGeometry.size());
    setGeometry(m_fullScreenGeometry);
    m_windowProxy->setPosition(m_fullScreenGeometry.topLeft());

    updateGeometryOfMask();
    changeWindowBlurRegion( m_maskGeometry.x(), m_maskGeometry.y(), m_maskGeometry.width(), m_maskGeometry.height(), 16);
}

bool MenuWindow::isFullScreen() const
{
    return m_isFullScreen;
}

/**
 * beforeFullScreenChanged -> (qml)onWidthChanged -> fullScreenChanged
 * @param isFullScreen
 */
void MenuWindow::setFullScreen(bool isFullScreen)
{
    if (m_isFullScreen == isFullScreen) {
        return;
    }

    Q_EMIT beforeFullScreenChanged();

    m_isFullScreen = isFullScreen;

    updateGeometryOfMask();

    // 更新contentItem尺寸
    QEvent event(QEvent::Resize);
    QCoreApplication::sendEvent(this, &event);

    Q_EMIT fullScreenChanged();
}

void MenuWindow::changeWindowBlurRegion(qreal x, qreal y, qreal w, qreal h, qreal radius)
{
    QPainterPath path;
    path.addRoundedRect(x, y, w, h, radius, radius);
    m_windowProxy->setBlurRegion(true, QRegion(path.toFillPolygon().toPolygon()));
}

void MenuWindow::exitFullScreen()
{
    Q_EMIT beforeFullScreenExited();
}

void MenuWindow::exposeEvent(QExposeEvent *event)
{
    QQuickView::exposeEvent(event);
}

void MenuWindow::focusOutEvent(QFocusEvent *event)
{
    // void QQuickWindow::focusOutEvent(QFocusEvent *ev) { Q_D(QQuickWindow); if (d->contentItem)  d->contentItem->setFocus(false, ev->reason()); }
    if (event->reason() == Qt::PopupFocusReason) {
        return;
    }
    QQuickView::focusOutEvent(event);
    this->setVisible(false);
}

bool MenuWindow::event(QEvent *event)
{
    if (event->type() == QEvent::Show) {
        if (QX11Info::isPlatformX11()) {
            requestActivate();
        }
    } else if (event->type() == QEvent::MouseButtonPress) {
        ContextMenuManager::instance()->closeMenu();
    }

    return QQuickView::event(event);
}

bool MenuWindow::effectEnabled() const
{
    return GlobalSetting::instance()->get(GlobalSetting::EffectEnabled).toBool();
}

double MenuWindow::transparency() const
{
    return GlobalSetting::instance()->get(GlobalSetting::Transparency).toDouble();
}

int MenuWindow::panelPos() const
{
    return m_panelPos;
}

bool MenuWindow::editMode() const
{
    return m_editMode;
}

void MenuWindow::setEditMode(bool mode)
{
    if (mode == m_editMode) {
            return;
        }

    m_editMode = mode;
    Q_EMIT editModeChanged();
}

void MenuWindow::updateGeometryOfMask()
{
    if (m_isFullScreen) {
        m_maskGeometry = QRect(0, 0, m_fullScreenGeometry.width(), m_fullScreenGeometry.height());
    } else {
        m_maskGeometry = m_normalGeometry;
    }

    setMask(m_maskGeometry);
}

void MenuWindow::updateCurrentScreenGeometry()
{
    if (!m_screen || m_screen->geometry().isEmpty()) {
        return;
    }

    // qt的可用区域有问题，暂时使用任务栏尺寸计算
    //QRect normalMaskRect, fullRect = screen()->availableGeometry();
    QRect normalMaskRect, fullRect = m_screen->geometry();
    int width = MenuSetting::instance()->get(MENU_WIDTH).toInt();
    int height = MenuSetting::instance()->get(MENU_HEIGHT).toInt();
    int margin = MenuSetting::instance()->get(MENU_MARGIN).toInt();
    UkuiQuick::WindowProxy::SlideFromEdge slideFromEdge = UkuiQuick::WindowProxy::NoEdge;
    bool isMirrored = qGuiApp->layoutDirection() == Qt::LayoutDirection::RightToLeft;

    if (m_panelType == 0) {
        //经典任务栏
        //上: 1, 下: 0, 左: 2, 右: 3
        switch (m_panelPos) {
            default:
            case 0: {
                fullRect.adjust(0, 0, 0, -m_panelSize);

                QSize normalSize(qMin<int>(fullRect.width() - margin*2, width), qMin<int>(fullRect.height() - margin*2, height));
                if (isMirrored) {
                    normalMaskRect.setTopLeft({fullRect.width() - margin - width, fullRect.height() - normalSize.height() - margin});
                } else {
                    normalMaskRect.setTopLeft({margin, fullRect.height() - normalSize.height() - margin});
                }
                normalMaskRect.setSize(normalSize);
                slideFromEdge = UkuiQuick::WindowProxy::BottomEdge;
                break;
            }
            case 1: {
                fullRect.adjust(0, m_panelSize, 0, 0);

                QSize normalSize(qMin<int>(fullRect.width() - margin*2, width), qMin<int>(fullRect.height() - margin*2, height));
                if (isMirrored) {
                    normalMaskRect.setTopLeft({fullRect.width() - margin - width, margin});
                } else {
                    normalMaskRect.setTopLeft({margin, margin});
                }
                normalMaskRect.setSize(normalSize);
                slideFromEdge = UkuiQuick::WindowProxy::TopEdge;
                break;
            }
            case 2: {
                fullRect.adjust(m_panelSize, 0, 0, 0);

                normalMaskRect.setTopLeft({margin, margin});
                normalMaskRect.setSize({qMin<int>(fullRect.width() - margin*2, width), qMin<int>(fullRect.height() - margin*2, height)});
                slideFromEdge = UkuiQuick::WindowProxy::LeftEdge;
                break;
            }
            case 3: {
                fullRect.adjust(0, 0, -m_panelSize, 0);

                QSize normalSize(qMin<int>(fullRect.width() - margin*2, width), qMin<int>(fullRect.height() - margin*2, height));
                normalMaskRect.setTopLeft({fullRect.width() - normalSize.width() - margin, margin});
                normalMaskRect.setSize(normalSize);
                slideFromEdge = UkuiQuick::WindowProxy::RightEdge;
                break;
            }
        }
    } else if (m_panelType == 1) {
        //三岛任务栏
        if (m_dataIslandPosition == 0) {
            fullRect.adjust(0, 0, 0, -m_panelSize);

            QSize normalSize(qMin<int>(fullRect.width() - margin*2, width), qMin<int>(fullRect.height() - margin*2, height));
            if (isMirrored) {
                normalMaskRect.setTopLeft({fullRect.width() -
                                           (fullRect.width() - PanelGSettings::instance()->getPanelLength(m_screen->name())) / 2 - width ,
                                           fullRect.height() - normalSize.height() - margin});
            } else {
                normalMaskRect.setTopLeft({(fullRect.width() - PanelGSettings::instance()->getPanelLength(m_screen->name())) / 2,
                                           fullRect.height() - normalSize.height() - margin});
            }
            normalMaskRect.setSize(normalSize);
            slideFromEdge = UkuiQuick::WindowProxy::BottomEdge;
        } else if (m_dataIslandPosition == 1) {
            fullRect.adjust(0, m_topbarSize, 0, 0);

            QSize normalSize(qMin<int>(fullRect.width() - margin*2, width), qMin<int>(fullRect.height() - margin*2, height));
            if (isMirrored) {
                normalMaskRect.setTopLeft({fullRect.width() - margin - width, margin});
            } else {
                normalMaskRect.setTopLeft({margin, margin});
            }
            normalMaskRect.setSize(normalSize);
            slideFromEdge = UkuiQuick::WindowProxy::TopEdge;
        }
    }

    m_normalGeometry = normalMaskRect;
    m_fullScreenGeometry = fullRect;

    m_windowProxy->slideWindow(slideFromEdge, 0);

    Q_EMIT panelPosChanged();
    Q_EMIT normalRectChanged();
}

QRect MenuWindow::normalRect() const
{
    return m_normalGeometry;
}

void MenuWindow::activeMenuWindow(bool active)
{
    if (active == isVisible()) {
        return;
    }

    if (active) {
        if (SecurityFunctionControl::instance()->disable()) return;
        if (m_screen != UkuiQuick::WindowProxy::currentScreen()) {
            if (m_screen) {
                m_screen->disconnect(this);
            }
            m_screen = UkuiQuick::WindowProxy::currentScreen();
            this->setScreen(m_screen);
            connect(m_screen, &QScreen::geometryChanged, this, &MenuWindow::updateGeometry, Qt::UniqueConnection);
            updateGeometry();
        }
    }

    setVisible(active);
}

} // UkuiMenu
