/*!
  \file
  \brief メニュー

  \author Satofumi KAMIMURA

  $Id: Menu.cpp 899 2009-05-17 21:36:48Z satofumi $

  \todo clickedId() の情報を、イベントで監視すべき
*/

#include "Menu.h"
#include "Button.h"
#include "Label.h"
#include "LayerManager.h"
#include "CallbackEvent.h"
#include "AlignUtils.h"
#include "log_printf.h"
#include <set>

using namespace qrk;
using namespace std;


namespace
{
  typedef map<size_t, Button*> Items;
  typedef vector<size_t> ButtonOrder;
}


struct Menu::pImpl
{
  LayerManager layer_manager_;
  Items items_;
  Rect<long> rect_;
  Align align_;
  float alpha_;
  ButtonOrder button_order_;
  set<size_t> disabled_buttons_;
  int default_index_;
  int clicked_index_;
  int previous_index_;
  int index_;
  int decided_press_index_;
  bool step_specified_;
  bool key_decided_;
  Point<long> step_;
  Point<long> base_point_;
  Point<long> prev_cursor_;
  Point<long> cursor_;

  bool enable_no_select_;
  bool enable_rotate_;

  CallbackEvent decide_press_event_;
  CallbackEvent decide_release_event_;
  CallbackEvent up_event_;
  CallbackEvent down_event_;

  Label* icon_label_;
  bool icon_draw_front_;
  Point<long> icon_offset_;


  pImpl(const Rect<long>& rect, Align align)
    : rect_(rect), align_(align), alpha_(1.0), default_index_(InvalidIndex),
      clicked_index_(default_index_), previous_index_(default_index_),
      index_(default_index_), decided_press_index_(InvalidIndex),
      step_specified_(false), key_decided_(false),
      prev_cursor_(layer_manager_.cursor()), cursor_(prev_cursor_),
      enable_no_select_(false), enable_rotate_(false),
      icon_label_(NULL), icon_draw_front_(true)
  {
    setAilgnBase(align);

    // リターンキーで決定
    decide_press_event_.setEventAcceptable(false);
    decide_press_event_.key(SDLK_RETURN, SDL_KEYDOWN, KMOD_NONE);
    decide_release_event_.key(SDLK_RETURN, SDL_KEYUP, KMOD_NONE);

    // カーソルキーで選択項目の移動
    up_event_.key(SDLK_UP, SDL_KEYDOWN, KMOD_NONE);
    down_event_.key(SDLK_DOWN, SDL_KEYDOWN, KMOD_NONE);

    layer_manager_.insertEvent(&decide_press_event_);
    layer_manager_.insertEvent(&decide_release_event_);
    layer_manager_.insertEvent(&up_event_);
    layer_manager_.insertEvent(&down_event_);

    setEnableNoSelect(enable_no_select_);
  }


  ~pImpl(void)
  {
    layer_manager_.removeEvent(&decide_press_event_);
    layer_manager_.removeEvent(&decide_release_event_);
    layer_manager_.removeEvent(&up_event_);
    layer_manager_.removeEvent(&down_event_);
  }


  void setAilgnBase(Align align)
  {
    switch (align) {
    case Left:
    case Top:
      // base_point_ は (0, 0) のまま
      break;

    case Center:
      base_point_ = Point<long>(rect_.w / 2, 0);
      break;

    case Right:
      base_point_ = Point<long>(rect_.w - 1, 0);
      break;

    case Middle:
      base_point_ = Point<long>(0, rect_.h / 2);
      break;

    case Bottom:
      base_point_ = Point<long>(0, rect_.h - 1);
      break;
    }
  }


  void setAlpha(float alpha)
  {
    for (Items::iterator it = items_.begin(); it != items_.end(); ++it) {
      it->second->setAlpha(alpha);
    }
    alpha_ = alpha;
  }


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

    // キー操作の反映
    updateKeySelect();

    if (icon_label_ && (! icon_draw_front_)) {
      // アイコンを背面に描画する場合
      drawIcon(area, own_position);
    }

    // メニュー項目の描画
    Point<long> draw_point = base_point_ + own_position;
    for (Items::iterator it = items_.begin(); it != items_.end(); ++it) {
      Button* button = it->second;
      Point<long> aligned_point = draw_point;

      aligned_point += alignedPosition(button);
      button->setPosition(aligned_point);
      button->draw(area);

      draw_point += step_;
    }

    if (icon_label_ && (! icon_draw_front_)) {
      // アイコンを前面に描画する場合
      drawIcon(area, own_position);
    }

    // マウス操作の反映
    prev_cursor_ = cursor_;
    cursor_ = layer_manager_.cursor();
    updateCursorSelect();

    detectClicked();
  }


  void drawIcon(const Rect<long>& area, const Point<long>& own_position)
  {
    if (index_ < 0) {
      // 位置が無効な場合は戻る
      return;
    }

    Point<long> draw_point = base_point_ + own_position;
    for (int i = 0; i < index_; ++i) {
      draw_point += step_;
    }

    draw_point += alignedPosition(icon_label_);
    icon_label_->setPosition(draw_point);
    icon_label_->draw(area);
  }


  Point<long> alignedPosition(Component* component)
  {
    Point<long> offset;

    switch (align_) {
    case Left:
    case Top:
      // そのままの位置を用いる
      break;

    case Center:
      offset.x = center(*component, 0);
      break;

    case Right:
      offset.x = right(*component, 0);
      break;

    case Middle:
      offset.y = middle(*component, 0);
      break;

    case Bottom:
      offset.y = bottom(*component, 0);
      break;
    }
    return offset;
  }


  void addButton(Button* button, size_t id)
  {
    // step が指定されたことがなければ align に応じてステップを更新する
    if (! step_specified_) {
      switch (align_) {
      case Left:
      case Center:
      case Right:
        step_.y = max(button->rect().h, step_.y);
        break;

      case Top:
      case Middle:
      case Bottom:
        step_.x = max(button->rect().w, step_.x);
        break;
      }
    }
    items_[id] = button;
    button->setAlpha(alpha_);
  }


  void detectClicked(void)
  {
    if (clicked_index_ >= 0) {
      // 既に項目が決定されていれば戻る
      return;
    }

    size_t n = button_order_.size();
    for (size_t i = 0; i < n; ++i) {
      Button* button = items_[button_order_[i]];
      if (button->isClicked()) {
        clicked_index_ = i;
      }
    }

    if (clicked_index_ >= 0) {
      // 今回のチェックで決定が検出された場合、ボタンへの操作を無効にする
      setEventAcceptable(false);
      setButtonsEnabled(false);

      if (! key_decided_) {
        Button* button = buttonResource(button_order_[clicked_index_]);
        button->click();

        // マウス操作によって決定された場合は index_ を無効にする
        index_ = InvalidIndex;
        key_decided_ = false;
      }
    }
  }


  void setButtonsEnabled(bool enable)
  {
    for (Items::iterator it = items_.begin(); it != items_.end(); ++it) {
      Button* button = it->second;
      button->setEventAcceptable(enable);
      if (enable) {
        button->release();
      }
    }
  }


  void updateCursorSelect(void)
  {
    if (clickedId() != InvalidId) {
      // 決定されていれば、操作を反映させない
      return;
    }

    if ((prev_cursor_.x != cursor_.x) || (prev_cursor_.y != cursor_.y)) {

      // マウスが移動し、別ボタンにフォーカスが発生したときに選択を無効化
      int focused_index = focusedIndex(index_, true);
      if (focused_index != InvalidIndex) {
        if (index_ >= 0) {
          release(button_order_[index_]);
        }
        index_ = default_index_;

      } else {
        if (enable_no_select_) {
          if (index_ >= 0) {
            size_t id = button_order_[index_];
            Button* focused_button = items_[id];
            if (! focused_button->isCursorInner()) {
              focused_button->release();
            }
          }
        }
      }
    }
  }


  void updateKeySelect(void)
  {
    if (clickedId() != InvalidId) {
      // 決定されていれば、操作を反映させない
      return;
    }

    // キー操作の描画への反映
    bool decide_pressed = decide_press_event_.isActive();
    if (decide_pressed) {
      decided_press_index_ = index_;
    }
    // 配置直後の押下に反応しないための処理
    // これがないと、ウィンドウ配置用の押下に反応する
    decide_press_event_.setEventAcceptable(true);

    size_t n = button_order_.size();
    bool decided = decide_release_event_.isActive();
    if ((index_ >= 0) && (index_ < static_cast<int>(n)) &&
        decided && (decided_press_index_ == index_)) {
      // 決定扱いにする
      press(button_order_[index_]);
      key_decided_ = true;

      // フォーカス表示のために index と previous_index を違う値にする
      previous_index_ = index_ - 1;
      return;
    }

    // !!! align によってキーの役割を変更すること
    bool up = up_event_.isActive();
    bool down = down_event_.isActive();

    // フォーカス中のボタン情報を index_ に反映させる
    int focused_index = focusedIndex();
    if (focused_index >= 0) {
      index_ = focused_index;
    }

    // 選択項目の移動
    if (down) {
      for (size_t i = 0; i < n; ++i) {
        ++index_;
        if (index_ >= static_cast<int>(n)) {
          index_ = (enable_rotate_) ? 0 : previous_index_;
        }
        size_t id = button_order_[index_];
        if (disabled_buttons_.find(id) == disabled_buttons_.end()) {
          break;
        }
      }
    }
    if (up) {
      for (size_t i = 0; i < n; ++i) {
        if (index_ <= 0) {
          if (enable_rotate_) {
            index_ = static_cast<int>(n - 1);
          }
        } else {
          --index_;
        }
        size_t id = button_order_[index_];
        if (disabled_buttons_.find(id) == disabled_buttons_.end()) {
          break;
        }
      }
    }

    if (index_ != previous_index_) {
      // 見た目の更新
      if (previous_index_ >= 0) {
        release(button_order_[previous_index_]);
      }
      if (index_ >= 0) {
        setFocus(button_order_[index_]);
      }
      previous_index_ = index_;
    }
  }


  int focusedIndex(int ignore_index = InvalidIndex, bool check_inner = false)
  {
    size_t n = button_order_.size();
    for (size_t i = 0; i < n; ++i) {
      if (static_cast<int>(i) == ignore_index) {
        continue;
      }
      size_t id = button_order_[i];
      Button* button = items_[id];

      static_cast<void>(check_inner);
#if 0
      if (check_inner && (button->isInner())) {
        button->setFocus();
      }
#endif

      if (button->isFocused()) {
        return static_cast<int>(i);
      }
    }
    return InvalidIndex;
  }


  Button* buttonResource(size_t id)
  {
    Items::iterator it = items_.find(id);
    if (it == items_.end()) {
      return NULL;
    } else {
      return it->second;
    }
  }


  void setFocus(size_t id)
  {
    Button* button = buttonResource(id);
    if (button) {
      button->setFocus();
    }
  }


  void press(size_t id)
  {
    Button* button = buttonResource(id);
    if (button) {
      button->click();
    }
  }


  void release(size_t id)
  {
    Button* button = buttonResource(id);
    if (button) {
      button->release();
    }
  }


  void release(void)
  {
    setEventAcceptable(true);
    setButtonsEnabled(true);
    clicked_index_ = InvalidIndex;
  }


  bool isClicked(size_t id)
  {
    Button* button = buttonResource(id);
    return (button) ? button->isClicked() : false;
  }


  size_t clickedId(void)
  {
    vector<size_t> clicked_id;

    for (Items::iterator it = items_.begin(); it != items_.end(); ++it) {
      if (it->second->isClicked()) {
        return it->first;
      }
    }
    return InvalidId;
  }


  void updateButtonOrder(void)
  {
    button_order_.clear();
    for (Items::iterator it = items_.begin(); it != items_.end(); ++it) {
      button_order_.push_back(it->first);
    }
  }


  void setEventAcceptable(bool acceptable)
  {
    decide_press_event_.setEventAcceptable(acceptable);
    decide_release_event_.setEventAcceptable(acceptable);
    up_event_.setEventAcceptable(acceptable);
    down_event_.setEventAcceptable(acceptable);
  }


  void setEnableNoSelect(bool enable)
  {
    default_index_ = (enable) ? InvalidIndex : 0;

    if ((! enable) && (index_ == InvalidIndex)) {
      index_ = default_index_;
    }
  }
};


Menu::Menu(const Rect<long>& rect, Align align)
  : pimpl(new pImpl(rect, align))
{
}


Menu::~Menu(void)
{
}


Rect<long> Menu::rect(void) const
{
  return pimpl->rect_;
}


void Menu::setEventAcceptable(bool acceptable)
{
  pimpl->setEventAcceptable(acceptable);
}


void Menu::setAlpha(float alpha)
{
  pimpl->setAlpha(alpha);
}


void Menu::draw(const Rect<long>& area)
{
  pimpl->draw(area, position());
}


void Menu::setButtonStep(const Point<long>& step)
{
  pimpl->step_ = step;
  pimpl->step_specified_ = true;
}


void Menu::addButton(Component& button, size_t id)
{
  // !!! dynamic_cast の使い方を見直す
  Button* raw_button = dynamic_cast<Button*>(&button);
  if (raw_button == NULL) {
    log_printf("Menu::addButton(): no Button obuject.\n");
    return;
  }

  pimpl->addButton(raw_button, id);
  pimpl->updateButtonOrder();
}


void Menu::removeButton(size_t id)
{
  pimpl->items_.erase(id);
  pimpl->updateButtonOrder();
}


void Menu::setButtonEnabled(size_t id, bool enable)
{
  if (! enable) {
    pimpl->disabled_buttons_.insert(id);
  } else {
    pimpl->disabled_buttons_.erase(id);
  }

  Button* button = pimpl->buttonResource(id);
  if (button) {
    button->setEnabled(enable);
  }
}


void Menu::setEnableNoSelect(bool enable)
{
  pimpl->setEnableNoSelect(enable);
}


void Menu::setEnableRotate(bool enable)
{
  pimpl->enable_rotate_ = enable;
}


bool Menu::empty(void)
{
  return pimpl->items_.empty();
}


size_t Menu::size(void)
{
  return pimpl->items_.size();
}


void Menu::setFocus(size_t id)
{
  pimpl->setFocus(id);
}


void Menu::press(size_t id)
{
  pimpl->press(id);
}


void Menu::release(size_t id)
{
  pimpl->release(id);
}


void Menu::release(void)
{
  pimpl->release();
}


void Menu::setIndex(int index)
{
  index = max(static_cast<int>(InvalidIndex), index);
  index = min(index, static_cast<int>(pimpl->items_.size()));

  pimpl->index_ = index;
}


bool Menu::isClicked(void) const
{
  return (pimpl->clicked_index_ == InvalidIndex) ? false : true;
}


size_t Menu::clickedId(void) const
{
  if (pimpl->clicked_index_ == InvalidIndex) {
    return InvalidId;
  } else {
    return pimpl->button_order_[pimpl->clicked_index_];
  }
}


int Menu::clickedIndex(void) const
{
  return pimpl->clicked_index_;
}


void Menu::registerIconLabel(Component& icon_label, bool draw_front)
{
  // !!! dynamic_cast の使い方を見直す
  Label* raw_label = dynamic_cast<Label*>(&icon_label);
  if (raw_label == NULL) {
    log_printf("Menu::registerIconLabel(): no Label obuject.\n");
    return;
  }

  pimpl->icon_label_ = raw_label;
  pimpl->icon_draw_front_ = draw_front;
}


void Menu::unregisterIconLabel(void)
{
  pimpl->icon_label_ = NULL;
}


void Menu::setIconOffset(const Point<long>& offset)
{
  pimpl->icon_offset_ = offset;
}


void Menu::connectPressedEvent(ButtonIdCallback::slot_type& slot)
{
  static_cast<void>(slot);
  // !!!
}
