/*!
  \file
  \brief USB ジョイスティック

  \author Satofumi KAMIMURA

  $Id: UsbJoystick.cpp 1386 2009-10-09 08:00:11Z satofumi $
*/

#include "UsbJoystick.h"
#include "SdlJoystickInit.h"
#include <SDL.h>
#include <SDL_joystick.h>
#include <vector>
#include <string>

using namespace qrk;
using namespace std;


struct UsbJoystick::pImpl : private SdlJoystickInit
{
  //! 複数ジョイスティックの管理
  class JoystickGroup
  {
  public:

    //! ジョイスティック情報の管理
    class JoystickInfo
    {
    public:
      SDL_Joystick* ptr_;
      int own_index_;
      vector<short> axis_value_;
      vector<bool> button_value_;

      JoystickInfo(void) : ptr_(NULL), own_index_(-1)
      {
      }
    };
    vector<JoystickInfo> js_;

    static JoystickGroup* object(void)
    {
      static JoystickGroup singleton_object;
      return &singleton_object;
    }
  };

  JoystickGroup* joysticks_;
  string error_message_;
  size_t own_id_;
  size_t axis_num_;
  size_t buttons_num_;

  pImpl(void)
    : joysticks_(JoystickGroup::object()), error_message_("no error"),
      own_id_(joysticks_->js_.size()), axis_num_(0), buttons_num_(0)
  {
    JoystickGroup::JoystickInfo joystick_info_;
    joysticks_->js_.push_back(joystick_info_);
  }


  bool connect(int index)
  {
    if (SDL_JoystickOpened(index)) {
      error_message_ = "already connected.";
      return false;
    }

    SDL_Joystick* p = SDL_JoystickOpen(index);
    if (p == NULL) {
      error_message_ = SDL_GetError();
      return false;
    }
    joysticks_->js_[own_id_].ptr_ = p;
    joysticks_->js_[own_id_].own_index_ = index;
    axis_num_ = SDL_JoystickNumAxes(p);
    buttons_num_ = SDL_JoystickNumButtons(p);
    for (size_t i = 0; i < axis_num_; ++i) {
      joysticks_->js_[own_id_].axis_value_.push_back(0);
    }
    for (size_t i = 0; i < buttons_num_; ++i) {
      joysticks_->js_[own_id_].button_value_.push_back(false);
    }
    return true;
  }


  void joyAxisEventHandler(SDL_JoyAxisEvent& event)
  {
    joysticks_->js_[event.which].axis_value_[event.axis] = event.value;
  }


  void joyButtonEventHandler(SDL_JoyButtonEvent& event)
  {
    joysticks_->js_[event.which].button_value_[event.button] =
      (event.state == SDL_PRESSED) ? true : false;
  }
};


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


UsbJoystick::~UsbJoystick(void)
{
  disconnect();
}


const char* UsbJoystick::what(void) const
{
  return pimpl->error_message_.c_str();
}


bool UsbJoystick::connect(int id)
{
  if (id >= 0) {
    return pimpl->connect(id);

  } else {
    size_t n = joystickNum();
    if (n <= 0) {
      pimpl->error_message_ = "No joystick device.";
      return false;
    }
    for (size_t i = 0; i < n; ++i) {
      if (pimpl->connect(i)) {
        return true;
      }
    }
  }
  pimpl->error_message_ = "Joystick devices are full connected.";
  return false;
}


void UsbJoystick::disconnect(void)
{
  if (isConnected()) {
    SDL_JoystickClose(pimpl->joysticks_->js_[pimpl->own_id_].ptr_);
  }
}


bool UsbJoystick::isConnected(void) const
{
  return (pimpl->joysticks_->js_[pimpl->own_id_].ptr_ == NULL) ? false : true;
}


size_t UsbJoystick::axisNum(void) const
{
  return pimpl->axis_num_;
}


int UsbJoystick::axisValue(size_t index)
{
  if (! isConnected()) {
    pimpl->error_message_ = "not connected.";
    return 0;
  }
  return pimpl->joysticks_->js_[pimpl->own_id_].axis_value_[index];
}


size_t UsbJoystick::buttonsNum(void) const
{
  return pimpl->buttons_num_;
}


bool UsbJoystick::isButtonPressed(size_t index)
{
  if (! isConnected()) {
    pimpl->error_message_ = "not connected.";
    return 0;
  }
  return pimpl->joysticks_->js_[pimpl->own_id_].button_value_[index];
}


size_t UsbJoystick::joystickNum(void)
{
  return SDL_NumJoysticks();
}


void UsbJoystick::setEvent(void* event)
{
  SDL_Event* sdl_event = static_cast<SDL_Event*>(event);
  switch (sdl_event->type) {

  case SDL_JOYAXISMOTION:
    pimpl->joyAxisEventHandler(sdl_event->jaxis);
    break;

#if 0
  case SDL_JOYBALLMOTION:
    pimpl->joyBallEventHandler(event.jball);
    break;

  case SDL_JOYHATMOTION:
    pimpl->joyHatEventHandler(event.jhat);
    break;
#endif

  case SDL_JOYBUTTONDOWN:
  case SDL_JOYBUTTONUP:
    pimpl->joyButtonEventHandler(sdl_event->jbutton);
    break;
  }
}
