/*!
  \file
  \brief ボタン

  \author Satofumi KAMIMURA

  $Id: Button.cpp 892 2009-05-15 21:48:28Z satofumi $
*/

#include "Button.h"
#include "SwitchSurface.h"
#include "CallbackEvent.h"
#include "LayerManager.h"
#include "RectUtils.h"

using namespace qrk;
using namespace boost;


struct Button::pImpl
{
  typedef enum {
    None,
    Focused,
    Pressed,
  } ButtonState;

  LayerManager layer_manager_;

  SwitchSurface surfaces_;
  Rect<long> rect_;
  bool enabled_;
  ButtonState button_state_;
  bool is_clicked_;
  bool force_focus_;
  Point<long> pre_position_;

  CallbackEvent* click_event_;
  CallbackEvent focus_out_event_;
  CallbackEvent press_event_;
  CallbackEvent release_event_;
  CallbackEvent moved_event_;

  ButtonCallback clicked_event_function_;
  ButtonCallback focused_event_function_;


  pImpl(Surface& normal_surface,
        Surface& focused_surface,
        Surface& pressed_surface)
    : enabled_(true), button_state_(None), is_clicked_(false),
      force_focus_(false), click_event_(NULL)
  {
    surfaces_.addSurface(normal_surface, Normal);
    surfaces_.addSurface(focused_surface, Focused);
    surfaces_.addSurface(pressed_surface, Pressed);
  }


  ~pImpl(void)
  {
    layer_manager_.removeEvent(&focus_out_event_);
    layer_manager_.removeEvent(&press_event_);
    layer_manager_.removeEvent(&release_event_);
    layer_manager_.removeEvent(&moved_event_);
  }


  void initializeFirst(const Point<long>& own_position)
  {
    // カーソル位置を初期化時とは違う位置にする
    pre_position_ = own_position + Point<long>(1, 1);

    // サイズは、基本サーフェスを用いる
    surfaces_.setDrawSurface(Normal);
    rect_ = surfaces_.rect();

    press_event_.click(SDL_BUTTON_LEFT, SDL_MOUSEBUTTONDOWN);
    release_event_.click(SDL_BUTTON_LEFT, SDL_MOUSEBUTTONUP);

    moved_event_.moveCursor(true);

    layer_manager_.insertEvent(&focus_out_event_);
    layer_manager_.insertEvent(&press_event_);
    layer_manager_.insertEvent(&release_event_);
    layer_manager_.insertEvent(&moved_event_);
  }


  // カーソルのフォーカスイベント用のエリアを登録
  // 位置が変更されたら、再度初期化が必要
  void initializeEvent(const Point<long>& own_position)
  {
    focus_out_event_.clearEvent();

    Rect<long> area = rect_;
    area.x = own_position.x;
    area.y = own_position.y;

    focus_out_event_.enterCursor(area, false);
  }


  void draw(const Rect<long>& area, const Point<long>& own_position)
  {
    static_cast<void>(area);
    // !!! area を反映させる

    if (! surfaces_.isValid()) {
      return;
    }

    // 指定位置への Surface 描画
    Rect<long> draw_rect = surfaces_.rect();
    draw_rect.x = own_position.x;
    draw_rect.y = own_position.y;

    // クリック情報の更新
    detectClicked(draw_rect);
    if (click_event_ && click_event_->isActive()) {
      is_clicked_ = true;
    }

    // 表示サーフェスの決定
    ButtonMode mode = Button::Normal;
    if (is_clicked_ || (button_state_ == pImpl::Pressed)) {
      mode = Button::Pressed;

    } else if (button_state_ == pImpl::Focused) {
      mode = Button::Focused;
    }
    if (force_focus_) {
      mode = Button::Focused;
    }
    surfaces_.setDrawSurface(mode);

    // disable 時は、色を薄くして描画
    float alpha = 1.0;
    if (! enabled_) {
      alpha = surfaces_.alpha();
      surfaces_.setAlpha(alpha * 0.4);
    }
    surfaces_.draw(NULL, &draw_rect);
    if (! enabled_) {
      surfaces_.setAlpha(alpha);
    }
  }


  void detectClicked(const Rect<long>& draw_area)
  {
    if (! enabled_) {
      button_state_ = pImpl::None;
      return;
    }

    bool is_focus_out = focus_out_event_.isActive();
    bool pressed = press_event_.isActive();
    bool released = release_event_.isActive();

    // 現在の状態と、マウス位置でクリック判定を行う
    switch (button_state_) {
    case pImpl::None:
      // None のときに領域に入ったら Focused
      if (moved_event_.isActive()) {
        // カーソル位置がボタンの上ならばフォーカス扱いとする
        // この条件がないとカーソルが１周期でボタン上に In/Out したときに
        // フォーカスしたままになる
        Point<long> cursor = layer_manager_.cursor();
        if (isInner(draw_area, cursor)) {
          button_state_ = pImpl::Focused;
          // フォーカス時のコールバック関数
          focused_event_function_();
        }
      }
      break;

    case pImpl::Focused:
      // Focused のときに押下されたら Pressed
      if (pressed) {
        button_state_ = pImpl::Pressed;
      } else if (is_focus_out) {
        button_state_ = pImpl::None;
      }
      break;

    case pImpl::Pressed:
      // Pressed のときに離されたらクリック確定
      if (released) {
        button_state_ = Focused;
        is_clicked_ = true;
        // クリック時のコールバック関数
        clicked_event_function_();

      } else if (is_focus_out) {
        button_state_ = pImpl::None;
      }
      break;
    }
  }
};


Button::Button(Surface& normal_surface,
               Surface& focused_surface,
               Surface& pressed_surface)
  : pimpl(new pImpl(normal_surface, focused_surface, pressed_surface))
{
  pimpl->initializeFirst(position());
}


Button::~Button(void)
{
}


Rect<long> Button::rect(void) const
{
  return pimpl->surfaces_.rect();
}


void Button::setEventAcceptable(bool acceptable)
{
  if (pimpl->click_event_) {
    pimpl->click_event_->setEventAcceptable(acceptable);
  }
  pimpl->focus_out_event_.setEventAcceptable(acceptable);
  pimpl->press_event_.setEventAcceptable(acceptable);
  pimpl->release_event_.setEventAcceptable(acceptable);
  pimpl->moved_event_.setEventAcceptable(acceptable);
}


void Button::setAlpha(float alpha)
{
  pimpl->surfaces_.setAlpha(alpha);
}


void Button::draw(const Rect<long>& area)
{
  Point<long> own_position = position();
  pimpl->draw(area, own_position);

  // 位置の変更を検出して、イベントの登録範囲を更新する
  if ((own_position.x != pimpl->pre_position_.x) ||
      (own_position.y != pimpl->pre_position_.y)) {
    pimpl->initializeEvent(own_position);
    pimpl->pre_position_ = own_position;
  }
}


void Button::setSurface(Surface& surface, ButtonMode mode)
{
  pimpl->surfaces_.addSurface(surface, mode);
}


void Button::setEnabled(bool enable)
{
  pimpl->enabled_ = enable;
}


void Button::setFocus(void)
{
  pimpl->force_focus_ = true;
}


bool Button::isFocused(void)
{
  return (pimpl->force_focus_ ||
          (pimpl->button_state_ == pImpl::Focused)) ? true : false;
}


bool Button::isCursorInner(void)
{
  const Point<long>& own_position = position();
  Rect<long> draw_rect = pimpl->rect_;
  draw_rect.x = own_position.x;
  draw_rect.y = own_position.y;
  Point<long> cursor = LayerManager::cursor();

  return (isInner(draw_rect, cursor)) ? true : false;
}


void Button::click(void)
{
  pimpl->is_clicked_ = true;
  pimpl->force_focus_ = false;
  pimpl->clicked_event_function_();
}


void Button::release(void)
{
  pimpl->is_clicked_ = false;
  pimpl->force_focus_ = false;
  pimpl->button_state_ = pImpl::None;

  // イベント状態の読み捨て
  pimpl->moved_event_.isActive();
}


bool Button::isClicked(void)
{
  return pimpl->is_clicked_;
}


void Button::setClickEvent(CallbackEvent* event)
{
  pimpl->click_event_ = event;
}


void Button::connectClickedEvent(const ButtonCallback::slot_type& slot)
{
  pimpl->clicked_event_function_.connect(slot);
}


void Button::connectFocusedEvent(const ButtonCallback::slot_type& slot)
{
  pimpl->focused_event_function_.connect(slot);
}
