/*
 * Copyright (C) 2020 Uniontech Technology Co., Ltd.
 *
 * Author:     xinbo wang <wangxinbo@uniontech.com>
 *
 * Maintainer: xinbo wang <wangxinbo@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 "dtk_wmjack_x11.h"
#include "dtk_wmjack_wayland.h"
#include "log.h"

#include <dbus/dbus-glib.h>
#include <glib.h>
#include <glib/gi18n.h>
#include <glib/gvarianttype.h>
#include <gio/gio.h>

#define DBUS_NAME "com.deepin.wm"
#define DBUS_PATH "/com/deepin/wm"
#define DBUS_INTERFACE "com.deepin.wm"

#define VIDEO_BLACKLIST_PATH "/usr/share/displayjack/wmjack/video-blacklist"

DtkWmJackPtr pWmJack;

int InitDtkWmDisplay()
{
    pWmJack = (DtkWmJackPtr)malloc(sizeof(DtkWmJack));
    memset(pWmJack, 0, sizeof(DtkWmJack));

    if (!pWmJack) {
        log_error("malloc dtk wm jack failed \n");
        return -1;
    }

    if (!pWmJack->xdgSessionType) {
        pWmJack->xdgSessionType = getenv("XDG_SESSION_TYPE");
        pWmJack->isWayland = (pWmJack->xdgSessionType && strcmp(pWmJack->xdgSessionType, "wayland") == 0 ? true : false);
    }

    if (pWmJack->isWayland) {
        log_debug("current enviroment is wayland");
        pWmJack->InitBackend = wInitWmJack;
        pWmJack->DestroyBackend = wDestoryWmJack;

        pWmJack->MaximizeWindow = wMaximizeWindow;
        pWmJack->MinimizeWindow = wMinimizeWindow;
        pWmJack->RestoreWindow = wRestoreWindow;
        pWmJack->GetWindowText = wGetWindowText;
        pWmJack->GetWindowSize = wGetWindowSize;
        pWmJack->GetWindowPosition = wGetWindowPosition;
        pWmJack->GetActiveWindowID = wGetActiveWindowID;
        pWmJack->GetDesktopWindowID = wGetDesktopWindowID;
        pWmJack->GetWindowChildren = wGetWindowChildren;
        pWmJack->GetPointerPosition = wGetPointerPosition;
        pWmJack->FreeWindowList = wFreeWindowList;
        pWmJack->GetAllWindowStates = wGetAllWindowStates;
        pWmJack->GetWindowState = wGetWindowState;
        pWmJack->GetWindowPID = wGetWindowPID;
        pWmJack->GetWindowFromPoint = wGetWindowFromPoint;
        pWmJack->ShowSplitMenu = wShowSplitMenu;
        pWmJack->HideSplitMenu = wHideSplitMenu;
        pWmJack->GetRendererString = wGetRendererString;
        pWmJack->GetSpecificWindowState = wGetSpecificWindowState;
        pWmJack->GetAllWindowStatesList = wGetAllWindowStatesList;
    } else {
        log_debug("current enviroment is x11");
        pWmJack->InitBackend = xInitWmJack;
        pWmJack->DestroyBackend = xDestoryWmJack;

        pWmJack->MaximizeWindow = xMaximizeWindow;
        pWmJack->MinimizeWindow = xMinimizeWindow;
        pWmJack->RestoreWindow = xRestoreWindow;
        pWmJack->GetWindowText = xGetWindowText;
        pWmJack->GetWindowSize = xGetWindowSize;
        pWmJack->GetWindowPosition = xGetWindowPosition;
        pWmJack->GetActiveWindowID = xGetActiveWindowID;
        pWmJack->GetDesktopWindowID = xGetDesktopWindowID;
        pWmJack->GetWindowChildren = xGetWindowChildren;
        pWmJack->GetPointerPosition = xGetPointerPosition;
        pWmJack->FreeWindowList = xFreeWindowList;
        pWmJack->GetAllWindowStates = xGetAllWindowStates;
        pWmJack->GetWindowState = xGetWindowState;
        pWmJack->GetWindowPID = xGetWindowPID;
        pWmJack->GetWindowFromPoint = xGetWindowFromPoint;
        pWmJack->ShowSplitMenu = xShowSplitMenu;
        pWmJack->HideSplitMenu = xHideSplitMenu;
        pWmJack->GetRendererString = xGetRendererString;
        pWmJack->GetSpecificWindowState = xGetSpecificWindowState;
        pWmJack->GetAllWindowStatesList = xGetAllWindowStatesList;
    }

    log_debug("now init backend");
    return pWmJack->InitBackend();
}

void DestoryDtkWmDisplay()
{
    if (!pWmJack) {
        log_error("dnd security has been destroyed \n");
        return;
    }

    pWmJack->DestroyBackend();
    free(pWmJack);
    pWmJack = NULL;
}

bool GetCompositorSwitchStatus(void)
{
    GVariant *result = NULL;
    GVariant *parameters = NULL;                             // Get方法的参数
    const GVariantType *reply_type = G_VARIANT_TYPE("(v)");  // Get函数的返回值类型为"(v)";
    GDBusCallFlags flags = G_DBUS_CALL_FLAGS_NO_AUTO_START;
    int timeout = 5000;    // 超时5秒自动退出
    GError *error = NULL;  // 捕捉错误信息
    double property_value = -1;

    DBusGConnection *bus;
    bus = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL);

    if (!bus) {
        g_error("Couldn't connect to system bus :%s", error->message);
        g_error_free(error);
        error = NULL;
        return false;
    }

    // Get方法需要2个参数：接口名、属性名
    parameters = g_variant_new("(ss)", DBUS_INTERFACE, "compositingEnabled");
    result = g_dbus_connection_call_sync(bus,
                                         DBUS_NAME,
                                         DBUS_PATH,
                                         "org.freedesktop.DBus.Properties",
                                         "Get",
                                         parameters,
                                         reply_type,
                                         flags,
                                         timeout,
                                         NULL,
                                         &error);

    if (error) {
        printf("[property-get-sync] %s\n", error->message);
        g_error_free(error);
        return false;
    }

    if (result) {
        GVariant *value = NULL;
        g_variant_get(result, "(v)", &value);           // Get函数返回值是变体Variant类型
        property_value = g_variant_get_boolean(value);  // 将变体Variant类型转为Bool类型
        g_variant_unref(value);
        g_variant_unref(result);
        result = NULL;
    }
    printf("[property-get-sync] population=%lf\n", property_value);
    return property_value;
}

int MaximizeWindow(WindowId wid)
{
    return pWmJack->MaximizeWindow(wid);
}

int ToggleMultitaskView()
{
    if (GetCompositorSwitchStatus() == false) {
        return -1;
    }

    DBusGConnection *bus;
    DBusGProxy *remote_object;
    GError *error = NULL;
    gboolean parameter = false;

    g_type_init();

    bus = dbus_g_bus_get(DBUS_BUS_SESSION, &error);
    if (!bus) {
        g_error("Couldn't connect to system bus :%s", error->message);
        g_error_free(error);
        error = NULL;
        return -1;
    }

    remote_object = dbus_g_proxy_new_for_name(bus, DBUS_NAME, DBUS_PATH, DBUS_INTERFACE);

    dbus_g_proxy_set_default_timeout(remote_object, 1000000000);
    if (!dbus_g_proxy_call(remote_object, "ShowWorkspace", &error, G_TYPE_INVALID, G_TYPE_INVALID)) {
        g_error("Failed to complete \"%s\":%s", DBUS_INTERFACE, error->message);
        g_error_free(error);
        error = NULL;
        return -1;
    }

    g_object_unref(G_OBJECT(remote_object));

    return 0;
}

int MinimizeWindow(WindowId wid)
{
    return pWmJack->MinimizeWindow(wid);
}

int RestoreWindow(WindowId wid)
{
    return pWmJack->RestoreWindow(wid);
}

char *GetWindowText(WindowId wid)
{
    return pWmJack->GetWindowText(wid);
}

Size GetWindowSize(WindowId wid)
{
    return pWmJack->GetWindowSize(wid);
}

Position GetWindowPosition(WindowId wid)
{
    return pWmJack->GetWindowPosition(wid);
}

WindowId GetActiveWindowID()
{
    return pWmJack->GetActiveWindowID();
}

WindowId GetDesktopWindowID()
{
    return pWmJack->GetDesktopWindowID();
}

int GetWindowChildren(WindowId wid, WindowId **pChildIDs)
{
    return pWmJack->GetWindowChildren(wid, pChildIDs);
}

Position GetPointerPosition()
{
    return pWmJack->GetPointerPosition();
}

void FreeWindowList(WindowId *windownlist)
{
    return pWmJack->FreeWindowList(windownlist);
}

struct dtk_array *GetAllWindowStates()
{
    return pWmJack->GetAllWindowStates();
}

WindowState *GetWindowState(WindowId wid)
{
    return pWmJack->GetWindowState(wid);
}

int GetWindowPID(WindowId wid)
{
    return pWmJack->GetWindowPID(wid);
}

WindowId GetWindowFromPoint()
{
    return pWmJack->GetWindowFromPoint();
}

void ShowSplitMenu(int x, int y, int width, int height)
{
    pWmJack->ShowSplitMenu(x, y, width, height, GetWindowFromPoint());
}

void HideSplitMenu(bool delay, WindowId wid)
{
    pWmJack->HideSplitMenu(delay, wid);
}

static int exec(const char *cmd, char **res)
{
    static const int BUFFER_SIZE = 128;

    if (!res)
        return -1;
    *res = NULL;

    FILE *file = popen(cmd, "r");
    if (!file)
        return -1;

    char buffer[BUFFER_SIZE];
    int cur = 0, length = BUFFER_SIZE;
    *res = (char *)malloc(length);
    **res = 0;
    while (fgets(buffer, BUFFER_SIZE, file)) {
        cur += strlen(buffer);
        if (cur >= length) {
            length += BUFFER_SIZE;
            char *new_res = (char *)malloc(length);
            strcpy(new_res, *res);
            free(*res);
            *res = new_res;
        }
        strcat(*res, buffer);
    }

    pclose(file);
    return 0;
}

int GetDevicePerformanceLevel()
{
    // const char *model_available[] = {
    //     "PGK90", "PGKA0",   // PanguV
    //     "KVK90",    // KLU
    //     "KVK90A",   // KLUA
    //     "KVKA0",    // KLV
    //     "PWC30",    // PanguW
    // };

    // char *model_string = NULL;
    // if (exec("dmidecode | grep -i 'string 4'", &model_string) == 0 && model_string) {
    //     if (strlen(model_string) != 0) {
    //         char *model = strrchr(model_string, ' ') + 1;
    //         for (int i = (int)strlen(model) - 1; i >= 0 && !isalnum(model[i]); --i)
    //             model[i] = '\0';
    //         for (int i = 0; i < sizeof(model_available) / sizeof(const char *); ++i) {
    //             if (strcmp(model_available[i], model) == 0) {
    //                 free(model_string);
    //                 return 1;
    //             }
    //         }
    //     }
    //     free(model_string);
    // }

    // const char *gpu_available[] = { "amd", "radeon", "nvidia", "mtt", "inno", "intel", "zx" };

    // char *render = pWmJack->GetRendererString();
    // bool found = false;
    // if (render) {
    //     if (strlen(render) != 0) {
    //         for (int i = 0, sz = sizeof(gpu_available) / sizeof(const char *); i < sz; ++i) {
    //             if (strstr(render, gpu_available[i])) {
    //                 found = true;
    //                 break;
    //             }
    //         }
    //     }
    //     free(render);
    // }

    // if (!found)
    //     return 0;

    // const char *cpu_available[] = { "intel", "amd" };

    // char *cpu_name = NULL;
    // found = false;
    // if (exec("lscpu | grep 'Model name'", &cpu_name) == 0 && cpu_name) {
    //     if (strlen(cpu_name) != 0) {
    //         for (char *p = cpu_name; *p; ++p) {
    //             if (isalpha(*p))
    //                 *p = tolower(*p);
    //         }
    //         for (int i = 0, sz = sizeof(cpu_available) / sizeof(const char *); i < sz; ++i) {
    //             if (strstr(cpu_name, cpu_available[i])) {
    //                 found = true;
    //                 break;
    //             }
    //         }
    //     }
    //     free(cpu_name);
    // }

    // return found ? 1 : 2;

    FILE *blacklist = fopen(VIDEO_BLACKLIST_PATH, "r");
    if (!blacklist) {
        log_error("Fail to open video-blacklist\n");
        return 1;
    }

    char *renderer = NULL, *cpu_name = NULL;
    int gpu_level = 1, cpu_level = 1;

    enum _State {
        FindTag = 0, GPUList, CPUList
    } state = FindTag;

    char buffer[128];
    while (!feof(blacklist)) {
        fscanf(blacklist, "%s", buffer);
        if (buffer[0] == '[')
            state = FindTag;
        switch (state) {
        case FindTag:
            if (strcmp(buffer, "[GPU]") == 0) {
                state = GPUList;
            } else if (strcmp(buffer, "[CPU]") == 0) {
                state = CPUList;
            }
            break;
        case GPUList:
            if (!renderer) {
                renderer = pWmJack->GetRendererString();
                if (!renderer || strlen(renderer) == 0) {
                    gpu_level = cpu_level = 1;
                    log_error("Fail to get renderer string\n");
                    goto EXIT;
                }
            }
            if (strstr(renderer, buffer)) {
                gpu_level = 0;
                state = FindTag;
            }
            break;
        case CPUList:
            if (!cpu_name) {
                if (exec("lscpu | grep 'Model name'", &cpu_name) != 0 || !cpu_name || strlen(cpu_name) == 0) {
                    gpu_level = cpu_level = 1;
                    log_error("Fail to get cpu model\n");
                    goto EXIT;
                }
                for (char *p = cpu_name; *p; ++p) {
                    if (isalpha(*p))
                        *p = tolower(*p);
                }
            }
            if (strstr(cpu_name, buffer)) {
                cpu_level = 0;
                state = FindTag;
            }
            break;
        default:
            break;
        }
    }

EXIT:
    if (renderer)
        free(renderer);
    if (cpu_name)
        free(cpu_name);
    fclose(blacklist);

    if (cpu_level == 0 && gpu_level == 0) {
        return 0;
    } else if (cpu_level == 1 && gpu_level == 1) {
        return 1;
    }
    return 2;
}

WindowState GetSpecificWindowState(uint32_t wid)
{
    return pWmJack->GetSpecificWindowState(wid);
}

int GetAllWindowStatesList(WindowState **states)
{
    return pWmJack->GetAllWindowStatesList(states);
}