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

  \author Satofumi KAMIMURA

  $Id: CallbackEvent.cpp 891 2009-05-15 14:28:07Z 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;
}


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);
}
