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

#include <X11/Xlibint.h>

#include "uaceExt.h"

#define foreach(index, container) \
    for ((index) = (container)->head; (index); (index) = (index)->next)

WIExtInfo wiExtInfo;
char wiExtName[] = UACE_EXTENSION_NAME;

static WIExtDisplayInfo *wi_ext_find_display(WIExtInfo *extInfo, Display *dpy) {

    if (extInfo->cur && extInfo->cur->display == dpy) {
        return extInfo->cur;
    }

    WIExtDisplayInfo *info = NULL;

    {
        _XLockMutex(_Xglobal_lock);
        foreach(info, extInfo) {
            if (info->display == dpy) {
                extInfo->cur = info;
                break;
            }
        }
        _XUnlockMutex(_Xglobal_lock);
    }
    return info;
}

static int wi_ext_remove_display(WIExtInfo *extInfo, Display *dpy) {
    WIExtDisplayInfo *info, *prev;

    {
        _XLockMutex(_Xglobal_lock);
        prev = NULL;
        foreach(info, extInfo) {
            if (info->display == dpy) {
                break;
            }
            prev = info;
        }

        if (info) {
            if (prev) {
                prev->next = info->next;
            } else {
                extInfo->head = info->next;
            }
            extInfo->ndisplays--;

            if (info == extInfo->cur) {
                extInfo->cur = NULL;
            }
        }
        _XUnlockMutex(_Xglobal_lock);
    }

    if (info) {
        XFree(info);
        return True;
    } else {
        return False;
    }
}

static int wi_close_display(Display *dpy, __attribute__((unused)) XExtCodes *codes) {
    return wi_ext_remove_display(&wiExtInfo, dpy);
}

static WIExtDisplayInfo *wi_ext_add_display(WIExtInfo *extInfo,
                                            Display *dpy,
                                            char *extName) {
    WIExtDisplayInfo *info;

    info = (WIExtDisplayInfo *) Xmalloc(sizeof(WIExtDisplayInfo));
    if (!info) {
        return NULL;
    }

    info->display = dpy;
    info->codes = XInitExtension(dpy, extName);

    if (info->codes) {
        XESetCloseDisplay(dpy, info->codes->extension, wi_close_display);
        UnlockDisplay(dpy);
        SyncHandle();
    } else {
        /* The server doesn't have this extension.
         * Use a private Xlib-internal extension to hang the close_display
         * hook on so that the "cache" (extinfo->cur) is properly cleaned.
         * (XBUG 7955)
         */
        XExtCodes *codes = XAddExtension(dpy);
        if (!codes) {
            Xfree(info);
            return NULL;
        }
        XESetCloseDisplay(dpy, codes->extension, wi_close_display);
    }

    {
        _XLockMutex(_Xglobal_lock);
        info->next = extInfo->head;
        extInfo->head = info;
        extInfo->cur = info;
        extInfo->ndisplays++;
        _XUnlockMutex(_Xglobal_lock);
    }

    return info;
}

WIExtDisplayInfo *wi_find_display(Display *dpy) {
    WIExtDisplayInfo *info;

    info = wi_ext_find_display(&wiExtInfo, dpy);
    if (!info) {
        info = wi_ext_add_display(&wiExtInfo, dpy, wiExtName);
    }
    return info;
}
