/*
    SPDX-FileCopyrightText: 2003 Lubos Lunak <l.lunak@kde.org>

    SPDX-License-Identifier: MIT
*/

#include "kxerrorhandler_p.h"
#include <config-kwindowsystem.h>

#include <fixx11h.h>

#include "netwm_def.h"

#include <stdio.h>

#include <QByteArray>
#include <QString>

class KXErrorHandlerPrivate
{
public:
    KXErrorHandlerPrivate(Display *dpy)
        : first_request(XNextRequest(dpy))
        , display(dpy)
        , was_error(false)
    {
    }
    unsigned long first_request;
    Display *display;
    bool was_error;
    XErrorEvent error_event;
};

KXErrorHandler **KXErrorHandler::handlers = nullptr;
int KXErrorHandler::pos = 0;
int KXErrorHandler::size = 0;

KXErrorHandler::KXErrorHandler(Display *dpy)
    : user_handler1(nullptr)
    , user_handler2(nullptr)
    , old_handler(XSetErrorHandler(handler_wrapper))
    , d(new KXErrorHandlerPrivate(dpy))
{
    addHandler();
}

KXErrorHandler::KXErrorHandler(int (*handler)(Display *, XErrorEvent *), Display *dpy)
    : user_handler1(nullptr)
    , user_handler2(handler)
    , old_handler(XSetErrorHandler(handler_wrapper))
    , d(new KXErrorHandlerPrivate(dpy))
{
    addHandler();
}

KXErrorHandler::~KXErrorHandler()
{
    XSetErrorHandler(old_handler);
    Q_ASSERT_X(this == handlers[pos - 1], "KXErrorHandler", "out of order");
    --pos;
    delete d;
}

void KXErrorHandler::addHandler()
{
    if (size == pos) {
        size += 16;
        handlers = static_cast<KXErrorHandler **>(realloc(handlers, size * sizeof(KXErrorHandler *)));
    }
    handlers[pos++] = this;
}

bool KXErrorHandler::error(bool sync) const
{
    if (sync) {
        XSync(d->display, False);
    }
    return d->was_error;
}

XErrorEvent KXErrorHandler::errorEvent() const
{
    return d->error_event;
}

int KXErrorHandler::handler_wrapper(Display *dpy, XErrorEvent *e)
{
    --pos;
    int ret = handlers[pos]->handle(dpy, e);
    ++pos;
    return ret;
}

int KXErrorHandler::handle(Display *dpy, XErrorEvent *e)
{
    if (dpy == d->display
        // e->serial >= d->first_request , compare like X timestamps to handle wrapping
        && NET::timestampCompare(e->serial, d->first_request) >= 0) {
        // it's for us
        // qDebug( "Handling: %p", static_cast< void* >( this ));
        bool error = false;
        if (user_handler1 != nullptr) {
            if (user_handler1(e->request_code, e->error_code, e->resourceid)) {
                error = true;
            }
        } else if (user_handler2 != nullptr) {
            if (user_handler2(dpy, e) != 0) {
                error = true;
            }
        } else { // no handler set, simply set that there was an error
            error = true;
        }
        if (error && !d->was_error) {
            // only remember the first
            d->was_error = true;
            d->error_event = *e;
        }
        return 0;
    }
    // qDebug( "Going deeper: %p", static_cast< void* >( this ));
    return old_handler(dpy, e);
}

QByteArray KXErrorHandler::errorMessage(const XErrorEvent &event, Display *dpy)
{
    // "Error: <error> (<value>), Request: <request>(<value>), Resource: <value>"
    QByteArray ret;
    char tmp[256];
#if 0 // see below
    char num[ 256 ];
    if (event.request_code < 128)  // core request
#endif
    {
        XGetErrorText(dpy, event.error_code, tmp, 255);
        if (char *paren = strchr(tmp, '(')) { // the explanation in parentheses just makes
            *paren = '\0'; // it more verbose and is not really useful
        }
        // the various casts are to get overloads non-ambiguous :-/
        /*
                ret = QByteArray("error: ") + (const char *)tmp + '[' + QByteArray::number(event.error_code) + ']';
                sprintf(num, "%d", event.request_code);
                XGetErrorDatabaseText(dpy, "XRequest", num, "<unknown>", tmp, 256);
                ret += QByteArray(", request: ") + (const char *)tmp + '[' + QByteArray::number(event.request_code) + ']';
                if (event.resourceid != 0) {
                    ret += QByteArray(", resource: 0x") + QByteArray::number((qlonglong)event.resourceid, 16);
                }
        */
    }
#if 0
    else { // extensions
        // XGetErrorText() currently has a bug that makes it fail to find text
        // for some errors (when error==error_base), also XGetErrorDatabaseText()
        // requires the right extension name, so it is needed to get info about
        // all extensions. However that is almost impossible:
        // - Xlib itself has it, but in internal data.
        // - Opening another X connection now can cause deadlock with server grabs.
        // - Fetching it at startup means a bunch of roundtrips.
        // So if this becomes more useful in the future, do the roundtrips at startup,
        // or fetch it in kded and export as an env.var or something.
        Display *dpy2 = XOpenDisplay(XDisplayString(dpy));
        int nextensions;
        char **extensions = XListExtensions(dpy2, &nextensions);
        int *majors = nullptr;
        int *error_bases = nullptr;
        if (extensions == nullptr) {
            nextensions = 0;
        } else {
            majors = new int[ nextensions ];
            error_bases = new int[ nextensions ];
            for (int i = 0;
                    i < nextensions;
                    ++i) {
                int dummy;
                if (!XQueryExtension(dpy2, extensions[ i ], &majors[ i ], &dummy, &error_bases[ i ])) {
                    majors[ i ] = 0;
                    error_bases[ i ] = 0;
                }
            }
        }
        XGetErrorText(dpy, event.error_code, tmp, 255);
        int index = -1;
        int base = 0;
        for (int i = 0;
                i < nextensions;
                ++i)
            if (error_bases[ i ] != 0
                    && event.error_code >= error_bases[ i ] && (index == -1 || error_bases[ i ] > base)) {
                index = i;
                base = error_bases[ i ];
            }
        if (tmp == QString::number(event.error_code)) { // XGetErrorText() failed,
            // or it has a bug that causes not finding all errors, check ourselves
            if (index != -1) {
                qsnprintf(num, 255, "%s.%d", extensions[ index ], event.error_code - base);
                XGetErrorDatabaseText(dpy, "XProtoError", num, "<unknown>", tmp, 255);
            } else {
                strcpy(tmp, "<unknown>");
            }
        }
        if (char *paren = strchr(tmp, '(')) {
            *paren = '\0';
        }
        if (index != -1)
            ret = QByteArray("error: ") + (const char *)tmp + '[' + (const char *)extensions[ index ]
                  + '+' + QByteArray::number(event.error_code - base) + ']';
        else {
            ret = QByteArray("error: ") + (const char *)tmp + '[' + QByteArray::number(event.error_code) + ']';
        }
        tmp[ 0 ] = '\0';
        for (int i = 0;
                i < nextensions;
                ++i)
            if (majors[ i ] == event.request_code) {
                qsnprintf(num, 255, "%s.%d", extensions[ i ], event.minor_code);
                XGetErrorDatabaseText(dpy, "XRequest", num, "<unknown>", tmp, 255);
                ret += QByteArray(", request: ") + (const char *)tmp + '[' + (const char *)extensions[ i ] + '+'
                       + QByteArray::number(event.minor_code) + ']';
            }
        if (tmp[ 0 ] == '\0')  // not found???
            ret += QByteArray(", request <unknown> [") + QByteArray::number(event.request_code) + ':'
                   + QByteArray::number(event.minor_code) + ']';
        if (event.resourceid != 0) {
            ret += QByteArray(", resource: 0x") + QByteArray::number((qlonglong)event.resourceid, 16);
        }
        if (extensions != nullptr) {
            XFreeExtensionList(extensions);
        }
        delete[] majors;
        delete[] error_bases;
        XCloseDisplay(dpy2);
    }
#endif
    return ret;
}