/*
 * 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 "log.h"

#include <xcb/xcb.h>
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/X.h>

#include "uace/src/ext/uaceExt.h"
#include "glad/glad.h"
#include <GL/glx.h>

Window desktopId;

int xInitWmJack()
{
    log_warn("xInitWmJack is not available \n");
    return 0;
}

void xDestoryWmJack()
{
    log_warn("xDestoryWmJack is not available \n");
}

static int eHandler(Display *dpy, XErrorEvent *error)
{
    char buffer[BUFSIZ];
    char mesg[BUFSIZ];
    char* mtype = "XlibMessage";
    XGetErrorText(dpy, error->error_code, buffer, BUFSIZ);
    XGetErrorDatabaseText(dpy, mtype, "XError", "X Error", mesg, BUFSIZ);
    fprintf(stderr, "%s: %s\n", mesg, buffer);
    return 0;
}

static bool isMinimized(Display *dpy, WindowId wid)
{
    Atom type;
    int format;
    unsigned long nitems, after;
    Atom *data = 0;
    bool bMinimized = false;

    XSetErrorHandler(eHandler);

    const Atom wmState = XInternAtom(dpy, "_NET_WM_STATE", false);
    const Atom wmMinimizedState = XInternAtom(dpy, "_NET_WM_STATE_HIDDEN", false);

    if (Success == XGetWindowProperty(dpy, wid, wmState, 0, 65536, false, XA_ATOM, &type, &format, &nitems, &after, (unsigned char**)&data)) {
        for (int i = 0; i < nitems; i++) {
            if(data[i] == wmMinimizedState) {
                bMinimized = true;
                break;
            }
        }
    }
    XFree(data);

    return bMinimized;
}

static bool isMaximized(Display *dpy, WindowId wid)
{
    Atom type;
    int format;
    unsigned long nitems, after;
    Atom *data = 0;
    bool bVMaximized = false;
    bool bHMaximized = false;

    XSetErrorHandler(eHandler);

    const Atom wmState = XInternAtom(dpy, "_NET_WM_STATE", false);
    const Atom wmVMaximizedState = XInternAtom(dpy, "_NET_WM_STATE_MAXIMIZED_VERT", false);
    const Atom wmHMaximizedState = XInternAtom(dpy, "_NET_WM_STATE_MAXIMIZED_HORZ", false);

    if (Success == XGetWindowProperty(dpy, wid, wmState, 0, 65536, false, XA_ATOM, &type, &format, &nitems, &after, (unsigned char**)&data)) {
        for (int i = 0; i < nitems; i++) {
            if (data[i] == wmVMaximizedState) {
                bVMaximized = true;
            }
            if (data[i] == wmVMaximizedState) {
                bHMaximized = true;
            }
        }
    }
    XFree(data);

    if (bVMaximized && bHMaximized) {
        return true;
    }

    return false;
}

int xMaximizeWindow(WindowId wid)
{
    const char *winText = xGetWindowText(wid);
    if (winText != NULL && !strcmp(winText, "")) {
        return -1;
    }

    Display *dpy;
    dpy = XOpenDisplay(NULL);

    if (!dpy) {
        fprintf(stderr, "Can not open display\n");
        return -1;
    }

    if (isMinimized(dpy, wid) || isMaximized(dpy, wid)) {
        return -1;
    }

    const Atom wmState = XInternAtom(dpy, "_NET_WM_STATE", false);
    const Atom wmAddState = XInternAtom(dpy, "_NET_WM_STATE_ADD", false);
    const Atom wmVMaximizedState = XInternAtom(dpy, "_NET_WM_STATE_MAXIMIZED_VERT", false);
    const Atom wmHMaximizedState = XInternAtom(dpy, "_NET_WM_STATE_MAXIMIZED_HORZ", false);

    XEvent xev;
    xev.xclient.type = ClientMessage;
    xev.xclient.display = dpy;
    xev.xclient.window = wid;
    xev.xclient.message_type = wmState;
    xev.xclient.format = 32;
    xev.xclient.data.l[0] = 2;
    xev.xclient.data.l[1] = wmVMaximizedState;
    xev.xclient.data.l[2] = wmHMaximizedState;
    xev.xclient.data.l[3] = 1;

    XSendEvent(dpy, DefaultRootWindow(dpy), false, SubstructureRedirectMask | SubstructureNotifyMask, &xev);
    XFlush(dpy);
    XCloseDisplay(dpy);

    return 0;
}

int xMinimizeWindow(WindowId wid)
{
    const char *winText = xGetWindowText(wid);
    if (winText != NULL && !strcmp(winText, "")) {
        return -1;
    }

    Display *dpy;
    dpy = XOpenDisplay(NULL);

    if (!dpy) {
        fprintf(stderr, "Can not open display\n");
        return -1;
    }

    bool bMinimized = isMinimized(dpy, wid);
    if (bMinimized) {
        XCloseDisplay(dpy);
        return -1;
    }

    const Atom wmState = XInternAtom(dpy, "_NET_WM_STATE", false);
    const Atom wmAddState = XInternAtom(dpy, "_NET_WM_STATE_ADD", false);
    const Atom wmMinimizedState = XInternAtom(dpy, "_NET_WM_STATE_HIDDEN", false);

    XEvent xev;
    xev.xclient.type = ClientMessage;
    xev.xclient.display = dpy;
    xev.xclient.window = wid;
    xev.xclient.message_type = wmState;
    xev.xclient.format = 32;
    xev.xclient.data.l[0] = wmAddState;
    xev.xclient.data.l[1] = wmMinimizedState;
    xev.xclient.data.l[2] = 0;
    xev.xclient.data.l[3] = 1;

    XSendEvent(dpy, DefaultRootWindow(dpy), false, SubstructureRedirectMask | SubstructureNotifyMask, &xev);
    XIconifyWindow(dpy, wid, DefaultScreen(dpy));
    XFlush(dpy);
    XCloseDisplay(dpy);

    return 0;
}

int xRestoreWindow(WindowId wid)
{
    const char *winText = xGetWindowText(wid);
    if (winText != NULL && !strcmp(winText, "")) {
        return -1;
    }

    Display *dpy;
    dpy = XOpenDisplay(NULL);

    if (!dpy) {
        fprintf(stderr, "Can not open display\n");
        return -1;
    }

    bool bMinimized = isMinimized(dpy, wid);
    if (bMinimized) {
        const Atom wmActiveWindow = XInternAtom(dpy, "_NET_ACTIVE_WINDOW", false);

        XEvent xev;
        xev.xclient.type = ClientMessage;
        xev.xclient.display = dpy;
        xev.xclient.window = wid;
        xev.xclient.message_type = wmActiveWindow;
        xev.xclient.format = 32;
        xev.xclient.data.l[0] = 1;
        xev.xclient.data.l[1] = CurrentTime;
        xev.xclient.data.l[2] = xev.xclient.data.l[3] = xev.xclient.data.l[4] = 0;

        XSendEvent(dpy, DefaultRootWindow(dpy), false, SubstructureRedirectMask | SubstructureNotifyMask, &xev);
        XFlush(dpy);
        XCloseDisplay(dpy);
        return 0;
    }

    bool bMaximized = isMaximized(dpy, wid);
    if (bMaximized) {
        const Atom wmState = XInternAtom(dpy, "_NET_WM_STATE", false);
        const Atom wmToggleState = XInternAtom(dpy, "_NET_WM_STATE_TOGGLE", false);
        const Atom wmVMaximizedState = XInternAtom(dpy, "_NET_WM_STATE_MAXIMIZED_VERT", false);
        const Atom wmHMaximizedState = XInternAtom(dpy, "_NET_WM_STATE_MAXIMIZED_HORZ", false);

        XEvent xev;
        xev.xclient.type = ClientMessage;
        xev.xclient.display = dpy;
        xev.xclient.window = wid;
        xev.xclient.message_type = wmState;
        xev.xclient.format = 32;
        xev.xclient.data.l[0] = wmToggleState;
        xev.xclient.data.l[1] = wmVMaximizedState;
        xev.xclient.data.l[2] = wmHMaximizedState;
        xev.xclient.data.l[3] = 1;

        XSendEvent(dpy, DefaultRootWindow(dpy), false, SubstructureRedirectMask | SubstructureNotifyMask, &xev);
        XFlush(dpy);
        XCloseDisplay(dpy);
        return 0;
    }

    return -1;
}

char *xGetWindowText(WindowId wid)
{
    Display *dpy;
    dpy = XOpenDisplay(NULL);

    if (!dpy) {
        fprintf(stderr, "Can not open display\n");
        return "";
    }

    Atom nameAtom = XInternAtom(dpy, "_NET_WM_NAME", false);
    Atom utf8Atom = XInternAtom(dpy, "UTF8_STRING", false);
    Atom type;
    int format;
    unsigned long nitems, after;
    unsigned char *data = 0;

    XSetErrorHandler(eHandler);

    if (Success == XGetWindowProperty(dpy, wid, nameAtom, 0, 65536, false, utf8Atom, &type, &format, &nitems, &after, &data)) {
        XCloseDisplay(dpy);
        return data;
    } else {
        XFree(data);
        XCloseDisplay(dpy);
        return "";
    }
}

Size xGetWindowSize(WindowId wid)
{
    Size size;
    size.w = -1;
    size.h = -1;

    const char *winText = xGetWindowText(wid);
    if (winText != NULL && !strcmp(winText, "")) {
        return size;
    }

    Display *dpy;
    dpy = XOpenDisplay(NULL);

    if (!dpy) {
        fprintf(stderr, "Can not open display\n");
        return size;
    }

    XSetErrorHandler(eHandler);

    XWindowAttributes attr;
    XGetWindowAttributes(dpy, wid, &attr);
    size.w = attr.width;
    size.h = attr.height;

    XCloseDisplay(dpy);
    return size;
}

Position xGetWindowPosition(WindowId wid)
{
    Position position;
    position.x = -1;
    position.y = -1;

    const char *winText = xGetWindowText(wid);
    if (winText != NULL && !strcmp(winText, "")) {
        return position;
    }

    Display *dpy;
    dpy = XOpenDisplay(NULL);

    if (!dpy) {
        fprintf(stderr, "Can not open display\n");
        return position;
    }

    int x;
    int y;
    Window child;
    XTranslateCoordinates(dpy, wid, DefaultRootWindow(dpy), 0, 0, &x, &y, &child);
    position.x = x;
    position.y = y;

    XCloseDisplay(dpy);
    return position;
}

WindowId xGetActiveWindowID()
{
    Display *dpy;
    dpy = XOpenDisplay(NULL);

    if (!dpy) {
        fprintf(stderr, "Can not open display\n");
        return 0;
    }

    XSetErrorHandler(eHandler);

    Window wid;
    int revertTo;
    XGetInputFocus(dpy, &wid, &revertTo);
    if (wid == None) {
        printf("no focus window\n");
        XCloseDisplay(dpy);
        return 0;
    }

    XCloseDisplay(dpy);
    return wid;
}

void xsearchWindowID(Display *dpy, Window w, Atom arrtAtom, Atom typeAtom)
{
    Window rootWindow;
    Window parentWindow;
    Window *childList;
    unsigned int childNumber;

    XSetErrorHandler(eHandler);

    if (0 != XQueryTree(dpy, w, &rootWindow, &parentWindow, &childList, &childNumber)) {
        if (childNumber) {
            for (unsigned i = 0; i < childNumber; i++) {
                Atom type;
                int format;
                unsigned long nitems, after;
                unsigned char *data = 0;

                if (Success ==
                    XGetWindowProperty(
                        dpy, childList[i], arrtAtom, 0, 65536, false, XA_ATOM, &type, &format, &nitems, &after, &data)) {
                    if (data) {
                        Atom atom = ((Atom *)data)[0];
                        if (atom == typeAtom) {
                            desktopId = childList[i];
                            XFree(data);
                            return;
                        }
                    }
                }

                XFree(data);
                xsearchWindowID(dpy, childList[i], arrtAtom, typeAtom);
            }
        }
    }
}

WindowId xGetDesktopWindowID()
{
    Display *dpy;
    dpy = XOpenDisplay(NULL);

    if (!dpy) {
        fprintf(stderr, "Can not open display\n");
        return 0;
    }

    XSetErrorHandler(eHandler);

    Atom nameAtom = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE", true);
    Atom desktopAtom = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_DESKTOP", true);
    desktopId = 0;
    xsearchWindowID(dpy, DefaultRootWindow(dpy), nameAtom, desktopAtom);

    XCloseDisplay(dpy);
    return desktopId;
}

int xGetWindowChildren(WindowId wid, WindowId **pChildIDs)
{
    Display *dpy;
    dpy = XOpenDisplay(NULL);

    if (!dpy) {
        fprintf(stderr, "Can not open display\n");
        return -1;
    }

    XSetErrorHandler(eHandler);

    Window rootWindow;
    Window parentWindow;
    Window *childList = NULL;
    unsigned int childNumber;
    if (0 == XQueryTree(dpy, wid, &rootWindow, &parentWindow, &childList, &childNumber)) {
        if (childList) {
            XFree(childList);
        }
        XCloseDisplay(dpy);
        return -1;
    }

    *pChildIDs = childList;

    XCloseDisplay(dpy);
    return childNumber;
}

Position xGetPointerPosition()
{
    Display *dpy;
    dpy = XOpenDisplay(NULL);
    Position position;
    position.x = -1;
    position.y = -1;

    if (!dpy) {
        fprintf(stderr, "Can not open display\n");
        return position;
    }

    XSetErrorHandler(eHandler);

    int screensNumber;
    Bool result;
    Window *rootWindow;
    Window retRootWid;
    Window retChildWid;
    int rootX, rootY;
    int winX, winY;
    unsigned int mask;

    screensNumber = XScreenCount(dpy);
    rootWindow = malloc(sizeof(Window) * screensNumber);

    for (int i = 0; i < screensNumber; i++) {
        rootWindow[i] = XRootWindow(dpy, i);
    }

    for (int i = 0; i < screensNumber; i++) {
        result = XQueryPointer(dpy, rootWindow[i], &retRootWid, &retChildWid, &rootX, &rootY, &winX, &winY, &mask);
        if (result) {
            break;
        }
    }

    if (!result) {
        fprintf(stderr, "No mouse found.\n");
        free(rootWindow);
        XCloseDisplay(dpy);
        return position;
    }

    position.x = rootX;
    position.y = rootY;

    free(rootWindow);
    XCloseDisplay(dpy);
    return position;
}

void xFreeWindowList(WindowId *windownlist)
{
    XFree(windownlist);
}

struct dtk_array *xGetAllWindowStates()
{
    return NULL;
}

WindowState *xGetWindowState(WindowId wid)
{
    return NULL;
}

int xGetWindowPID(WindowId wid)
{
    const char *winText = xGetWindowText(wid);
    if (winText != NULL && !strcmp(winText, "")) {
        return -1;
    }

    Display *dpy;
    dpy = XOpenDisplay(NULL);

    if (!dpy) {
        fprintf(stderr, "Can not open display\n");
        return -1;
    }

    XSetErrorHandler(eHandler);

    int nPID = XUaceExtGetWindowPID(dpy, wid);
    XCloseDisplay(dpy);
    return nPID;
}

static unsigned long *findVirtualRoot(Display *dpy, Window root, unsigned int *num)
{
    Atom type_ret;
    int format_ret;
    unsigned char *prop_ret;
    unsigned long bytes_after, num_ret;
    Atom atom;

    *num = 0;
    atom = XInternAtom(dpy, "_NET_VIRTUAL_ROOTS", False);
    if (!atom)
        return NULL;

    type_ret = None;
    prop_ret = NULL;
    if (XGetWindowProperty(dpy, root, atom, 0, 0x7fffffff, False,
                           XA_WINDOW, &type_ret, &format_ret, &num_ret,
                           &bytes_after, &prop_ret) != Success)
        return NULL;

    if (prop_ret && type_ret == XA_WINDOW && format_ret == 32) {
        *num = num_ret;
        return ((unsigned long *) prop_ret);
    }
    if (prop_ret)
        XFree(prop_ret);

    return NULL;
}

static bool windowHasWMState(Display *dpy, Window win)
{
    static Atom atom_wm_state = None;
    if (!atom_wm_state) {
        atom_wm_state = XInternAtom(dpy, "WM_STATE", false);
        if (!atom_wm_state)
            return false;
    }

    Atom type_ret;
    int format_ret;
    unsigned char *prop_ret;
    unsigned long bytes_after, num_ret;

    type_ret = None;
    prop_ret = NULL;
    XGetWindowProperty(dpy, win, atom_wm_state, 0, 0, False, AnyPropertyType,
                       &type_ret, &format_ret, &num_ret,
                       &bytes_after, &prop_ret);
    if (prop_ret)
        XFree(prop_ret);

    return type_ret != None;
}

static Window findClientInChildren(Display *dpy, Window parent)
{
    Window root, dummy_win;
    Window *children;
    unsigned int n_children;

    if (!XQueryTree(dpy, parent, &root, &dummy_win, &children, &n_children))
        return None;
    if (!children)
        return None;

    parent = None;
    for (int i = (int)n_children - 1; i >= 0; --i) {
        XWindowAttributes attrs;
        XGetWindowAttributes(dpy, children[i], &attrs);
        if (attrs.class != InputOutput || attrs.map_state != IsViewable) {
            children[i] = None;
            continue;
        }
        if (!windowHasWMState(dpy, children[i]))
            continue;

        parent = children[i];
        XFree(children);
        return parent;
    }

    for (int i = (int)n_children - 1; i >= 0; --i) {
        if (children[i] == None)
            continue;
        parent = findClientInChildren(dpy, children[i]);
        if (parent != None)
            break;
    }

    XFree(children);
    return parent;
}

static WindowId findClient(Display *dpy, Window parent)
{
    Window dummy_win;
    int dummy_int;
    unsigned int dummy_uint;

    unsigned int n_root;
    unsigned long *roots = findVirtualRoot(dpy, parent, &n_root);
    Window win;
    for (unsigned int i = 0; i < n_root; ++i) {
        if (parent != roots[i])
            continue;
        XQueryPointer(dpy, parent, &dummy_win, &win, &dummy_int, &dummy_int, &dummy_int, &dummy_int, &dummy_uint);
        if (win == None) {
            XFree(roots);
            return parent;
        }
        parent = win;
        break;
    }
    if (roots)
        XFree(roots);

    if (windowHasWMState(dpy, parent))
        return parent;

    win = findClientInChildren(dpy, parent);
    if (win != None)
        return win;

    return parent;
}

WindowId xGetWindowFromPoint()
{
    Display *dpy = XOpenDisplay(NULL);
    if (!dpy) {
        fprintf(stderr, "Can not open display\n");
        return None;
    }

    Window root = DefaultRootWindow(dpy);
    Window dummy_win;
    int dummy_int;
    unsigned int dummy_uint;

    Window child_ret;
    XQueryPointer(dpy, root, &dummy_win, &child_ret, &dummy_int, &dummy_int, &dummy_int, &dummy_int, &dummy_uint);
    WindowId target = findClient(dpy, child_ret);

    XCloseDisplay(dpy);
    return target;
}

void xShowSplitMenu(int x, int y, int width, int height, WindowId wid)
{
    Display *dpy;
    dpy = XOpenDisplay(NULL);

    if (!dpy) {
        fprintf(stderr, "Can not open display\n");
        return;
    }

    const Atom wmSplitMenu = XInternAtom(dpy, "_WM_TOGGLE_SPLIT_MENU", false);

    XEvent xev;
    xev.xclient.type = ClientMessage;
    xev.xclient.display = dpy;
    xev.xclient.window = wid;
    xev.xclient.message_type = wmSplitMenu;
    xev.xclient.format = 32;
    xev.xclient.data.l[0] = 1;
    xev.xclient.data.l[1] = x;
    xev.xclient.data.l[2] = y;
    xev.xclient.data.l[3] = width;
    xev.xclient.data.l[4] = height;

    XSendEvent(dpy, DefaultRootWindow(dpy), false, SubstructureRedirectMask | SubstructureNotifyMask, &xev);
    XFlush(dpy);
    XCloseDisplay(dpy);
}

void xHideSplitMenu(bool delay, WindowId wid)
{
    Display *dpy;
    dpy = XOpenDisplay(NULL);

    if (!dpy) {
        fprintf(stderr, "Can not open display\n");
        return;
    }

    const Atom wmSplitMenu = XInternAtom(dpy, "_WM_TOGGLE_SPLIT_MENU", false);

    XEvent xev;
    xev.xclient.type = ClientMessage;
    xev.xclient.display = dpy;
    xev.xclient.window = wid;
    xev.xclient.message_type = wmSplitMenu;
    xev.xclient.format = 32;
    xev.xclient.data.l[0] = 0;
    xev.xclient.data.l[1] = delay;

    XSendEvent(dpy, DefaultRootWindow(dpy), false, SubstructureRedirectMask | SubstructureNotifyMask, &xev);
    XFlush(dpy);
    XCloseDisplay(dpy);
}

char *xGetRendererString()
{
    static int attribs[] = {GLX_RGBA, None};

    Display *dpy = XOpenDisplay(NULL);
    if (!dpy) {
        fprintf(stderr, "failed to open display\n");
        return NULL;
    }

    XVisualInfo *visual_info = glXChooseVisual(dpy, DefaultScreen(dpy), attribs);
    if (!visual_info) {
        fprintf(stderr, "no suitable RGB visual\n");
        return NULL;
    }

    GLXContext glx_context = glXCreateContext(dpy, visual_info, NULL, GL_TRUE);
    if (!glx_context) {
        fprintf(stderr, "create context error\n");
        return NULL;
    }

    if (!glXMakeCurrent(dpy, DefaultRootWindow(dpy), glx_context)) {
        fprintf(stderr, "make current context error\n");
        return NULL;
    }

    if (!gladLoadGLLoader(glXGetProcAddress)) {
        fprintf(stderr, "gladLoadGLLoader error\n");
        return NULL;
    }

    char *result = NULL;
    const char *render = glGetString(GL_RENDERER);
    if (render) {
        const int length = strlen(render);
        if (length != 0) {
            result = (char *)malloc(length + 1);
            strcpy(result, render);
            for (char *p = result; *p; ++p) {
                if (isalpha(*p))
                    *p = tolower(*p);
            }
        }
    }

    return result;
}

WindowState xGetSpecificWindowState(uint32_t wid)
{
    WindowState state;
    memset(&state, 0, sizeof(WindowState));
    return state;
}

int xGetAllWindowStatesList(WindowState **states)
{
    if (states)
        *states = NULL;
    return 0;
}