/*
*   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.socketudp;

import dsfml.network.ipaddress;
import dsfml.network.packet;
import dsfml.network.socketstatus;

import dsfml.system.common;

/**
*   SocketUDP wraps a socket using UDP protocol to
*   send data fastly (but with less safety)
*/
class SocketUDP : DSFMLObject
{
    /**
    *   Default constructor
    */
    this()
	{
		super(sfSocketUDP_Create());
		m_intermediatePacket = new Packet();
	}

    override void dispose()
	{
		sfSocketUDP_Destroy(m_ptr);
	}

    /**
    *   Bind the socket to a specific port
    *
    *   Params: 
    *       port = Port to bind the socket to
    *
    *   Returns: 
    *       True if operation has been successful
    *
    */
    bool bind(ushort port)
	{
		m_port = port;
		return cast(bool)sfSocketUDP_Bind(m_ptr, port);
	}

    /**
    *   Unbind the socket from its previous port, if any
    *
    *   Returns: True if operation has been successful
    *
    */
    bool unbind()
	{
		m_port = 0;
		return cast(bool)sfSocketUDP_Unbind(m_ptr, m_port);
	}

    /**
    *   Send an array of bytes
    *
    *   Params: 
    *       data = bytes array to send
    *       address = Address of the computer to send the packet to
    *       port = Port to send the data to
    *
    *   Returns: 
    *       Status code
    *
    */
    SocketStatus send(byte[] data, IPAddress address, ushort port)
	{
		return cast(SocketStatus) sfSocketUDP_Send(m_ptr, data.ptr, data.length, address, port);
	}

    /**
    *   Receive an array of bytes.
    *   This function is blocking.
    *
    *   Params: 
    *       data = Pointer to a byte array to fill (make sure it is big enough)
    *       sizeReceived = Number of bytes received
    *       address = Address of the computer which sent the data
    *
    *   Returns: 
    *       Status code
    *
    *   Remarks:
    *       Assert if data is null or length == 0            
    *
    */
    SocketStatus receive(byte[] data, out size_t sizeReceived, out IPAddress address)
	{
		SocketStatus ret = sfSocketUDP_Receive(m_ptr, data.ptr, data.length, &sizeReceived, &address);
		return ret;
	}

    /**
    *   Send a packet of data
    *
    *   Params: 
    *       packetToSend = Packet to send
    *       address = Address of the computer to send the packet to
    *       port = Port to send the data to
    *
    *   Returns: 
    *       Status code
    *
    */
    SocketStatus send(Packet packetToSend, IPAddress address, ushort port)
	{
	   	byte[] dataArray = packetToSend.onSend();
	   	m_intermediatePacket.append(dataArray);
		SocketStatus stat = cast(SocketStatus)sfSocketUDP_SendPacket(m_ptr, m_intermediatePacket.getNativePointer, address, port);
		m_intermediatePacket.clear();
		return stat;
	}

    /**
    *   Receive a packet.
    *   This function is blocking.
    *
    *   Params: 
    *       packetToReceive = Packet to fill with received data
    *       address = Address of the computer which sent the packet
    *
    *   Returns: 
    *       Status code
    *
    */
    SocketStatus receive(Packet packetToReceive, out IPAddress address)
	{
		SocketStatus ret = sfSocketUDP_ReceivePacket(m_ptr, m_intermediatePacket.getNativePointer, &address);
		packetToReceive.onReceive(m_intermediatePacket.getData);
		m_intermediatePacket.clear();
		return ret;

	}

    /**
    *   Check if the socket is in a valid state ; this function
    *   can be called any time to check if the socket is OK
    *
    *   Returns: 
    *       True if the socket is valid
    *
    */
    bool isValid()
	{
		return cast(bool)sfSocketUDP_IsValid(m_ptr);
	}

    /**
    *   Get the port the socket is currently bound to
    *
    *   Returns: 
    *       Current port (0 means the socket is not bound)
    */
    ushort getPort()
	{
		return m_port;
	}

    ///
    bool opEquals(SocketUDP other)
	{
		return (other.getNativePointer == this.getNativePointer);
	}

package:
    this (void* ptr)
    {
        super(ptr);
        m_intermediatePacket = new Packet();
    }
   
private:
    Packet m_intermediatePacket;
	ushort m_port;
	
// External ====================================================================

    extern (C)
    {
        typedef void* function() pf_sfSocketUDP_Create;
    	typedef void function(void*) pf_sfSocketUDP_Destroy;
    	typedef int function(void*, ushort) pf_sfSocketUDP_Bind;
    	typedef int function(void*, ushort) pf_sfSocketUDP_Unbind;
    	typedef SocketStatus function(void*, byte*, size_t, IPAddress, ushort) pf_sfSocketUDP_Send;
    	typedef SocketStatus function(void*, byte*, size_t, size_t*, IPAddress*) pf_sfSocketUDP_Receive;
    	typedef SocketStatus function(void*, void*, IPAddress, ushort) pf_sfSocketUDP_SendPacket;
    	typedef SocketStatus function(void*, void*, IPAddress*) pf_sfSocketUDP_ReceivePacket;
    	typedef int function(void*) pf_sfSocketUDP_IsValid;
    
    	static pf_sfSocketUDP_Create sfSocketUDP_Create;
    	static pf_sfSocketUDP_Destroy sfSocketUDP_Destroy;
    	static pf_sfSocketUDP_Bind sfSocketUDP_Bind;
    	static pf_sfSocketUDP_Unbind sfSocketUDP_Unbind;
    	static pf_sfSocketUDP_Send sfSocketUDP_Send;
    	static pf_sfSocketUDP_Receive sfSocketUDP_Receive;
    	static pf_sfSocketUDP_SendPacket sfSocketUDP_SendPacket;
    	static pf_sfSocketUDP_ReceivePacket sfSocketUDP_ReceivePacket;
    	static pf_sfSocketUDP_IsValid sfSocketUDP_IsValid;
    }

    static this()
    {
        DllLoader dll = DllLoader.load("csfml-network");
        
        sfSocketUDP_Bind = cast(pf_sfSocketUDP_Bind)dll.getSymbol("sfSocketUDP_Bind");
        sfSocketUDP_Create = cast(pf_sfSocketUDP_Create)dll.getSymbol("sfSocketUDP_Create");
        sfSocketUDP_Destroy = cast(pf_sfSocketUDP_Destroy)dll.getSymbol("sfSocketUDP_Destroy");
        sfSocketUDP_IsValid = cast(pf_sfSocketUDP_IsValid)dll.getSymbol("sfSocketUDP_IsValid");
        sfSocketUDP_Receive = cast(pf_sfSocketUDP_Receive)dll.getSymbol("sfSocketUDP_Receive");
        sfSocketUDP_ReceivePacket = cast(pf_sfSocketUDP_ReceivePacket)dll.getSymbol("sfSocketUDP_ReceivePacket");
        sfSocketUDP_Send = cast(pf_sfSocketUDP_Send)dll.getSymbol("sfSocketUDP_Send");
        sfSocketUDP_SendPacket = cast(pf_sfSocketUDP_SendPacket)dll.getSymbol("sfSocketUDP_SendPacket");
        sfSocketUDP_Unbind = cast(pf_sfSocketUDP_Unbind)dll.getSymbol("sfSocketUDP_Unbind");
    }
}