/*
 * 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 "x11.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fstream>
#include <pwd.h>
#include <unistd.h>

X11WindowSystem::X11WindowSystem()
{
    m_display = XOpenDisplay(NULL);
}

X11WindowSystem::~X11WindowSystem()
{
    if (m_display) {
        XCloseDisplay(m_display);
    }
}

Window X11WindowSystem::getRootWindow() const
{
    if (!m_display)
        return 0;

    return XDefaultRootWindow(m_display);
}

Atom X11WindowSystem::parseAtom(const char* name, bool only_if_exists)
{
    if (!m_display)
        return 0;

    return XInternAtom(m_display, name, only_if_exists);
}

bool X11WindowSystem::isViewableWindow(Window win)
{
    if (!m_display)
        return false;

    Bool ok;
    XWindowAttributes xwa;

    XGetWindowAttributes(m_display, win, &xwa);

    ok = (xwa.c_class == InputOutput) && (xwa.map_state != IsUnviewable);

    return ok;
}

template <typename T>
std::vector<T> X11WindowSystem::getWindowProperty(Window window,
                                      const std::string &atomName,
                                      Atom atomType) const
{
    if (!m_display) {
        return std::vector<T>();
    }

    std::vector<T> propertiesVector;

    Atom atom = XInternAtom(m_display, atomName.c_str(), True);
    if (atom == None) {
        return propertiesVector;
    }

    long offset = 0;
    long offsetSize = 100;
    Atom atomRet;
    int size;
    unsigned long numItems;
    unsigned long bytesAfterReturn;
    unsigned char *ret;
    int status;

    do {
        status = XGetWindowProperty(this->m_display, window, atom, offset, offsetSize,
                                    False, atomType, &atomRet, &size, &numItems,
                                    &bytesAfterReturn, &ret);
        if (status == Success) {
            auto properties = reinterpret_cast<T *>(ret);
            for (int i = 0; i < numItems; i++) {
                propertiesVector.push_back(properties[i]);
            }
            XFree(ret);
            offset += offsetSize;
        }
    } while (status == Success && bytesAfterReturn != 0 && numItems != 0);

    return propertiesVector;
}

template <>
std::vector<char> X11WindowSystem::getWindowProperty<char>(Window window,
                                      const std::string &atomName,
                                      Atom atomType) const
{
    if (!m_display) {
        return std::vector<char>();
    }

    std::vector<char> propertiesVector;

    Atom atom = XInternAtom(m_display, atomName.c_str(), True);
    if (atom == None) {
        return propertiesVector;
    }

    long offset = 0;
    long offsetSize = 100;
    Atom atomRet;
    int size;
    unsigned long numItems;
    unsigned long bytesAfterReturn;
    unsigned char *ret;
    int status;

    do {
        status = XGetWindowProperty(this->m_display, window, atom, offset, offsetSize,
                                    False, atomType, &atomRet, &size, &numItems,
                                    &bytesAfterReturn, &ret);
        if (status == Success) {
            auto properties = reinterpret_cast<char *>(ret);
            for (int i = 0; i < numItems; i++) {
                propertiesVector.push_back(properties[i]);
            }
            XFree(ret);
            offset += offsetSize;
        }
    } while (status == Success && bytesAfterReturn != 0 && numItems != 0);

    return propertiesVector;
}

Window X11WindowSystem::getTopLevelWindow(Window window)
{
    if (!m_display) {
        return 0;
    }

    // Figure out to which top-level window "window" belongs to
    auto pair = this->findTopLevelWindowInChildren(window, getAllTopLevelWindows());
    Window topLevelWindow = pair.second;
    return topLevelWindow;
}

std::vector<Window> X11WindowSystem::getAllTopLevelWindows()
{
    if (!m_display) {
        return std::vector<Window>();
    }

    m_topLevelWindows.clear();
    execCmdToGetWindow("xwininfo  -tree -root | grep -v grep | grep -E \" 0x[0-9 | a-f]+.+-.+QRDClient.+\" | awk  '{print $1}'");
    return m_topLevelWindows;
}

std::pair<bool, Window> X11WindowSystem::findTopLevelWindowInChildren(
    Window window, const std::vector<Window> &topLevelWindows) const
{
    if (!m_display) {
        return std::make_pair(false, window);
    }
    // If "window" is in the "topLevelWindows" return it
    if (std::find(topLevelWindows.begin(), topLevelWindows.end(), window) !=
      topLevelWindows.end()) {
        return std::make_pair(true, window);
    }

    // Otherwise, find the top level window in the "window" children
    Window root;
    Window parent;
    Window *children;
    unsigned int numChildren;

    int status = XQueryTree(this->m_display, window, &root, &parent, &children,
                          &numChildren);
    if (status == 0) {
        return std::make_pair(true, None);
    }

    Window ret = None;
    bool found = false;
    int index = 0;
    while (!found && index < numChildren) {
        auto pair = this->findTopLevelWindowInChildren(children[index],
                                                       topLevelWindows);
        found = pair.first;
        ret = pair.second;
        index++;
    }

    if (children != nullptr) {
        XFree(children);
    }

    return std::make_pair(found, ret);
}

int X11WindowSystem::execCmdToGetWindow(const char *cmd)
{
    FILE *pipe = popen(cmd, "r");
    if (!pipe)
    {
        printf("popen error\n");
        return -1;
    }
    size_t ret = 0;
    char buf[15] = {0};
    while (fgets(buf, 15, pipe) != NULL)
    {
        std::string str(buf);
        m_topLevelWindows.push_back(stoi(str,0,16));
    }
    pclose(pipe);
    return 0;
}