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

  \author Satofumi KAMIMURA

  $Id: UsbJoystick.cpp 775 2009-05-05 08:05:45Z satofumi $

  \todo 接続直後の動作がおかしいのを修正する
*/

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

using namespace qrk;
using namespace std;


/*!
  \brief UsbJoystick の内部クラス
*/
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* getObject(void) {
      static JoystickGroup* obj = new JoystickGroup;
      return obj;
    }
  };
  JoystickGroup* joysticks;
  string error_message;
  size_t own_id;
  size_t axis_num;
  size_t buttons_num;


  pImpl(void)
    : joysticks(JoystickGroup::getObject()), 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)
{
  return pimpl->error_message.c_str();
}


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


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

  } else {
    size_t n = joystickNum();
    if (n <= 0) {
      pimpl->error_message = "No joystick device.";
      return false;
    }
    for (size_t i = 0; i < n; ++i) {
      bool ret = pimpl->connect(i);
      if (ret) {
        return ret;
      }
    }
  }
  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)
{
  return (pimpl->joysticks->js[pimpl->own_id].ptr == NULL) ? false : true;
}


const char* UsbJoystick::productInfo(void)
{
  if (! isConnected()) {
    pimpl->error_message = "not connected.";
    return "not connected.";
  }
  int index = pimpl->joysticks->js[pimpl->own_id].own_index;
  return SDL_JoystickName(index);
}


size_t UsbJoystick::numAxis(void)
{
  return pimpl->axis_num;
}


size_t UsbJoystick::numButtons(void)
{
  return pimpl->buttons_num;
}


void UsbJoystick::setEvent(SDL_Event& event)
{
  switch (event.type) {

  case SDL_JOYAXISMOTION:
    pimpl->joyAxisEventHandler(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(event.jbutton);
    break;
  }
}


short UsbJoystick::axisValue(size_t index)
{
  if (! isConnected()) {
    pimpl->error_message = "not connected.";
    return 0;
  }
  return pimpl->joysticks->js[pimpl->own_id].axis_value[index];
}


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