/*****************************************************************************
 * $LastChangedDate: 2011-04-23 21:07:07 -0400 (Sat, 23 Apr 2011) $
 * @file
 * @author  Jim E. Brooks  http://www.palomino3d.org
 * @brief   Higher-level Joystick class.
 *//*
 * LEGAL:   COPYRIGHT (C) 2004 JIM E. BROOKS
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE VERSION 2 (GPL 2).
 *****************************************************************************/

#ifndef CONTROL_JOYSTICK_HH
#define CONTROL_JOYSTICK_HH 1

// Get Joystick singleton of control module (input module has GET_JOYSTICK_DEVICE()).
#define GET_CONTROL_JOYSTICK() (control::Joystick::GetInstance())

#include <vector>
#include "base/singleton.hh"
#include "base/event.hh"
#include "base/array_tiny.hh"
using namespace base;
#include "input/module.hh"
#include "input/device_joystick.hh"
using namespace input;

namespace control {

////////////////////////////////////////////////////////////////////////////////
/// @brief Joystick control of a Craft, joystick calibration.
///
/// This is the client of the lower-level input::JoystickDevice.
/// This handles axis events: the current Craft is rotated by axis events.
/// This doesn't handle button events, rather, it defers them to a listener.
///
/// This class isn't derived from input::JoystickDevice.
/// Instead, this class is a layer over it.
///
/// To register a button handler, register an event listener
/// with Joystick::mEventButton (see base/event.hh).
///
/// Event classes:
/// base::Event is an object for broadcasting to listeners.
/// JoystickEvent is a simple struct.
///
class Joystick : public Singleton
{
PREVENT_COPYING(Joystick)
public:
    CLASS_CONST uint MAX_AXIS    = 4;  ///< pitch,roll,yaw,throttle = 4
    CLASS_CONST uint MAX_BUTTONS = 8;

public:
    /// @brief Defines a range of axis values for calibration.
    /// Roll/pitch/yaw axises have two ranges with a deadzone in between.
    /// Throttle axis has one range.
    ///
    struct Calibration
    {
        /// @brief min,max
        ///
        struct AxisRange
        {
            AxisRange( void )
            : mMin(0), mMax(input::JoystickDevice::MAX_AXIS_VALUE)
            { }
            int mMin, mMax;  // NOTE: min/max may be both positive or negative
        };
        AxisRange mPitchUp,  mPitchDown;
        AxisRange mRollLeft, mRollRight;
        AxisRange mYawLeft,  mYawRight;
        AxisRange mThrottle;
    };

private:
    /// Pitch/roll/yaw have two active segments/ranges with a deadzone between.
    /// Throttle has one segment/range.
    enum eMove
    {
        eMove_AT_CENTER,    // no movement
        eMove_PITCH_UP,
        eMove_PITCH_DOWN,
        eMove_ROLL_LEFT,
        eMove_ROLL_RIGHT,
        eMove_YAW_LEFT,
        eMove_YAW_RIGHT,
        eMove_THROTTLE,     // throtle has no deadzone/center
    };

public:
    /// @brief Remaps joystick axises and indicates which axis had an event.
    /// Calling the If*() methods effectively remaps the axises
    /// (that is, remapping isn't done by changing the Event struct).
    ///
    class AxisMap
    {
    friend class Joystick;
    public:
                enum { ROLL, PITCH, YAW, THROTTLE };
                AxisMap( void );
        bool    IfRoll( const input::JoystickEvent& event );
        bool    IfPitch( const input::JoystickEvent& event );
        bool    IfYaw( const input::JoystickEvent& event );
        bool    IfThrottle( const input::JoystickEvent& event );
    private:
        std::vector<uint> mAxises;
    };
    friend class AxisMap;

private:
    /// @brief Remaps buttons.
    ///
    class ButtonMap
    {
    public:
                ButtonMap( void );
        uint    size( void ) const { return mReal2Logical.size(); }
        bool    MapButton( const uint buttonLogical, const uint buttonReal );
        uint    GetLogicalButton( const uint realButton );
    private:
        std::vector<uint> mReal2Logical;   ///< index is real button, value is logical/remapped button
    };

private:
    /// @brief Polls joystick events.
    CLASS_TICK_FUNCTOR( control::Joystick, mJoystick )

private:
    Joystick( void );  // Singleton
    ~Joystick();

public:
                DEFINE_GetInstance( Joystick )  // Singleton
    bool        Enable( const bool enable = true );
    bool        IfEnabled( void );
    void        SetCalibration( const Calibration& calibration );
    Calibration GetCalibration( void );
    bool        MapAxis( const uint axisLogical, const uint axisReal );
    bool        MapButton( const uint buttonLogical, const uint buttonReal );
    void        EnableAutofire( bool f );
    int         GetAxisValue( const uint axis );
    bool        GetButtonValue( const uint button );

private:
    void        Poll( void );
    void        ProcessAxisEvent( const input::JoystickEvent& event );
    void        ProcessButtonEvent( const input::JoystickEvent& eventUnmapped );
    fp          ComputeFraction( const input::JoystickEvent& event, const uint axis, const Calibration::AxisRange& axisRange, const int dir );

public:
    base::Event<input::JoystickEvent> mEventButton;  //< for broadcasting button on/off event to registered listeners

private:
    DECLARE_SINGLETON_CLASS_VARS( Joystick )
    bool                        mEnabledBefore;     ///< if joystick was really enabled before
    Calibration                 mCalibration;       ///< defines range of axis values
    AxisMap                     mAxisMap;           ///< to configure axises
    ButtonMap                   mButtonMap;         ///< to configure buttons
    input::JoystickEvent        mEvent;             ///< current event
    input::JoystickEvent        mPrevEvent;         ///< updated only if buttons changed or autofire
    bool                        mAutofire;
    TinyArray<MAX_AXIS,int>     mAxisValues;        ///< for calibration (not used internally)
    TinyArray<MAX_BUTTONS,bool> mButtonValues;      ///< for calibration (not used internally)
    fp                          mRollFraction;      ///< these continue axis movement
    fp                          mPitchFraction;     ///< until axis is moved to dead-zone
    fp                          mYawFraction;
    const Milliseconds          mTickFreq;
};

} // namespace control

#endif // CONTROL_JOYSTICK_HH
