/*
*   DSFML - SFML Library binding in D language.
*   Copyright (C) 2008 Julien Dagorn (sirjulio13@gmail.com)
*
*   This software is provided 'as-is', without any express or
*   implied warranty. In no event will the authors be held
*   liable for any damages arising from the use of this software.
*
*   Permission is granted to anyone to use this software for any purpose,
*   including commercial applications, and to alter it and redistribute
*   it freely, subject to the following restrictions:
*
*   1.  The origin of this software must not be misrepresented;
*       you must not claim that you wrote the original software.
*       If you use this software in a product, an acknowledgment
*       in the product documentation would be appreciated but
*       is not required.
*
*   2.  Altered source versions must be plainly marked as such,
*       and must not be misrepresented as being the original software.
*
*   3.  This notice may not be removed or altered from any
*       source distribution.
*/

module dsfml.network.selector;

import dsfml.network.sockettcp;
import dsfml.network.socketudp;

import dsfml.system.common;

/**
* Selector TCP allow reading from multiple sockets
* without blocking. It's a kind of multiplexer. Use SocketTCP or 
* SocketUDP aliases.
*/
class Selector(T) : DSFMLObject
{
    //Ensure type is correct
    static if (!is(T : SocketTCP) && !is(T : SocketUDP))
        static assert("Only SocketTCP and SocketUDP are valid for Selector.");
    
    
    /**
    *   Default constructor
    */        
    this()
    {
       super(sfSelector_Create());
    }
    
    override void dispose()
    {
        sfSelector_Destroy(m_ptr);
    }
    
    /**
    *   Add a socket to watch
    *   
    *   Params:
    *       socket = A tcp or udp socket            
    */        
    void add(T socket)
    {
        if (!(socket.getNativePointer in m_watchedSockets))
        {
            sfSelector_Add(m_ptr, socket.getNativePointer);
            m_watchedSockets[socket.getNativePointer] = socket;
            m_numSocketsWatched++;
        }
    }
    
    /**
    *   Remove a previously added socket
    *   
    *   Params:
    *       socket = A tcp or udp socket                 
    */        
    void remove(T socket)
    {
        if (socket.getNativePointer in m_watchedSockets)
        {
            sfSelector_Remove(m_ptr, socket.getNativePointer);
            m_watchedSockets.remove(socket.getNativePointer);
            m_numSocketsWatched--;
        }
    }
    
    /**
    *   Clear all sockets being watched
    */        
    void clear()
    {
        sfSelector_Clear(m_ptr);
        foreach(key; m_watchedSockets.keys)
            m_watchedSockets.remove(key);
        m_numSocketsWatched = 0;
    }
    
    /**
    *   Wait and collect sockets which are ready for reading.
    *   This functions will return either when at least one socket
    *   is ready, or when the given time is out
    *
    *   Params: 
    *       timeout = Maximum time to wait, in seconds (0 to disable timeout)
    *
    *   Returns: 
    *       Number of sockets ready
    */
    uint wait(float timeout = 0.f)
    {
        return sfSelector_Wait(m_ptr, timeout);
    }
    
    /**
    *   After a call to Wait(), get the Index-th socket which is
    *   ready for reading. The total number of sockets ready
    *   is the integer returned by the previous call to Wait()
    *
    *   Params:
    *       index = Index of the socket to get
    *
    *   Returns:
    *       The Index-th socket
    */
    T GetSocketsReady(uint index)
    {
        return m_watchedSockets[sfSelector_GetSocketReady(m_ptr, index)];
    }
    

private: 
    size_t m_numSocketsWatched;
    T[void*] m_watchedSockets;
    
// External ====================================================================
    extern (C)
    {
        typedef void* function() pf_sfSelector_Create;
    	typedef void function(void*) pf_sfSelector_Destroy;
    	typedef void function(void*, void*) pf_sfSelector_Add;
    	typedef void function(void*, void*) pf_sfSelector_Remove;
    	typedef void function(void*) pf_sfSelector_Clear;
    	typedef uint function(void*, float) pf_sfSelector_Wait;
    	typedef void* function(void*, uint) pf_sfSelector_GetSocketReady;

    	static pf_sfSelector_Create sfSelector_Create;
    	static pf_sfSelector_Destroy sfSelector_Destroy;
    	static pf_sfSelector_Add sfSelector_Add;
    	static pf_sfSelector_Remove sfSelector_Remove;
    	static pf_sfSelector_Clear sfSelector_Clear;
    	static pf_sfSelector_Wait sfSelector_Wait;
    	static pf_sfSelector_GetSocketReady sfSelector_GetSocketReady;
    }

    static this()
    {
        DllLoader dll = DllLoader.load("csfml-network");
        
        static if (is (T : SocketTCP))
        {
            char[] symbol = "sfSelectorTCP";
        }
        else static if (is (T : SocketUDP))
        {
            char[] symbol = "sfSelectorUDP";
        }
        
        sfSelector_Add = cast(pf_sfSelector_Add)dll.getSymbol(symbol ~ "_Add");
        sfSelector_Clear = cast(pf_sfSelector_Clear)dll.getSymbol(symbol ~ "_Clear");
        sfSelector_Create = cast(pf_sfSelector_Create)dll.getSymbol(symbol ~ "_Create");
        sfSelector_Destroy = cast(pf_sfSelector_Destroy)dll.getSymbol(symbol ~ "_Destroy");
        sfSelector_GetSocketReady = cast(pf_sfSelector_GetSocketReady)dll.getSymbol(symbol ~ "_GetSocketReady");
        sfSelector_Wait = cast(pf_sfSelector_Wait)dll.getSymbol(symbol ~ "_Wait");
        sfSelector_Remove = cast(pf_sfSelector_Remove)dll.getSymbol(symbol ~ "_Remove");
    }
}

/**
*   alias of selector for TCP or UDP Socket. 
*/
alias Selector!(SocketTCP) SelectorTCP;
/// ditto
alias Selector!(SocketUDP) SelectorUDP;