/*!
  \file
  \brief イベント保持クラス

  \author Satofumi KAMIMURA

  $Id: CallbackEvent.cpp 1811 2010-04-30 16:12:05Z satofumi $
*/

#include "CallbackEvent.h"
#include "RectUtils.h"
#include <set>

using namespace qrk;
using namespace boost;
using namespace std;


namespace
{
    class KeyInput
    {
        long compareKey(void) const
        {
            return ((SDLK_LAST * mod) + key);
        }


    public:
        SDLKey key;
        SDLMod mod;


        KeyInput(SDLKey key_, SDLMod mod_) : key(key_), mod(mod_)
        {
        }


        bool operator < (const KeyInput& rhs) const
        {
            return (compareKey() < rhs.compareKey());
        }
    };
    typedef set<KeyInput> Keys;

    typedef set<Uint8> Clicks;
    typedef set<Rect<long> > Areas;


    bool existKeyEvent(const Keys& table, const KeyInput& find_key)
    {
        return (table.find(find_key) != table.end());
    }


    bool existClickEvent(const Clicks& table, Uint8 find_button)
    {
        return (table.find(find_button) != table.end());
    }


    bool existEnterCursorEvent(const Areas& table,
                               const Point<long>& current,
                               const Point<long>& previous)
    {
        for (Areas::iterator it = table.begin(); it != table.end(); ++it) {

            bool current_isInner = isInner(*it, current);
            bool previous_isInner = isInner(*it, previous);

            if (current_isInner && (! previous_isInner)) {
                return true;
            }
        }
        return false;
    }
}


struct CallbackEvent::pImpl
{
    EventCallback callback_;
    signals::connection connection_;

    Keys key_pressed_;
    Keys key_released_;
    Clicks click_pressed_;
    Clicks click_released_;
    Areas cursor_area_in_;
    Areas cursor_area_out_;

    bool is_active_;
    bool move_cursor_;
    bool acceptable_;


    pImpl(void) : is_active_(false), move_cursor_(false), acceptable_(true)
    {
    }


    ~pImpl(void)
    {
        connection_.disconnect();
    }


    void activate(void)
    {
        if (! acceptable_) {
            return;
        }
        is_active_ |= true;
        callback_();
    }


    void checkKey(SDLKey key, Uint8 type, SDLMod mod)
    {
        KeyInput key_input(key, mod);
        Keys& table = (type == SDL_KEYUP) ? key_released_ : key_pressed_;
        if (existKeyEvent(table, key_input)) {
            activate();
        }
    }


    void checkClick(Uint8 mouse_button, Uint8 type)
    {
        Clicks& table =
            (type == SDL_MOUSEBUTTONUP) ? click_released_ : click_pressed_;
        if (existClickEvent(table, mouse_button)) {
            activate();
        }
    }


    void checkEnterCursor(const Point<long>& current,
                          const Point<long>& previous)
    {
        if (existEnterCursorEvent(cursor_area_in_, current, previous) ||
            existEnterCursorEvent(cursor_area_out_, previous, current)) {
            activate();
        }
    }
};


CallbackEvent::CallbackEvent(void) : pimpl(new pImpl)
{
}


CallbackEvent::~CallbackEvent(void)
{
}


void CallbackEvent::checkKey(SDLKey key, Uint8 type, SDLMod mod, Uint16 unicode)
{
    static_cast<void>(unicode);

    pimpl->checkKey(key, type, mod);
}


void CallbackEvent::checkClick(Uint8 mouse_button, Uint8 type)
{
    pimpl->checkClick(mouse_button, type);
}


void CallbackEvent::checkEnterCursor(const Point<long>& current,
                                     const Point<long>& previous)
{
    pimpl->checkEnterCursor(current, previous);
}


void CallbackEvent::cursorMoved(void)
{
    if (pimpl->move_cursor_) {
        pimpl->activate();
    }
}


void CallbackEvent::setEventAcceptable(bool acceptable)
{
    pimpl->acceptable_ = acceptable;
}


void CallbackEvent::clearEvent(void)
{
    pimpl->key_pressed_.clear();
    pimpl->key_released_.clear();
    pimpl->click_pressed_.clear();
    pimpl->click_released_.clear();
    pimpl->cursor_area_in_.clear();
    pimpl->cursor_area_out_.clear();
}


void CallbackEvent::key(Uint16 key, Uint8 type, Uint16 mod, Uint16 unicode)
{
    static_cast<void>(unicode);

    KeyInput key_input(static_cast<SDLKey>(key), static_cast<SDLMod>(mod));
    Keys& table = (type == SDL_KEYUP) ?
        pimpl->key_released_ : pimpl->key_pressed_;
    table.insert(key_input);
}


void CallbackEvent::click(Uint8 mouse_button, Uint8 type)
{
    Clicks& table = (type == SDL_MOUSEBUTTONUP) ?
        pimpl->click_released_ : pimpl->click_pressed_;
    table.insert(mouse_button);
}


void CallbackEvent::enterCursor(const Rect<long>& area, bool enter)
{
    Areas& table = (enter) ? pimpl->cursor_area_in_ : pimpl->cursor_area_out_;
    table.insert(area);
}


void CallbackEvent::moveCursor(bool move)
{
    pimpl->move_cursor_ = move;
}


void CallbackEvent::setActive(void)
{
    pimpl->is_active_ = true;
}


bool CallbackEvent::isActive(void)
{
    bool is_active = pimpl->is_active_;
    pimpl->is_active_ = false;
    return is_active;
}


void CallbackEvent::setCallback(const EventCallback::slot_type& callback)
{
    pimpl->connection_ = pimpl->callback_.connect(callback);
}
