/*****************************************************************************
 * $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 (Linux).
 * @remarks FreeBSD's devel/linux-js port emulates a Linux joystick driver.
 *//*
 * 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).
 *****************************************************************************/

#if COMPILE_LINUX_JOYSTICK

#define INPUT_DEVICE_JOYSTICK_LINUX_CC 1
#include "base/module.hh"
#include "base/conf.hh"
#include "base/stream.hh"
using namespace base;
#include "input/module.hh"
#include "input/conf.hh"
#include "input/device_joystick.hh"
#include "input/device_joystick_linux.hh"

#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <linux/joystick.h>

namespace input {

////////////////////////////////////////////////////////////////////////////////
////////////////////////  JoystickDeviceLinux  /////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * ctor/dtor.
 *****************************************************************************/
JoystickDeviceLinux::JoystickDeviceLinux( void )
:   mFiledesc(-1),
    mJoystickNum(INPUT_CONF.mJoystickNum)
{
    // Let base Joystick ctor catch more than once instance.
}

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

/*****************************************************************************
 * Open Linux joystick device.
 *****************************************************************************/
bool
JoystickDeviceLinux::Open( void )
{
    // Which joystick.
    const char jn      = '0' + char(mJoystickNum);
    string deviceName1 = "/dev/input/js"; deviceName1 += jn;
    string deviceName2 = "/dev/js";       deviceName2 += jn; // deprecated

    // Open the joystick device only once.  Repetively opening-and-closing
    // it causes superfluous events and unreliable joystick readings.
    if ( mFiledesc != -1 )
    {
        // Even though redundant with Joystick::Open().
        return true;
    }

    // Open in non-blocking mode.
    mFiledesc = open( deviceName1.c_str(), O_RDONLY|O_NONBLOCK );
    if ( mFiledesc == -1 )
    {
        // Try alternate/old device file.
        mFiledesc = open( deviceName2.c_str(), O_RDONLY|O_NONBLOCK );
    }

    BEGIN_ONCE
    {
        if ( mFiledesc == -1 )
        {
            CERROR << "Can't open joystick device at "
                   << deviceName1 << " nor " << deviceName2 << '.' << std::endl
                   << "Try modprobe joydev." << std::endl;
        #if OS_FREEBSD
            CERROR << "On FreeBSD, install the port devel/linux-js" << std::endl
                   << "which emulates the Linux joystick driver /dev/input/js0" << std::endl;
        #endif
        }
    } END_ONCE

    return mFiledesc != -1;
}

/*****************************************************************************
 * Close Linux joystick device.
 *****************************************************************************/
void
JoystickDeviceLinux::Close( void )
{
    if ( mFiledesc != -1 )
        close( mFiledesc );

    mFiledesc = -1;  // remember it was closed
}

/*****************************************************************************
 * Dequeue one event from Linux driver and enqueue.
 * @return False if no more events.
 *****************************************************************************/
bool
JoystickDeviceLinux::PollJoystickDevice( JoystickEvent& event /*OUT*/ )
{
    // Base Joystick ensures this isn't recursed (busy flag).

    // Clear caller's Event in case false no more events.
    event.Clear();

    // Button events have priority over axis events.
    if ( not IfEnabled() )
        return false;
    ASSERT( mFiledesc != -1 );  // check IfEnabled()

    // Read the next event from the Linux joystick driver.
    struct js_event eventLinux;
    const int bytesRead = read( mFiledesc, &eventLinux, sizeof(struct js_event) );
    if ( bytesRead != sizeof(struct js_event) )
        return false;

    // Button event?
    if ( eventLinux.type == JS_EVENT_BUTTON )
    {
        event.mButton    = eventLinux.number;   // zero based
        event.mButtonVal = eventLinux.value;    // bool: pressed/released
        return true;
    }

    // An axis event?
    if ( eventLinux.type == JS_EVENT_AXIS )
    {
        event.mAxis    = eventLinux.number;  // zero based
        event.mAxisVal = eventLinux.value;
        return true;
    }

    return false;
}

/*****************************************************************************
 * Print status of joystick.
 *****************************************************************************/
void
JoystickDeviceLinux::PrintStatus( void )
{
    if ( not BASE_CONF.mQuiet )
    {
        if ( IfEnabled() )
        {
        #if OS_FREEBSD
            CLOG << "Enabled Linux joystick " << mJoystickNum << " (on FreeBSD)." << std::endl;
        #else
            CLOG << "Enabled Linux joystick " << mJoystickNum << '.' << std::endl;
        #endif
        }
        else
        {
            CLOG << "Linux joystick " << mJoystickNum << " not detected." << std::endl;
        }
    }
}

} // namespace input

#endif // COMPILE_LINUX_JOYSTICK 
