/*****************************************************************************
 * $LastChangedDate: 2011-04-23 21:07:07 -0400 (Sat, 23 Apr 2011) $
 * @file
 * @author  Jim E. Brooks  http://www.palomino3d.org
 * @brief   Joystick device class (Windows).
 * @remarks - Typically, the range of a joystick axis on Windows is 0..0xffff.
 *            To be similar to the Linux joystick code, 0x8000 is subtracted.
 *          - Link with winmm.lib.
 *//*
 * LEGAL:   COPYRIGHT (C) 2004 JIM E. BROOKS
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE (GPL).
 *****************************************************************************/

#if OS_WINDOWS

#define INPUT_DEVICE_JOYSTICK_WINDOWS_CC 1
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <windows.h>
#include <Mmsystem.h>
#include <io.h>
#include "base/module.hh"
#include "base/conf.hh"
#include "base/stream.hh"
using namespace base;
#include "input/module.hh"
#include "input/device_joystick.hh"
#include "input/device_joystick_windows.hh"

namespace input {

INTERN const uint JOYSTICK_WINDOWS_NUM         = 0;
INTERN const uint JOYSTICK_WINDOWS_MAX_AXIS    = 6;    // {X,Y,Z,R,U,V} max possible axises, actual axises determined at run-time
INTERN const uint JOYSTICK_WINDOWS_MAX_BUTTONS = 4;
INTERN const uint JOYSTICK_WINDOWS_BUTTON_MASK = 0xf;  // 4 bits:buttons
INTERN const uint JOYSTICK_WINDOWS_THRESHOLD   = 500;  // affects sensitivity of joystick movement

////////////////////////////////////////////////////////////////////////////////
/////////////////////////  JoystickDeviceWindows  //////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * ctor/dtor.
 *****************************************************************************/
JoystickDeviceWindows::JoystickDeviceWindows( void )
:   mOpened(false),
    mAxisCount(3)
{
    // NOP
}

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

/*****************************************************************************
 * Open joystick device.
 *****************************************************************************/
bool
JoystickDeviceWindows::Open( void )
{
    // Test the joystick once then remember the result.
    PERSISTENT int sOnce = 0;
    if ( sOnce++ )
    {
        return mOpened;
    }
    else
    {
        // Test if a joystick is present.
        JOYINFOEX joyinfo;
        joyinfo.dwSize  = sizeof(joyinfo);
        joyinfo.dwFlags = JOY_RETURNALL;
        const UINT wNumDevs = joyGetNumDevs();
        if ( wNumDevs == 0 )
            return (mOpened = false);
        const BOOL bDev1Attached = (joyGetPosEx(JOYSTICKID1,&joyinfo) != JOYERR_UNPLUGGED);
        const BOOL bDev2Attached = (joyGetPosEx(JOYSTICKID2,&joyinfo) != JOYERR_UNPLUGGED) and (wNumDevs == 2);
        if ( bDev1Attached or bDev2Attached )
        {
            // A joystick is present.
            // Determine how many axises it has.
            JOYCAPS joyCaps;
            if ( joyGetDevCaps( JOYSTICK_WINDOWS_NUM, &joyCaps, sizeof(joyCaps) ) == JOYERR_NOERROR )
                mAxisCount = joyCaps.wNumAxes;
            else
                mAxisCount = 3;

            // Succeess.
            return (mOpened = true);
        }
        else 
        {
            // No joystick.
            return (mOpened = false);
        }
    }
}

/*****************************************************************************
 * Close joystick device.
 *****************************************************************************/
void
JoystickDeviceWindows::Close( void )
{
    // NOP
}

/*****************************************************************************
 * Poll the next joystick event (non-blocking).
 * @return False if no more events.
 *****************************************************************************/
bool
JoystickDeviceWindows::PollJoystickDevice( JoystickEvent& event /*OUT*/ )
{
    // Clear caller's Event in case false no more events.
    event.Clear();

    // Is joystick enabled?
    if ( IfEnabled() )
    {
        ASSERT( mOpened );  // check IfEnabled()

        // Poll all axises.
        // For better responsiveness, during this tick, poll all axises.
        // This has to be done since the Windows joystick driver isn't a queue
        // (this code functions as if it were to directly read the PC I/O ports).
        // Note: This is done behind-the-back of Joystick::Tick()
        // as it thinks one event is enqueued at a time (ok).
        for ( uint axis = 0; axis < mAxisCount; ++axis )
        {
            if ( PollAxisEvent( event, axis ) )
                Enqueue( event );
        }

        // Poll button.
        return PollButtonEvent( event );
    }
    else
    {
        // Joystick disabled.
        return false;
    }
}

/*****************************************************************************
 * Poll for one axis.
 *****************************************************************************/
bool
JoystickDeviceWindows::PollAxisEvent( JoystickEvent& event /*OUT*/, const uint axisNum )
{
ASSERT( MAX_AXIS_VALUE == 0x7fff );

    // Clear event struct.
    event.Clear();

    // Poll the joystick driver.
    DWORD dwFlags = 0;
    switch ( axisNum )
    {
        case 0: dwFlags = JOY_RETURNX; break;
        case 1: dwFlags = JOY_RETURNY; break;
        case 2: dwFlags = JOY_RETURNZ; break;
        case 3: dwFlags = JOY_RETURNR; break;
        case 4: dwFlags = JOY_RETURNU; break;
        case 5: dwFlags = JOY_RETURNV; break;
        default:
        {
            event.Clear();  // ignore superfluous axis
            return false;
        }
        break;
    }
    JOYINFOEX joyinfo;  // extended joystick info
    base::CLEAR( &joyinfo, sizeof(joyinfo) );
    joyinfo.dwSize  = sizeof(joyinfo);
    joyinfo.dwFlags = dwFlags;
    if ( joyGetPosEx( 0, &joyinfo ) != JOYERR_NOERROR )
    {
        event.Clear();
        return false;
    }
    else
    {
        // Fill Event struct.
        // The range of a joystick axis on Windows is {0..0xffff}
        // so remap it to the system-neutral range {-0x7fff..+0x7fff}.
        event.mAxis = axisNum;
        switch ( axisNum )
        {
            case 0: event.mAxisVal = joyinfo.dwXpos - MAX_AXIS_VALUE; break;
            case 1: event.mAxisVal = joyinfo.dwYpos - MAX_AXIS_VALUE; break;
            case 2: event.mAxisVal = joyinfo.dwZpos - MAX_AXIS_VALUE; break;
            case 3: event.mAxisVal = joyinfo.dwRpos - MAX_AXIS_VALUE; break;
            case 4: event.mAxisVal = joyinfo.dwUpos - MAX_AXIS_VALUE; break;
            case 5: event.mAxisVal = joyinfo.dwVpos - MAX_AXIS_VALUE; break;
        }

        // Adjust axis value (0x8000 will appear on Windows but is 1 too much).
        if ( event.mAxisVal < -0x7fff )
            event.mAxisVal = -0x7fff;
        else if ( event.mAxisVal > 0x7fff )
            event.mAxisVal = 0x7fff;

        // Discard event if axis hasn't changed.
        PERSISTENT uint sAxisVals[JOYSTICK_WINDOWS_MAX_AXIS];
        if ( event.mAxisVal != sAxisVals[axisNum] )
        {
            ASSERT( axisNum < JOYSTICK_WINDOWS_MAX_AXIS );
            sAxisVals[axisNum] = event.mAxisVal;
            return true;
        }
        else
        {
            event.Clear();  // axis hasn't moved
            return false;
        }
    }
}

/*****************************************************************************
 * Poll for any buttons.
 *****************************************************************************/
bool
JoystickDeviceWindows::PollButtonEvent( JoystickEvent& event /*OUT*/ )
{
    // Clear event struct.
    event.Clear();

    // Poll the joystick driver.
    JOYINFO joyinfo;
    if ( joyGetPos( 0, &joyinfo ) != JOYERR_NOERROR )
    {
        return false;  // no event or error
    }
    else
    {
        // Button state.
        uint            buttonDelta;
        PERSISTENT uint sButtonPrev = 0;
        buttonDelta = joyinfo.wButtons ^ sButtonPrev;
        buttonDelta &= JOYSTICK_WINDOWS_BUTTON_MASK;  // ignore other bits
        if ( buttonDelta )
        {
            // Buttons are indicated by bits.
            // Correlate bit to its decimal number.
            uint button;
            for ( button = 0; button < JOYSTICK_WINDOWS_MAX_BUTTONS; ++button )
            {
                if ( buttonDelta & (1<<button) )
                    break;
            }
            ASSERT( button < JOYSTICK_WINDOWS_MAX_BUTTONS );
            event.mButton    = button;  // zero-based
            event.mButtonVal = BOOL_CAST( joyinfo.wButtons & (1<<button) );

            // Since only one button is reported at a time,
            // button_prev must record only the reported button,
            // in order to possibly report changes to other buttons.
            sButtonPrev &= (~(1<<button)); // merge reported bit, keep other bits
            sButtonPrev |= (joyinfo.wButtons & (1<<button));
            return true;
        }
        else
        {
            sButtonPrev = joyinfo.wButtons;
            return false;  // no event
        }
    }
}

/*****************************************************************************
 * Print status of joystick.
 *****************************************************************************/
void
JoystickDeviceWindows::PrintStatus( void )
{
    if ( (not BASE_CONF.mQuiet) and IfEnabled() )
    {
        CLOG << "Enabled Windows joystick (axises=" << mAxisCount << ')' << std::endl;
    }
}

} // namespace input

#endif // OS_WINDOWS
