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

#ifndef INPUT_DEVICE_HH
#define INPUT_DEVICE_HH 1

#include <deque>
#include "base/event.hh"

namespace input {

////////////////////////////////////////////////////////////////////////////////
/// @brief Device base class.
///
/// A concrete Device class will consist of:
/// - enabling and accessing a system device
/// - queueing of events
/// - broadcasting of events
///
/// All Devices shall be initially disabled to let program become ready.
///
/// Implementation:
/// - Enabling involves opening the actual device by Device::EnableDevice()
///   and enabling the queueing of events.
/// - EnableDevice() shall be called ONLY ONCE by the base Device class.
///   Once enabled, the actual devices shall remain enabled.
///   Disabling a device can be done logicaly by disabling its queue.
///   EnableDevice() and Enable() form a Template Method pattern.
/// - Factoring: There are obstacles to factoring-out a separate queue class.
///   Device must register a listener which Enqueue() must broadcast to.
///
template<class DeviceEvent>
class Device
{
PREVENT_COPYING(Device)
protected:
            Device( void );
    virtual ~Device();
    virtual bool EnableDevice( void ) = 0;  // Template Method pattern, called only once

//------------------------------------------------------------------------------
// Interface:

// Device enablement:
public:
    bool    Enable( const bool enable = true );
    bool    IfEnabled( void ) const;

// Event queueing:
public:
    void    Enqueue( const DeviceEvent& event );
    bool    PeekQueue( DeviceEvent& event /*OUT*/ ) const;
    bool    Dequeue( DeviceEvent& event /*OUT*/ );
    uint    QueueSize( void ) const;
    void    SetQueueOverflow( const uint overflow );
private:
    void    VentQueue( void );

// Event listening:
public:
    typedef base::Event<>::ListenerFunctor ListenerFunctor;
    void    RegisterListener( shptr<ListenerFunctor> listener );
    void    UnregisterListener( shptr<ListenerFunctor> listener );

//------------------------------------------------------------------------------

private:
    // Device enabling:
    bool            mEnabledLogical;        ///< if device and input queue are both enabled
    bool            mEnabledDevice;         ///< if EnableDevice() succeeded (assigned only once)
    bool            mTriedToEnableDevice;   ///< if tried to call EnableDevice()
    // Queueing:
    typedef std::deque<DeviceEvent> Queue;
    Queue           mQueue;                 ///< queue of events
    uint            mQueueOverflow;         ///< vent if more than this many events
    // Event broadcasting:
    base::Event<>   mBroadcaster;           ///< object used to broadcast input events to listeners
};

/*****************************************************************************
 * ctor/dtor.
 *****************************************************************************/
template<class DeviceEvent>
Device<DeviceEvent>::Device( void )
:   mEnabledLogical(false),
    mEnabledDevice(false),
    mTriedToEnableDevice(false),
    mQueue(),
    mQueueOverflow(0),
    mBroadcaster()
{
    // NOP
}

template<class DeviceEvent>
Device<DeviceEvent>::~Device()
{
    // NOP
}

////////////////////////////////////////////////////////////////////////////////
//////////////////////////  Device : device enabling  //////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * Template Method pattern.
 * @return Try to enable device, return true if enabled.
 *****************************************************************************/
template<class DeviceEvent>
bool
Device<DeviceEvent>::Enable( const bool enable )
{
    if ( enable )
    {
        // Try to enable the actual device only once.
        if ( not mTriedToEnableDevice )
        {
            mTriedToEnableDevice = true;
            mEnabledDevice = EnableDevice();  // mEnabledDevice is one-shot
        }

        // Logically enable if the actual device is enabled.
        mEnabledLogical = mEnabledDevice;     
    }
    else  // disable
    {
        mEnabledLogical = false;
    }

    return mEnabledLogical;
}

/*****************************************************************************
 * @return True if device is enabled.
 *****************************************************************************/
template<class DeviceEvent>
bool
Device<DeviceEvent>::IfEnabled( void ) const
{
    return mEnabledLogical;
}

////////////////////////////////////////////////////////////////////////////////
//////////////////////////  Device : event queueing  ///////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * Enqueue an event.
 *****************************************************************************/
template<class DeviceEvent>
void
Device<DeviceEvent>::Enqueue( const DeviceEvent& event )
{
    // NOP if queue is disabled.
    if ( IfEnabled() )
    {
        // Now is an opportunity to vent overflowing older events.
        VentQueue();

        // First enqueue this event.
        mQueue.push_back( event );

        // Then broadcast this event.
        // Either a listener or a poller should exist to do dequeueing.
        mBroadcaster.Broadcast( Void() );
    }
}

/*****************************************************************************
 * Peek next event without dequeueing it.
 * @return False if no more events (or disabled).
 *****************************************************************************/
template<class DeviceEvent>
bool
Device<DeviceEvent>::PeekQueue( DeviceEvent& event ) const
{
    // NOP if queue is disabled.
    if ( IfEnabled() and (mQueue.size() > 0) )
    {
        // Return the event (cannot dequeue nor vent events at this point).
        event = mQueue.front();
        return true;
    }
    else
    {
        return false;
    }
}

/*****************************************************************************
 * Dequeue next event.
 * @return False if no more events (or disabled).
 *****************************************************************************/
template<class DeviceEvent>
bool
Device<DeviceEvent>::Dequeue( DeviceEvent& event )
{
    // NOP if queue is disabled.
    if ( IfEnabled() and PeekQueue( event ) )
    {
        mQueue.pop_front();  // dequeue it

        // Now is an opportunity to vent overflowing events (after returning this event).
        VentQueue();

        return true;
    }
    else
    {
        return false;
    }
}

/*****************************************************************************
 * @return Amount of events in a queue.
 *****************************************************************************/
template<class DeviceEvent>
uint
Device<DeviceEvent>::QueueSize( void ) const
{
    return mQueue.size();
}

/*****************************************************************************
 * Set the maximum amount of events that can be queued at one time.
 * 0 : disable overflowing
 *****************************************************************************/
template<class DeviceEvent>
void
Device<DeviceEvent>::SetQueueOverflow( const uint overflow )
{
    mQueueOverflow = overflow;
}

/*****************************************************************************
 * Begin dropping events if queue is overflowing.
 * The joystick is more prone to causing this problem than the keyboard.
 *****************************************************************************/
template<class DeviceEvent>
void
Device<DeviceEvent>::VentQueue( void )
{
    // 0 disables venting.
    // Check if enabled else Dequeue() ad infinitum.
    if ( IfEnabled() and (mQueueOverflow > 0) )
    {
        // Every iteration repeatedly calls QueueCount() to recompute the count.
        DeviceEvent event;
        while ( QueueSize() > mQueueOverflow )
        {
            // Drop oldest event.
            Dequeue( event /*OUT*/ );
        }
    }
}

////////////////////////////////////////////////////////////////////////////////
/////////////////////////  Device : event listening  ///////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * Register functor to be called when an event is enqueued.
 *****************************************************************************/
template<class DeviceEvent>
void
Device<DeviceEvent>::RegisterListener( shptr<ListenerFunctor> listener )
{
    mBroadcaster.Listen( listener );
}

/*****************************************************************************
 * Unregister listener.
 *****************************************************************************/
template<class DeviceEvent>
void
Device<DeviceEvent>::UnregisterListener( shptr<ListenerFunctor> listener )
{
    mBroadcaster.Unlisten( listener );
}

} // namespace input

#endif // INPUT_DEVICE_HH
