/******************************************************************************

    Defines the Signal/Slot library providing inter-object communication.

    Classes:
        SIGSLOT_Signal
        SIGSLOT_Connection

    $Revision: 2$
    $Date: 2007-07-27 15:28:50$
    $NoKeywords$

*******************************************************************************

  NOTES AND EXAMPLE CODE FOR CONSUMERS OF SIGSLOT

  - Any class implementing slots must satisfy the following requirements:
      * contains a SIGSLOT_SlotManager instance.
      * provides a method GetSlotManager() returning a reference to that instance.
      * calls DisconnectAll() on its SIGSLOT_SlotManager instance in the destructor.
  - Copy construction is not supported for any of the SigSlot classes.

  The following code implements a class providing a signal.

    function RadiationDetector( )
    {
        this.m_radiationDetectedSignal = new SIGSLOT_Signal( );
    }
    RadiationDetector.prototype =
    {
        GetRadiationDetectedSignal : function( )
        {
            this.m_radiationDetectedSignal;
        }
    };


  The following code defines a class that permits signal connections.

    function Car( )
    {
        this.m_slotManager = new SIGSLOT_SlotManager( );
    }
    Car.prototype =
    {
        GetSlotManager : function( )
        {
            return this.m_slotManager;
        },

        destructor : function( )
        {
            this.m_slotManager.DisconnectAll( );
        },

        OnNewEnergyAvailable : function( )
        {
            this.Drive( );
        },

        Drive : function( )
        {
            //...
        }
    };

  To connect a signal to a slot, use one of the following forms, passing in a
  destination class object or dereferenced pointer to one, and the address of
  the slot function:

    radiationDetector.GetRadiationDetectedSignal( ).Connect( volvo, "OnNewEnergyAvailable" );

  Notes:
    * Multiple connections can be built between the same signal and slot.

  To fire a signal, use one of the following functionally equivalent forms:

    radiationDetector.GetRadiationDetectedSignal( ).Emit( );

  Notes:
    * Firing an unconnected signal is not an error; it will simply do nothing.

  To break all of a signal's connections:

    radiationDetector.GetRadiationDetectedSignal( ).DisconnectAll( );

  To disconnect a slot object from all signals connected to any of its slots:

    volvo.GetSlotManager( ).DisconnectAll( );

*******************************************************************************/


/******************************************************************************
  SIGSLOT_Signal

    Represents a signal.

******************************************************************************/
function SIGSLOT_Signal( )
{
    try
    {
        var counter = 0;
        this.GetNextConnectionId = function( )
        {
            return counter++;
        };

        this.m_slotConnections = { };
    }
    catch ( e )
    {
        EX_ASSERT_NO_EXCEPTIONS( e, "SIGSLOT_Signal::SIGSLOT_Signal( )" );
    }
}
SIGSLOT_Signal.prototype =
{

    /******************************************************************************
    PUBLIC METHODS
    ******************************************************************************/

    /******************************************************************************
      Connect

        Wires up this signal to the passed in slot within the passed in slot
        object. Creates a new connection object using the passed in arguments,
        adds it to the end of the connections list, and tells the passed in slot
        object's slot manager to add this signal to its senders map.

        Parameters:
            [in/out] slotOwner    - object owning slot.
            [in]     slotCallBack - slot implemented on slotOwner

    ******************************************************************************/
    Connect : function( slotOwner, slotCallBack )
    {
        try
        {
            var connection = new SIGSLOT_Connection( this, this.GetNextConnectionId( ), slotOwner, slotCallBack );

            this.m_slotConnections[ connection.m_signalId ] = connection;
        }
        catch ( e )
        {
            EX_ASSERT_NO_EXCEPTIONS( e, "SIGSLOT_Signal::Connect( )" );
        }
    },

    /******************************************************************************
      Emit

        Walks the connections list, firing signals to connected slots by invoking
        SIGSLOT_Connection.Emit( ).

        Parameters:
            Variable list - all passed to the connected slots.

    ******************************************************************************/
    Emit : function( )
    {
        try
        {
            for ( var slotIndex in this.m_slotConnections )
            {
                var connection = this.m_slotConnections[ slotIndex ];

                connection.Emit.apply( connection, arguments );
            }
        }
        catch ( e )
        {
            EX_ASSERT_NO_EXCEPTIONS( e, "SIGSLOT_Signal::Emit( )" );
        }
    },

    /******************************************************************************
      DisconnectAll

        Disconnects all connections to the signal.

    ******************************************************************************/
    DisconnectAll : function( )
    {
        try
        {
            for ( var connectionIndex in this.m_slotConnections )
            {
                var connection = this.m_slotConnections[ connectionIndex ];

                connection.SignalDisconnect( );
            }

            this.m_slotConnections = { };
        }
        catch ( e )
        {
            EX_ASSERT_NO_EXCEPTIONS( e, "SIGSLOT_Signal::DisconnectAll( )" );
        }
    },


    /******************************************************************************
    PRIVATE METHODS
    ******************************************************************************/

    /******************************************************************************
      SlotDisconnect

        Removes from the signal's connections list all connections that are
        associated with the slot owner object of the disconnecting connection.

        Parameters:
            [in]  disconnectingConnection

    ******************************************************************************/
    SlotDisconnect : function( disconnectingConnection )
    {
        try
        {
            for ( var connectionIndex in this.m_slotConnections )
            {
                var connection = this.m_slotConnections[ connectionIndex ];

                if ( connection.m_slotOwner === disconnectingConnection.m_slotOwner )
                {
                    delete this.m_slotConnections[ connection.m_signalId ];
                }
            }
        }
        catch ( e )
        {
            EX_ASSERT_NO_EXCEPTIONS( e, "SIGSLOT_Signal::SlotDisconnect( )" );
        }
    }
};


/******************************************************************************
  SIGSLOT_SlotManager

    Manages slots on an object containing slots.

******************************************************************************/
function SIGSLOT_SlotManager( )
{
    try
    {
        var counter = 0;
        this.GetNextConnectionId = function( )
        {
            return counter++;
        };

        this.m_signalConnections = { };
    }
    catch ( e )
    {
        EX_ASSERT_NO_EXCEPTIONS( e, "SIGSLOT_SlotManager::SIGSLOT_SlotManager( )" );
    }
}
SIGSLOT_SlotManager.prototype =
{

    /******************************************************************************
    PUBLIC METHODS
    ******************************************************************************/

    /******************************************************************************
      DisconnectAll

        Disconnects all signals connected to all slots.

    ******************************************************************************/
    DisconnectAll : function( )
    {
        try
        {
            for ( var connectionIndex in this.m_signalConnections )
            {
                var signalConnection = this.m_signalConnections[ connectionIndex ];

                signalConnection.SlotDisconnect( );
            }

            this.m_signalConnections = { };
        }
        catch ( e )
        {
            EX_ASSERT_NO_EXCEPTIONS( e, "SIGSLOT_SlotManager::SignalDisconnect( )" );
        }
    },


    /******************************************************************************
    PRIVATE METHODS
    ******************************************************************************/

    /******************************************************************************
      SignalConnect

        Adds a signal connection to the collection of connected signals.

        Parameters:
            [in]  signalConnection

        Returns:
            Signal connection id

    ******************************************************************************/
    SignalConnect : function( signalConnection )
    {
        try
        {
            var signalConnectionId = this.GetNextConnectionId( );

            this.m_signalConnections[ signalConnectionId ] = signalConnection;

            return signalConnectionId;
        }
        catch ( e )
        {
            EX_ASSERT_NO_EXCEPTIONS( e, "SIGSLOT_SlotManager::SignalConnect( )" );
        }
    },

    /******************************************************************************
      SignalDisconnect

        Removes all connections associated with the signal of the disconnecting
        connection.

        Parameters:
            [in]  disconnectingConnection

    ******************************************************************************/
    SignalDisconnect : function( disconnectingConnection )
    {
        try
        {
            for ( var connectionIndex in this.m_signalConnections )
            {
                var connection = this.m_signalConnections[ connectionIndex ];

                if ( connection.m_signal === disconnectingConnection.m_signal )
                {
                    delete this.m_signalConnections[ connection.m_slotId ];
                }
            }
        }
        catch ( e )
        {
            EX_ASSERT_NO_EXCEPTIONS( e, "SIGSLOT_SlotManager::SignalDisconnect( )" );
        }
    }
};


/******************************************************************************
  SIGSLOT_Connection

    Internal SigSlot class that encapsulates a connection between a signal and
    slot allowing communication between the two.

    Parameters:
        [in] signal
        [in] signalId
        [in] slotOwner
        [in] slotCallBack

******************************************************************************/
function SIGSLOT_Connection( signal, signalId, slotOwner, slotCallBack )
{
    try
    {
        this.m_signal       = signal;
        this.m_signalId     = signalId;
        this.m_slotOwner    = slotOwner;
        this.m_slotCallBack = slotCallBack;

        this.m_slotId = slotOwner.GetSlotManager( ).SignalConnect( this );
    }
    catch ( e )
    {
        EX_ASSERT_NO_EXCEPTIONS( e, "SIGSLOT_Connection::SIGSLOT_Connection( )" );
    }
}
SIGSLOT_Connection.prototype=
{

    /******************************************************************************
    PUBLIC METHODS
    ******************************************************************************/

    /******************************************************************************
      Emit

        Invokes slot call back.

        Parameters:
            Variable list - all passed to the connected slot.

    ******************************************************************************/
    Emit : function( )
    {
        try
        {
            var slot = this.m_slotOwner[ this.m_slotCallBack ];
            slot.apply( this.m_slotOwner, arguments );
        }
        catch ( e )
        {
            EX_ASSERT_NO_EXCEPTIONS( e, "SIGSLOT_Connection::Emit( )" );
        }
    },

    /******************************************************************************
      SignalDisconnect

        Removes connection from connected slot.

    ******************************************************************************/
    SignalDisconnect : function( )
    {
        try
        {
            this.m_slotOwner.GetSlotManager( ).SignalDisconnect( this );
        }
        catch ( e )
        {
            EX_ASSERT_NO_EXCEPTIONS( e, "SIGSLOT_Connection::SignalDisconnect( )" );
        }
    },

    /******************************************************************************
      SlotDisconnect

        Removes connection from connected signal.

    ******************************************************************************/
    SlotDisconnect : function( )
    {
        try
        {
            this.m_signal.SlotDisconnect( this );
        }
        catch ( e )
        {
            EX_ASSERT_NO_EXCEPTIONS( e, "SIGSLOT_Connection::SlotDisconnect( )" );
        }
    }
};