/*****************************************************************************
 * $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.
 * @verbatim
 *
 * Overview:
 * ---------
 * Every tick, joystick events are dequeued from input::JoystickDevice's input queue.
 * control::Joystick handles axis movements by manipulating the current Craft (defined by Control).
 *
 * Continuing of axis events:
 * --------------------------
 * The handler will continue an axis event until another one supercedes it.
 * For example, holding the joystick all the way right for a while
 * is supposed to continually do a roll, yet one axis event will be enqueued
 * at the axis extreme then enqueuing will stop.
 *
 * @endverbatim
 *//*
 * 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).
 *****************************************************************************/

#define CONTROL_JOYSTICK_CC 1
#include "base/module.hh"
#include "base/busy.hh"
#include "base/conf.hh"
#include "base/stream.hh"
using namespace base;
#include "math/module.hh"
#include "math/funcs.hh"
#include "math/funcs_debug.hh"
using namespace math;
#include "input/module.hh"
#include "input/device_joystick.hh"
#include "input/conf.hh"
using namespace input;
#include "control/module.hh"
#include "control/joystick.hh"
#include "control/defs.hh"
#include "control/conf.hh"

namespace control {

////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////  Joystick  ///////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

DEFINE_SINGLETON_CLASS_VARS( Joystick )

/*****************************************************************************
 * ctor/dtor.
 *****************************************************************************/
Joystick::Joystick( void )
:   mEnabledBefore(false),
    mCalibration(),
    mAxisMap(),
    mButtonMap(),
    mEvent(),
    mPrevEvent(),
    mAutofire(false),
    mAxisValues(0),        // fill TinyArray
    mButtonValues(false),  // fill TinyArray
    mRollFraction(0.0f),
    mPitchFraction(0.0f),
    mYawFraction(0.0f),
    mTickFreq(INPUT_CONF.mInputQueueTickFreq)  // use same frequency as input::JoysticDevice
{
ASSERT_RUN_ONCE;
    // NOP
}

Joystick::~Joystick()
{
    // NOP
}

/*****************************************************************************
 * Enable joystick.
 * @return True if joystick is really enabled.
 *****************************************************************************/
bool
Joystick::Enable( const bool enable )
{
    // Try to enable joystick device which may or may not be available.
    JoystickDevice& joystickDevice = GET_JOYSTICK_DEVICE();
    const bool joystickDeviceEnabled = joystickDevice.Enable( enable );

    // Do these actions once:
    if ( joystickDeviceEnabled and (not mEnabledBefore) )
    {
        mEnabledBefore = true;

        // Confirm to user that joystick is available.
        joystickDevice.PrintStatus();

        // Register a timer functor to poll for joystick events to control the current Craft.
        // Use timer-tick frequency defined (and limited) by INPUT_CONF.mInputQueueTickFreq.
        GET_TIMER().Register( new TickFunctor(*this), mTickFreq );
    }

    return joystickDeviceEnabled;
}

/*****************************************************************************
 * @return True if joystick is enabled.
 *****************************************************************************/
bool
Joystick::IfEnabled( void )
{
    // Defer to lower-level JoystickDevice.
    return GET_JOYSTICK_DEVICE().IfEnabled();
}

/*****************************************************************************
 * Set/get calibration.
 *****************************************************************************/
void
Joystick::SetCalibration( const Calibration& calibration )
{
    mCalibration = calibration;
}

Joystick::Calibration
Joystick::GetCalibration( void )
{
    return mCalibration;
}

/*****************************************************************************
 * Map a joystick axis.
 *****************************************************************************/
bool
Joystick::MapAxis( const uint axisLogical, const uint axisReal )
{
ASSERT( mAxisMap.mAxises.size() == MAX_AXIS );

    if ( (axisLogical < mAxisMap.mAxises.size())
     and (axisReal    < mAxisMap.mAxises.size()) )
    {
        mAxisMap.mAxises[axisLogical] = axisReal;
        return true;
    }
    else
    {
        CERROR << "WARNING: invalid axis for AxisMap " << axisLogical << ' ' << axisReal << std::endl;
        return false;
    }
}

/*****************************************************************************
 * Map button.
 *****************************************************************************/
bool
Joystick::MapButton( const uint buttonLogical, const uint buttonReal )
{
    return mButtonMap.MapButton( buttonLogical, buttonReal );
}

/*****************************************************************************
 * Timer-tick handler.
 *****************************************************************************/
void
Joystick::TickFunctor::Tick( UNUSED const Milliseconds millisecElapsed )
{
    BUSY_BEGIN {            // prevent being interrupted by another timer-tick
        mJoystick.Poll();   // a loop around polling here caused problems
    } BUSY_END
}

/*****************************************************************************
 * Poll joystick axises and buttons.
 *****************************************************************************/
void
Joystick::Poll( void )
{
    // Is joystick enabled?
    if ( GET_JOYSTICK_DEVICE().IfEnabled() )
    {
        // Try to dequeue an event.  Queue could be empty.
        GET_JOYSTICK_DEVICE().Dequeue( mEvent /*OUT*/ );

        // These two methods are called always.
        // Axis events may be repeated since movement
        // must continue until moved back to the dead-zone.
        // Button events may be repeated in case of autofire.
        ProcessAxisEvent( mEvent );
        ProcessButtonEvent( mEvent );
    }
}

/*****************************************************************************
 * Process a joystick axis event.
 *
 * Continuing axis movements:
 * Imagine the joystick moved to top-left corner then moved up and down.
 * The leftward movement remains in effect while the new events are occurring
 * for up/down movements.  Therefore, persistent variables are used to
 * continue axis movement until the axis is moved to its dead-zone.
 *
 * Remapping axises:
 * Remapping an axis is done effectively by calling AxisMap::If*()
 * (remapping isn't done by changing the Event struct).
 *****************************************************************************/
void
Joystick::ProcessAxisEvent( const input::JoystickEvent& event )
{
    control::Control& control = GET_CONTROL();
    shptr0<Craft>     craft   = control.GetCurrentCraft();

    // For GetAxisValue().
    if ( event.mAxis < mAxisValues.size() )  // could be NONE=-1 or superfluous axis
        mAxisValues[event.mAxis] = event.mAxisVal;

    // If axis val is between {-max..-min} or {+min..+max}
    #define IF_IN_AXIS_RANGE( AXIS_RANGE )                                                  \
        ((event.mAxisVal < 0)                                                               \
         and (event.mAxisVal >= AXIS_RANGE.mMax) and (event.mAxisVal <= AXIS_RANGE.mMin))   \
     or ((event.mAxisVal >= 0)                                                              \
         and (event.mAxisVal >= AXIS_RANGE.mMin) and (event.mAxisVal <= AXIS_RANGE.mMax))

    // -------------------------------------------------------------------------
    // Roll:
    if ( mAxisMap.IfRoll( event ) )
    {
        if ( IF_IN_AXIS_RANGE( mCalibration.mRollLeft ) )
        {
            mRollFraction = ComputeFraction( event, AXIS_ROLL, mCalibration.mRollLeft, control.GetRollLeftDir() );
        }
        else if ( IF_IN_AXIS_RANGE( mCalibration.mRollRight ) )
        {
            mRollFraction = ComputeFraction( event, AXIS_ROLL, mCalibration.mRollRight, control.GetRollRightDir() );
        }
        else
        {
            mRollFraction = 0.0f;  // moved to dead-zone
        }
    }
    // -------------------------------------------------------------------------
    // Pitch:
    if ( mAxisMap.IfPitch( event ) )
    {
        if ( IF_IN_AXIS_RANGE( mCalibration.mPitchDown ) )
        {
            mPitchFraction = ComputeFraction( event, AXIS_PITCH, mCalibration.mPitchDown, control.GetPitchDownDir() );
        }
        else if ( IF_IN_AXIS_RANGE( mCalibration.mPitchUp ) )
        {
            mPitchFraction = ComputeFraction( event, AXIS_PITCH, mCalibration.mPitchUp, control.GetPitchUpDir() );
        }
        else
        {
            mPitchFraction = 0.0f;  // moved to dead-zone
        }
    }
    // -------------------------------------------------------------------------
    // Yaw:
    if ( mAxisMap.IfYaw( event ) )
    {
        if ( IF_IN_AXIS_RANGE( mCalibration.mYawLeft ) )
        {
            mYawFraction = ComputeFraction( event, AXIS_YAW, mCalibration.mYawLeft, control.GetYawLeftDir() );
        }
        else if ( IF_IN_AXIS_RANGE( mCalibration.mYawRight ) )
        {
            mYawFraction = ComputeFraction( event, AXIS_YAW, mCalibration.mYawRight, control.GetYawRightDir() );
        }
        else
        {
            mYawFraction = 0.0f;  // moved to dead-zone
        }
    }
    // -------------------------------------------------------------------------
    // Throttle:
    // Different since throttle has no center dead-zone.
    if ( mAxisMap.IfThrottle(event) )
    {
        // Some ThrottleRange possibilities:
        // ThrottleRange 0 0x7fff
        // ThrottleRange 0x7fff 0  # min > max (reverse throttle's direction)
        fp d, fraction;
        if ( mCalibration.mThrottle.mMin < mCalibration.mThrottle.mMax )
        {
            d = mCalibration.mThrottle.mMax - mCalibration.mThrottle.mMin;
            fraction = (event.mAxisVal - mCalibration.mThrottle.mMin) / d;
        }
        else  // min < max (backwards)
        {
            d = mCalibration.mThrottle.mMin - mCalibration.mThrottle.mMax;
            fraction = 1.0f - (event.mAxisVal - mCalibration.mThrottle.mMax) / d;
        }

        // Clamp fraction (axis value could be outside calibration range).
        fraction = Range<fp>( fraction, 0.0f, 1.0f );

        // Set Craft's throttle.
        craft->SetThrottle( fraction );
    }

    // Although one axis event happens at a time,
    // previous axis movements remain in effect
    // until the axis is moved to its dead-zone.
    // Rotate the current craft (for stability, ignore small rotations.
    if ( ABS(mRollFraction)  > control::defs::JOYSTICK_MIN_FRACTION ) craft->PhysicalRotate( AXIS_ROLL,  mRollFraction,  mTickFreq );
    if ( ABS(mPitchFraction) > control::defs::JOYSTICK_MIN_FRACTION ) craft->PhysicalRotate( AXIS_PITCH, mPitchFraction, mTickFreq );
    if ( ABS(mYawFraction)   > control::defs::JOYSTICK_MIN_FRACTION ) craft->PhysicalRotate( AXIS_YAW,   mYawFraction,   mTickFreq );
}

/*****************************************************************************
 * Process a joystick button event.
 *****************************************************************************/
void
Joystick::ProcessButtonEvent( const input::JoystickEvent& eventUnmapped )
{
    // Ignore superfluous buttons.
    if ( eventUnmapped.mButton < MAX_BUTTONS )
    {
        // Remap joystick button.
        input::JoystickEvent event = eventUnmapped;
        event.mButton = mButtonMap.GetLogicalButton( eventUnmapped.mButton );

        // For GetButtonValue().
        if ( event.mButton < mButtonValues.size() )
            mButtonValues[event.mButton] = event.mButtonVal;

        // Ignore if no buttons changed and autofire disabled.
        if ( mAutofire or (not mPrevEvent.IfEqualButtons(event)) )
        {

            // Remember.
            mPrevEvent = event;

            // Broadcast joystick button to listeners.
            mEventButton.Broadcast( event );
        }
    }
}

/*****************************************************************************
 * @return Fraction of axis movement {0.0,..,+-1.0}.  May return negative.
 * @param   event
 * @param   axis
 *          AXIS_ROLL/PITCH/YAW
 * @param   axisRange
 *          Calibration.AxisRange
 * @param   dir
 *          +-1 which can reverse direction of axis.
 *****************************************************************************/
fp
Joystick::ComputeFraction( const input::JoystickEvent& event, const uint axis, const Calibration::AxisRange& axisRange, const int dir )
{
ASSERT( axis < Joystick::MAX_AXIS );  // not ASSERT_AXIS()
ASSERT( ABS(dir) == 1 );
ASSERT( (axisRange.mMax | axisRange.mMin) != 0 );

    // Compute the axis movement as a fraction {0.0,..,1.0}.
    // This fraction is the level of control input that
    // partially determines the degree to rotate the current Craft.
    fp fraction;
    if ( ABS(event.mAxisVal) < ABS(axisRange.mMax) )
        fraction = ABS( fp(event.mAxisVal) / fp(axisRange.mMax) );
    else
        fraction = 1.0f;  // impose ceiling
    fraction *= dir;  // axis direction
    ASSERT( ABS(fraction) < 1.05f );  // ceiling should be 1.0
    return fraction;
}

/*****************************************************************************
 * Enable/disable autofire.
 *****************************************************************************/
void
Joystick::EnableAutofire( bool f )
{
    mAutofire = f;
}

/*****************************************************************************
 * @return Axis value.
 * For clients (not used internally).
 *****************************************************************************/
int
Joystick::GetAxisValue( const uint axis )
{
    // mAxisValues[] is updated by axis events.
    if ( EX( axis < mAxisValues.size() ) )
    {
        return mAxisValues[axis];
    }
    else
    {
        ASSERT(false);
        return 0;  // oops
    }
}

/*****************************************************************************
 * @return Button value.
 * For clients (not used internally).
 *****************************************************************************/
bool
Joystick::GetButtonValue( const uint button )
{
    // mButtonValues[] is updated by button events.
    if ( EX( button < mButtonValues.size() ) )
    {
        return mButtonValues[button];
    }
    else
    {
        ASSERT(false);
        return false;  // oops
    }
}

////////////////////////////////////////////////////////////////////////////////
////////////////////////////  Joystick::AxisMap  ///////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * ctor.
 *****************************************************************************/
Joystick::AxisMap::AxisMap( void )
{
    // Identity map.
    mAxises.resize( Joystick::MAX_AXIS );
    for ( uint i = 0; i < mAxises.size(); ++i )
        mAxises[i] = i;
}

/*****************************************************************************
 * @return True if joystick event struct matches the aircraft control.
 *****************************************************************************/
bool
Joystick::AxisMap::IfRoll( const input::JoystickEvent& event )
{
    return event.mAxis == mAxises[ROLL];
}

bool
Joystick::AxisMap::IfPitch( const input::JoystickEvent& event )
{
    return event.mAxis == mAxises[PITCH];
}

bool
Joystick::AxisMap::IfYaw( const input::JoystickEvent& event )
{
    return event.mAxis == mAxises[YAW];
}

bool
Joystick::AxisMap::IfThrottle( const input::JoystickEvent& event )
{
    return event.mAxis == mAxises[THROTTLE];
}

////////////////////////////////////////////////////////////////////////////////
///////////////////////////  Joystick::ButtonMap  //////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * ctor.
 *****************************************************************************/
Joystick::ButtonMap::ButtonMap( void )
:   mReal2Logical()
{
    // Identity mapped.
    mReal2Logical.resize( Joystick::MAX_BUTTONS );
    for ( uint i = 0; i < Joystick::MAX_BUTTONS; ++i )
        mReal2Logical[i] = i;
}

/*****************************************************************************
 * Remap button.
 *****************************************************************************/
bool
Joystick::ButtonMap::MapButton( const uint buttonLogical, const uint buttonReal )
{
ASSERT( mReal2Logical.size() == Joystick::MAX_BUTTONS );

    if ( (buttonLogical < mReal2Logical.size())
     and (buttonReal    < mReal2Logical.size()) )
    {
        mReal2Logical[buttonReal] = buttonLogical;
        return true;
    }
    else
    {
        CERROR << "WARNING: invalid joystick button for ButtonMap " << buttonLogical << ' ' << buttonReal << std::endl
               << "Max joystick button is " << Joystick::MAX_BUTTONS << std::endl;
        return false;
    }
}

/*****************************************************************************
 * @return Logical (remapped) button.
 *****************************************************************************/
uint
Joystick::ButtonMap::GetLogicalButton( const uint realButton )
{
    if ( realButton < mReal2Logical.size() )
    {
        return mReal2Logical[realButton];
    }
    else
    {
        CERROR << "ERROR[Joystick::GetLogicalButton] invalid real button " << realButton << std::endl;
        return realButton;  // toss back the grenade
    }
}

} // namespace control
