#include "ipc-server.h"

#include <X11/Xatom.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <cstdio>

#include "ipc-protocol.h"
#include "xconnection.h"

using std::string;
using std::vector;

IpcServer::IpcServer(XConnection& xconnection)
    : X(xconnection)
    , nextHookNumber_(0)
{
    // main task of the construtor is to setup the hook window
    hookEventWindow_ = XCreateSimpleWindow(X.display(), X.root(),
                                             42, 42, 42, 42, 0, 0, 0);
    // set wm_class for window
    XClassHint *hint = XAllocClassHint();
    hint->res_name = (char*)HERBST_HOOK_CLASS;
    hint->res_class = (char*)HERBST_HOOK_CLASS;
    XSetClassHint(X.display(), hookEventWindow_, hint);
    XFree(hint);
    // ignore all events for this window
    XSelectInput(X.display(), hookEventWindow_, 0l);
    // set its window id in root window
    XChangeProperty(X.display(), X.root(), X.atom(HERBST_HOOK_WIN_ID_ATOM),
        XA_ATOM, 32, PropModeReplace, (unsigned char*)&hookEventWindow_, 1);
    X.setPropertyCardinal(hookEventWindow_, X.atom(HERBST_IPC_HAS_ERROR), {1});
}

IpcServer::~IpcServer() {
    // remove property from root window
    XDeleteProperty(X.display(), X.root(), X.atom(HERBST_HOOK_WIN_ID_ATOM));
    XDestroyWindow(X.display(), hookEventWindow_);
}

void IpcServer::addConnection(Window window) {
    XSelectInput(X.display(), window, PropertyChangeMask);
}

bool IpcServer::handleConnection(Window win, CallHandler callback) {
    std::experimental::optional<vector<string>> maybeArguments =
            X.getWindowPropertyTextList(win, X.atom(HERBST_IPC_ARGS_ATOM));
    if (!maybeArguments.has_value()) {
        // if the args atom is not present any more then it already has been
        // executed (e.g. after being called by ipc_add_connection())
        return false;
    }
    auto result = callback(maybeArguments.value());
    // send output back
    // here, it is important to use 'long' because XChangeProperty() requires
    // data of type long.
    long status = static_cast<long>(result.exitCode);
    // Mark this command as executed
    XDeleteProperty(X.display(), win, X.atom(HERBST_IPC_ARGS_ATOM));
    X.setPropertyString(win, X.atom(HERBST_IPC_OUTPUT_ATOM), result.output);
    X.setPropertyString(win, X.atom(HERBST_IPC_ERROR_ATOM), result.error);
    // and also set the exit status
    XChangeProperty(X.display(), win, X.atom(HERBST_IPC_STATUS_ATOM),
        XA_ATOM, 32, PropModeReplace, (unsigned char*)&(status), 1);
    XEvent dummy;
    // directly discard all events generated by the above property functions:
    // for the predicate passed to Xlib, we need to set up the closure context manually:
    struct Context {
        Window window;
        Atom argsProperty;
    };
    struct Context context = {win, X.atom(HERBST_IPC_ARGS_ATOM)};
    auto predicate = [](Display*, XEvent* ev, XPointer arg) -> Bool {
        if (ev->type != PropertyNotify) {
            return False;
        }
        struct Context* captured = (struct Context*)arg;
        XPropertyEvent* pev = &(ev->xproperty);
        if (pev->window != captured->window) {
            // only discard events regarding the current ipc client window
            return False;
        }
        // discard the delete event and the property change events for all atoms
        // except for the "args" atom, because there could be a new command coming
        // in from the same ipc client as soon as the old "args" property is deleted.
        return (pev->state == PropertyDelete) || pev->atom != captured->argsProperty;
    };
    while (XCheckIfEvent(X.display(), &dummy, predicate, (XPointer)&context)) {
    }
    return true;
}

bool IpcServer::isConnectable(Window window) {
    return X.getClass(window) == HERBST_IPC_CLASS;
}

void IpcServer::emitHook(vector<string> args) {
    if (args.empty()) {
        // nothing to do
        return;
    }
    static char atom_name[1000];
    snprintf(atom_name, 1000, HERBST_HOOK_PROPERTY_FORMAT, nextHookNumber_);
    X.setPropertyString(hookEventWindow_, X.atom(atom_name), args);
    // set counter for next property
    nextHookNumber_ += 1;
    nextHookNumber_ %= HERBST_HOOK_PROPERTY_COUNT;
}
