/******************************************************************************!
 * \file event.cpp
 * \author Sebastien Beaugrand
 * \sa http://beaugrand.chez.com/
 * \copyright CeCILL 2.1 Free Software license
 ******************************************************************************/
#include <iostream>
#include <algorithm>
#include <X11/XKBlib.h>
#include "event.h"
#include "display.h"
#include "window.h"

namespace X11 {

/******************************************************************************!
 * \class ExposeCallback
 ******************************************************************************/
class ExposeCallback : public Callback
{
public:
    int expose(const Event* event)
    {
        if (event->mEvent.xexpose.count == 0) {
            const std::list<Input>& lst = event->getInputList();
            std::list<Input>::const_iterator it = lst.begin();
            Window* window = nullptr;
            while ((it != lst.end() &&
                    (window = (*it).getWindow()) &&
                    window->id() != event->mEvent.xexpose.window) ||
                   (it != lst.end() && (*it).getWindow() == 0)) {
                ++it;
            }
            if (window != nullptr && it != lst.end()) {
                window->refresh();
            }
        }
        return 0;
    }
};
static ExposeCallback gExposeCallback;

/******************************************************************************!
 * \class MappingNotifyCallback
 ******************************************************************************/
class MappingNotifyCallback : public Callback
{
public:
    int mappingNotify(const Event* event)
    {
        XRefreshKeyboardMapping(&const_cast<Event*>(event)->mEvent.xmapping);
        return 0;
    }
};
static MappingNotifyCallback gMappingNotifyCallback;

/******************************************************************************!
 * \fn operator==
 ******************************************************************************/
bool
operator==(const Input& i1, const Input& i2)
{
    return ! (i1 != i2);
}

/******************************************************************************!
 * \fn operator!=
 ******************************************************************************/
bool
operator!=(const Input& i1, const Input& i2)
{
    int mask = i1.mCmpMask & i2.mCmpMask;

    if ((mask & EVENT_CMP_TYPE) && i1.mType != i2.mType) {
        return true;
    }
    if ((mask & EVENT_CMP_WIN) && i1.mWinId != i2.mWinId) {
        return true;
    }
    if ((mask & EVENT_CMP_MASK) && i1.mMask != i2.mMask) {
        return true;
    }
    return false;
}

/******************************************************************************!
 * \fn Event
 ******************************************************************************/
Event::Event(Display* display) :
    mCallbackObj(NULL),
    mCallbackFunc(NULL)
{
    mDisplay = display->id();
    addInput(0, 0, MappingNotify, &gMappingNotifyCallback,
             (int (Callback::*)(const Event*))
             & MappingNotifyCallback::mappingNotify);
}

/******************************************************************************!
 * \fn ~Event
 ******************************************************************************/
Event::~Event()
{
}

/******************************************************************************!
 * \fn addWindowWithExposeCB
 ******************************************************************************/
void
Event::addWindowWithExposeCB(Window* w)
{
    addInput(w, ExposureMask, Expose, &gExposeCallback,
             (int (Callback::*)(const Event*))
             & ExposeCallback::expose);
}

/******************************************************************************!
 * \fn addInput
 ******************************************************************************/
void
Event::addInput(Window* window, long mask, long type, Callback* cbObj,
                int (Callback::* cbFunc)(const Event*))
{
    if (mask || type) {
        Input input(window, mask, type, cbObj, cbFunc);
        input.mCmpMask = 0;
        if (window) {
            window->addInput(mask);
            input.mWinId = window->id();
            input.mCmpMask |= EVENT_CMP_WIN;
        } else {
            input.mWinId = 0;
        }
        input.mCmpMask |= (mask) ? EVENT_CMP_MASK : 0;
        input.mCmpMask |= (type) ? EVENT_CMP_TYPE : 0;
        mInputList.push_back(input);
    } else {
        mCallbackObj = cbObj;
        mCallbackFunc = cbFunc;
    }
    if (window && ! window->id()) {
        std::cerr << "warning : X11::Event::addInput"
            " identificateur de fenetre non initialise"
            " dans X11::Window (fenetre non projetee)" << std::endl;
    }
}

/******************************************************************************!
 * \fn delInput
 ******************************************************************************/
void
Event::delInput(Window* window)
{
    std::list<Input>::iterator it;
    Window* w;
    for (it = mInputList.begin();
         it != mInputList.end();) {
        if ((w = (*it).getWindow()) && w->id() == window->id()) {
            it = mInputList.erase(it);
        } else {
            ++it;
        }
    }
}

/******************************************************************************!
 * \fn run
 ******************************************************************************/
int
Event::run()
{
    std::list<Input>::iterator it;
    Input input;
    Callback* cbObj;
    int (Callback::* cbFunc)(const Event*);

#ifndef NDEBUG
    std::cout << "liste des evenements geres :" << std::endl;
    for (it = mInputList.begin();
         it != mInputList.end(); ++it) {
        std::cerr << "event " << (*it).mType
                  << " window " << (*it).mWinId << std::endl;
    }
#endif

    input.mCmpMask = EVENT_CMP_TYPE | EVENT_CMP_WIN;
    mLoop = True;
    while (mLoop) {
        if (XPending(mDisplay)) {
            XNextEvent(mDisplay, &mEvent);
#ifndef NDEBUG
            std::cerr << "receive event " << mEvent.type
                      << " from window " << mEvent.xany.window << std::endl;
#endif
            input.mType = mEvent.type;
            input.mWinId = mEvent.xany.window;
            it = std::find(mInputList.begin(), mInputList.end(), input);
            if (it != mInputList.end() &&
                (cbObj = (*it).mCallbackObj) &&
                (cbFunc = (*it).mCallbackFunc) &&
                (cbObj->*cbFunc)(this) == EXIT) {
                mLoop = False;
            } else if (mEvent.type == MappingNotify) {
                // Pour les evenements ne dependant pas d'une fenetre
                // comme celui de type MappingNotify
                input.mCmpMask = EVENT_CMP_TYPE;
                it = std::find(mInputList.begin(), mInputList.end(), input);
                if (it != mInputList.end() &&
                    (cbObj = (*it).mCallbackObj) &&
                    (cbFunc = (*it).mCallbackFunc) &&
                    (cbObj->*cbFunc)(this) == EXIT) {
                    mLoop = False;
                }
                input.mCmpMask = EVENT_CMP_TYPE | EVENT_CMP_WIN;
            }
        }
        if (mCallbackObj) {
            (mCallbackObj->*mCallbackFunc)(this);
        }
    }
    return 0;
}

/******************************************************************************!
 * \fn getNextKey
 ******************************************************************************/
KeySym
Event::getNextKey() const
{
    //return XKeycodeToKeysym(mDisplay, mEvent.xkey.keycode, 0);
    return XkbKeycodeToKeysym(mDisplay, mEvent.xkey.keycode, 0,
                              (mEvent.xkey.state & ShiftMask) ? 1 : 0);
}

/******************************************************************************!
 * \fn getNextState
 ******************************************************************************/
int
Event::getNextState() const
{
    return mEvent.xkey.state;
}

}  // namespace X11