/********************************************************************
 K Win - the KDE window manager
 This file is part of the KDE project.

Copyright (C) 2012, 2013 Martin Gräßlin <mgraesslin@kde.org>
Copyright (C) 2015       Daniel Vrátil <dvratil@redhat.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 2 of the License, or
(at your option) 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/>.
*********************************************************************/

#ifndef XCB_WRAPPER_H
#define XCB_WRAPPER_H

#include <functional>
#include <type_traits>

#include <QX11Info>
#include <QScopedPointer>

#include <xcb/xcb.h>
#include <xcb/randr.h>

namespace XCB
{

template<typename T>
using ScopedPointer = QScopedPointer<T, QScopedPointerPodDeleter>;

xcb_connection_t *connection();
void closeConnection();
xcb_screen_t *screenOfDisplay(xcb_connection_t *c, int screen);

struct GrabServer
{
    GrabServer();
    ~GrabServer();
};

template <typename Reply,
          typename Cookie,
          typename ReplyFunc,
          ReplyFunc replyFunc,
          typename RequestFunc,
          RequestFunc requestFunc,
          typename ... RequestFuncArgs>
class Wrapper
{
public:
    Wrapper()
    : m_retrieved(false)
    , m_window(XCB_WINDOW_NONE)
    , m_reply(nullptr)
    {
        m_cookie.sequence = 0;
    }
    explicit Wrapper(const RequestFuncArgs& ... args)
    : m_retrieved(false)
    , m_cookie(requestFunc(connection(), args ...))
    , m_window(requestWindow<RequestFuncArgs ...>(args ...))
    , m_reply(nullptr)
    {
    }
    explicit Wrapper(const Wrapper &other)
    : m_retrieved(other.m_retrieved)
    , m_cookie(other.m_cookie)
    , m_window(other.m_window)
    , m_reply(nullptr)
    {
        takeFromOther(const_cast<Wrapper&>(other));
    }
    virtual ~Wrapper() {
        cleanup();
    }
    inline Wrapper &operator=(const Wrapper &other) {
        if (this != &other) {
            // if we had managed a reply, free it
            cleanup();
            // copy members
            m_retrieved = other.m_retrieved;
            m_cookie = other.m_cookie;
            m_window = other.m_window;
            m_reply = other.m_reply;
            // take over the responsibility for the reply pointer
            takeFromOther(const_cast<Wrapper&>(other));
        }
        return *this;
    }

    inline operator const Reply*() const {
        getReply();
        return m_reply;
    }
    inline const Reply* operator->() const {
        getReply();
        return m_reply;
    }
    inline bool isNull() const {
        getReply();
        return m_reply == nullptr;
    }
    inline operator bool() const {
        return !isNull();
    }
    inline const Reply* data() const {
        getReply();
        return m_reply;
    }
    inline xcb_window_t window() const {
        return m_window;
    }
    inline bool isRetrieved() const {
        return m_retrieved;
    }
    /**
     * Returns the value of the reply pointer referenced by this object. The reply pointer of
     * this object will be reset to null. Calling any method which requires the reply to be valid
     * will crash.
     *
     * Callers of this function take ownership of the pointer.
     **/
    inline Reply *take() {
        getReply();
        Reply *ret = m_reply;
        m_reply = nullptr;
        m_window = XCB_WINDOW_NONE;
        return ret;
    }

protected:
    void getReply() const {
        if (m_retrieved || !m_cookie.sequence) {
            return;
        }
        m_reply = replyFunc(connection(), m_cookie, nullptr);
        m_retrieved = true;
    }

private:
    inline void cleanup() {
        if (!m_retrieved && m_cookie.sequence) {
            xcb_discard_reply(connection(), m_cookie.sequence);
        } else if (m_reply) {
            free(m_reply);
        }
    }
    inline void takeFromOther(Wrapper &other) {
        if (m_retrieved) {
            m_reply = other.take();
        } else {
            //ensure that other object doesn't try to get the reply or discards it in the dtor
            other.m_retrieved = true;
            other.m_window = XCB_WINDOW_NONE;
        }
    }
    template<typename ... Args>
    constexpr xcb_window_t requestWindow(const Args & ... args) const {
        return std::is_same<typename std::tuple_element<0, std::tuple<Args ...>>::type, xcb_window_t>::value
                    ? std::get<0>(std::tuple<Args ...>(args ...))
                    : static_cast<xcb_window_t>(XCB_WINDOW_NONE);
    }

    mutable bool m_retrieved;
    Cookie m_cookie;
    xcb_window_t m_window;
    mutable Reply *m_reply;
};

#define XCB_DECLARE_TYPE(name, xcb_request, ...) \
    typedef Wrapper<xcb_request##_reply_t, \
                    xcb_request##_cookie_t, \
                    decltype(&xcb_request##_reply), \
                    xcb_request##_reply, \
                    decltype(&xcb_request), \
                    xcb_request, \
                    ##__VA_ARGS__> name

XCB_DECLARE_TYPE(ScreenInfo, xcb_randr_get_screen_info,
                 xcb_window_t);

XCB_DECLARE_TYPE(ScreenSize, xcb_randr_get_screen_size_range,
                 xcb_window_t);

XCB_DECLARE_TYPE(PrimaryOutput, xcb_randr_get_output_primary,
                 xcb_window_t);

XCB_DECLARE_TYPE(InternAtom, xcb_intern_atom,
                 uint8_t, uint16_t, const char *);

XCB_DECLARE_TYPE(OutputInfo, xcb_randr_get_output_info,
                 xcb_randr_output_t, xcb_timestamp_t);

XCB_DECLARE_TYPE(CRTCInfo, xcb_randr_get_crtc_info,
                 xcb_randr_crtc_t, xcb_timestamp_t);

XCB_DECLARE_TYPE(AtomName, xcb_get_atom_name,
                 xcb_atom_t);

}

#endif
