/*
*	DSFML - SFML Library wrapper for the D programming language.
*	Copyright (C) 2008 Julien Dagorn (sirjulio13@gmail.com)
*	Copyright (C) 2010 Andreas Hollandt
*
*	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()
	{
	debug
		DllLoader dll = DllLoader.load("csfml-network-d");
	else
		DllLoader dll = DllLoader.load("csfml-network");
		
		static if (is (T : SocketTCP))
		{
			string symbol = "sfSelectorTCP";
		}
		else static if (is (T : SocketUDP))
		{
			string 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;