/*!
  \file
  \brief ボタン

  \author Satofumi KAMIMURA

  $Id: Button.cpp 1954 2011-07-17 22:08:18Z 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);
}


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


void Button::setRotateAngle(const Angle& angle)
{
    pimpl->surfaces_.setRotateAngle(angle);
}


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(bool on)
{
    pimpl->force_focus_ = on;
}


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->button_state_ = pImpl::Pressed;
    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);
}
