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

import dsfml.system.common;
import dsfml.system.stringutil;

/** 
*	Packet wraps data to send / to receive through the network
*	
*	The order of insertion and extraction must be the same.
*	
*	You can derive from Packet and override onSend and onReceive to do custom operations before send or after reception.	
*
*	Litterals integer are promoted to int.
*	Litterals floating point are promoted to float.
*	
*	Extraction or insertion can be specified with explicit template.
*	Examples:
*	------------------------------------------------------------
*	Packet p = new Packet();
*	
*	int i = 32, j = 42;
*	string k = hello;
*	
*	p.set(i, k, j); //Set the data in the packet
*	
*	int a, b;
*	string c;
*	p.get(a, c, b); //Get data from the packet
*	
*	//...
*	
*	Packet p = new Packet();
*	p.set!(byte)(5); // Litteral are inserted with byte type
*	------------------------------------------------------------
*	
*	See_Also:
*		$(LINK2 http://www.digitalmars.com/d/1.0/lex.html, D litterals Specification) for more informations.
*/
class Packet : DSFMLObject
{
	/**
	* Default constructor
	*
	*/
	this()
	{
		super(sfPacket_Create());
	}

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

	/**
	*	Append data to the end of the packet.
	*	
	*	Params: 
	*		data = Array of data to append
	*
	*/
	void append(byte[] data)
	{
		if (data !is null)
			sfPacket_Append(m_ptr, data.ptr, data.length);
	}

	/**
	*	Clear the packet data
	*
	*/
	void clear()
	{
		sfPacket_Clear(m_ptr);
	}

	/**
	*	Get an array to the data contained in the packet
	*	$(B the returned array may be invalid after you
	*	append data to the packet)
	*
	*	Returns: 
	*		array of data
	*		
	*	Remarks:
	*		return an array of $(B all) data in the packet.
	*		
	*	----------
	*	Packet p = new Packet();
	*	
	*	string str1 = "Hi";
	*	string str2 = "Hello";
	*	
	*	p.set(str1, str2);
	*	
	*	// Retrieve str1 from packet	
	*	string str3;	
	*	p.get(str3);
	*	
	*	// Returns an array containing str1 and str2.	
	*	byte[] ptr = p.getData();										 
	*	----------						 
	*/
	byte[] getData()
	{
		if (canRead)
			return sfPacket_GetData(m_ptr)[0..getDataSize()];
		else
			return null;
	}

	
	/**
	*	Get the size of the data contained in the packet
	*
	*	Returns: 
	*		Data size, in bytes
	*/
	uint getDataSize() 
	{
		return sfPacket_GetDataSize(m_ptr);
	}
	
	/**
	*	Tell if the reading position has reached the end of the packet
	*	
	*	Returns:
	*		true if all data have been read.			
	*/		
	bool endOfPacket()
	{
		return cast(bool)sfPacket_EndOfPacket(m_ptr);
	}
	/**
	*	Tell if the packet is valid for reading
	*
	*	Returns: 
	*		True if data can be extracted from the packet
	*
	*/
	bool canRead() 
	{
		return cast(bool)sfPacket_CanRead(m_ptr);
	}
		
	
	/**
	*	Add new variables to the packet
	*	Accept (u)byte, (u)short, (u)int, float, double, string and wstring types  
	*/		
	Packet set(T...)(T t)
	{
		foreach (v; t)
			internalSet(t);
		return this;
	}

	/**
	*	Retrieve data from the packet
	*	Accept (u)byte, (u)short, (u)int, float, double, string and wstring types	
	*/		
	Packet get(T...)(ref T t)
	{
		foreach (v; t)
			internalGet(t);
		return this;
	}

	/**
	*	Called before packet is send
	*	
	*	Params:
	*		size = Variable to fill with the size of the data to send
	*	Returns:
	*		Array of byte to send					
	*/		
	byte[] onSend()
	{
		return getData();
	}
	
	/**
	*	Called after a packet has been received
	*	
	*	Params:
	*		data = Array of byte received			
	*/		
	void onReceive(byte[] data)
	{
		append(data);
	}

private:
	void internalGet(ref bool data)
	{
		data = cast(bool)sfPacket_ReadInt32(m_ptr);
	}
	void internalGet(ref byte data)
	{
		data = sfPacket_ReadInt8(m_ptr);
	}
	void internalGet(ref ubyte data)
	{
		data = sfPacket_ReadUint8(m_ptr);
	}
	void internalGet(ref short data)
	{
		data = sfPacket_ReadInt16(m_ptr);
	}
	void internalGet(ref ushort data)
	{
		data = sfPacket_ReadUint16(m_ptr);
	}
	void internalGet(ref int data)
	{
		data = sfPacket_ReadInt32(m_ptr);
	}
	void internalGet(ref uint data)
	{
		data = sfPacket_ReadUint32(m_ptr);
	}
	void internalGet(ref float data)
	{
		data = sfPacket_ReadFloat(m_ptr);
	}
	void internalGet(ref double data)
	{
		data = sfPacket_ReadDouble(m_ptr);
	}
	void internalGet(ref string data)
	{
		scope char[] temp = new char[sfPacket_GetDataSize(m_ptr)];
		sfPacket_ReadString(m_ptr, temp.ptr);
		size_t l = fromStringz(temp.ptr).length;
		data = cast(string) temp[0 .. l];
	}
	
	void internalGet(ref wstring data)
	{
		scope wchar[] temp = new wchar[sfPacket_GetDataSize(m_ptr)];
		sfPacket_ReadWideString(m_ptr, temp.ptr);
		size_t l = fromStringz(temp.ptr).length;
		data = cast(wstring) temp[0 .. l];
	}
	
	void internalSet(bool data)
	{
		sfPacket_WriteInt32(m_ptr, cast(int)data);
	}
	void internalSet(byte data)
	{
		sfPacket_WriteInt8(m_ptr, data);
	}
	void internalSet(ubyte data)
	{
		sfPacket_WriteUint8(m_ptr, data);
	}
	void internalSet(short data)
	{
		sfPacket_WriteInt16(m_ptr, data);
	}
	void internalSet(ushort data)
	{
		sfPacket_WriteUint16(m_ptr, data);
	}
	void internalSet(int data)
	{
		sfPacket_WriteInt32(m_ptr, data);
	}
	void internalSet(uint data)
	{
		sfPacket_WriteUint32(m_ptr, data);
	}
	void internalSet(float data)
	{
		sfPacket_WriteFloat(m_ptr, data);
	}
	void internalSet(double data)
	{
		sfPacket_WriteDouble(m_ptr, data);
	}
	void internalSet(string data)
	{
		sfPacket_WriteString(m_ptr, toStringz(data));
	}

	void internalSet(wstring data)
	{
		sfPacket_WriteWideString(m_ptr, toStringz(data));
	}

// External ====================================================================

	extern (C)
	{
		typedef SFMLClass function() pf_sfPacket_Create;
		typedef void function(SFMLClass) pf_sfPacket_Destroy;
		typedef void function(SFMLClass, const(void)*, size_t) pf_sfPacket_Append;
		typedef void function(SFMLClass) pf_sfPacket_Clear;
		typedef byte* function(SFMLClass) pf_sfPacket_GetData;
		typedef uint function(SFMLClass) pf_sfPacket_GetDataSize;
		typedef int function(SFMLClass) pf_sfPacket_EndOfPacket;
		typedef int function(SFMLClass) pf_sfPacket_CanRead;
		typedef byte function(SFMLClass) pf_sfPacket_ReadInt8;
		typedef ubyte function(SFMLClass) pf_sfPacket_ReadUint8;
		typedef short function(SFMLClass) pf_sfPacket_ReadInt16;
		typedef ushort function(SFMLClass) pf_sfPacket_ReadUint16;
		typedef int function(SFMLClass) pf_sfPacket_ReadInt32;
		typedef uint function(SFMLClass) pf_sfPacket_ReadUint32;
		typedef float function(SFMLClass) pf_sfPacket_ReadFloat;
		typedef double function(SFMLClass) pf_sfPacket_ReadDouble;
		typedef void function(SFMLClass, char*) pf_sfPacket_ReadString;
		typedef void function(SFMLClass, wchar*) pf_sfPacket_ReadWideString;
		typedef void function(SFMLClass, byte) pf_sfPacket_WriteInt8;
		typedef void function(SFMLClass, ubyte) pf_sfPacket_WriteUint8;
		typedef void function(SFMLClass, short) pf_sfPacket_WriteInt16;
		typedef void function(SFMLClass, ushort) pf_sfPacket_WriteUint16;
		typedef void function(SFMLClass, int) pf_sfPacket_WriteInt32;
		typedef void function(SFMLClass, uint) pf_sfPacket_WriteUint32;
		typedef void function(SFMLClass, float) pf_sfPacket_WriteFloat;
		typedef void function(SFMLClass, double) pf_sfPacket_WriteDouble;
		typedef void function(SFMLClass, cchar*) pf_sfPacket_WriteString;
		typedef void function(SFMLClass, cwchar*) pf_sfPacket_WriteWideString;
	
		static pf_sfPacket_Create sfPacket_Create;
		static pf_sfPacket_Destroy sfPacket_Destroy;
		static pf_sfPacket_Append sfPacket_Append;
		static pf_sfPacket_Clear sfPacket_Clear;
		static pf_sfPacket_GetData sfPacket_GetData;
		static pf_sfPacket_GetDataSize sfPacket_GetDataSize;
		static pf_sfPacket_EndOfPacket sfPacket_EndOfPacket;
		static pf_sfPacket_CanRead sfPacket_CanRead;
		static pf_sfPacket_ReadInt8 sfPacket_ReadInt8;
		static pf_sfPacket_ReadUint8 sfPacket_ReadUint8;
		static pf_sfPacket_ReadInt16 sfPacket_ReadInt16;
		static pf_sfPacket_ReadUint16 sfPacket_ReadUint16;
		static pf_sfPacket_ReadInt32 sfPacket_ReadInt32;
		static pf_sfPacket_ReadUint32 sfPacket_ReadUint32;
		static pf_sfPacket_ReadFloat sfPacket_ReadFloat;
		static pf_sfPacket_ReadDouble sfPacket_ReadDouble;
		static pf_sfPacket_ReadString sfPacket_ReadString;
		static pf_sfPacket_ReadWideString sfPacket_ReadWideString;
		static pf_sfPacket_WriteInt8 sfPacket_WriteInt8;
		static pf_sfPacket_WriteUint8 sfPacket_WriteUint8;
		static pf_sfPacket_WriteInt16 sfPacket_WriteInt16;
		static pf_sfPacket_WriteUint16 sfPacket_WriteUint16;
		static pf_sfPacket_WriteInt32 sfPacket_WriteInt32;
		static pf_sfPacket_WriteUint32 sfPacket_WriteUint32;
		static pf_sfPacket_WriteFloat sfPacket_WriteFloat;
		static pf_sfPacket_WriteDouble sfPacket_WriteDouble;
		static pf_sfPacket_WriteString sfPacket_WriteString;
		static pf_sfPacket_WriteWideString sfPacket_WriteWideString;
	}

	static this()
	{
	debug
		DllLoader dll = DllLoader.load("csfml-network-d");
	else
		DllLoader dll = DllLoader.load("csfml-network");
		
		sfPacket_Append = cast(pf_sfPacket_Append)dll.getSymbol("sfPacket_Append");
		sfPacket_CanRead = cast(pf_sfPacket_CanRead)dll.getSymbol("sfPacket_CanRead");
		sfPacket_Clear = cast(pf_sfPacket_Clear)dll.getSymbol("sfPacket_Clear");
		sfPacket_Create = cast(pf_sfPacket_Create)dll.getSymbol("sfPacket_Create");
		sfPacket_Destroy = cast(pf_sfPacket_Destroy)dll.getSymbol("sfPacket_Destroy");
		sfPacket_EndOfPacket = cast(pf_sfPacket_EndOfPacket)dll.getSymbol("sfPacket_EndOfPacket");
		sfPacket_GetData = cast(pf_sfPacket_GetData)dll.getSymbol("sfPacket_GetData");
		sfPacket_GetDataSize = cast(pf_sfPacket_GetDataSize)dll.getSymbol("sfPacket_GetDataSize");
		sfPacket_ReadDouble = cast(pf_sfPacket_ReadDouble)dll.getSymbol("sfPacket_ReadDouble");
		sfPacket_ReadFloat = cast(pf_sfPacket_ReadFloat)dll.getSymbol("sfPacket_ReadFloat");
		sfPacket_ReadInt16 = cast(pf_sfPacket_ReadInt16)dll.getSymbol("sfPacket_ReadInt16");
		sfPacket_ReadInt32 = cast(pf_sfPacket_ReadInt32)dll.getSymbol("sfPacket_ReadInt32");
		sfPacket_ReadInt8 = cast(pf_sfPacket_ReadInt8)dll.getSymbol("sfPacket_ReadInt8");
		sfPacket_ReadString = cast(pf_sfPacket_ReadString)dll.getSymbol("sfPacket_ReadString");
		sfPacket_ReadWideString = cast(pf_sfPacket_ReadWideString)dll.getSymbol("sfPacket_ReadWideString");
		sfPacket_ReadUint16 = cast(pf_sfPacket_ReadUint16)dll.getSymbol("sfPacket_ReadUint16");
		sfPacket_ReadUint32 = cast(pf_sfPacket_ReadUint32)dll.getSymbol("sfPacket_ReadUint32");
		sfPacket_ReadUint8 = cast(pf_sfPacket_ReadUint8)dll.getSymbol("sfPacket_ReadUint8");
		sfPacket_WriteDouble = cast(pf_sfPacket_WriteDouble)dll.getSymbol("sfPacket_WriteDouble");
		sfPacket_WriteFloat = cast(pf_sfPacket_WriteFloat)dll.getSymbol("sfPacket_WriteFloat");
		sfPacket_WriteInt16 = cast(pf_sfPacket_WriteInt16)dll.getSymbol("sfPacket_WriteInt16");
		sfPacket_WriteInt32 = cast(pf_sfPacket_WriteInt32)dll.getSymbol("sfPacket_WriteInt32");
		sfPacket_WriteInt8 = cast(pf_sfPacket_WriteInt8)dll.getSymbol("sfPacket_WriteInt8");
		sfPacket_WriteString = cast(pf_sfPacket_WriteString)dll.getSymbol("sfPacket_WriteString");
		sfPacket_WriteWideString = cast(pf_sfPacket_WriteWideString)dll.getSymbol("sfPacket_WriteWideString");
		sfPacket_WriteUint16 = cast(pf_sfPacket_WriteUint16)dll.getSymbol("sfPacket_WriteUint16");
		sfPacket_WriteUint32 = cast(pf_sfPacket_WriteUint32)dll.getSymbol("sfPacket_WriteUint32");
		sfPacket_WriteUint8 = cast(pf_sfPacket_WriteUint8)dll.getSymbol("sfPacket_WriteUint8");
	}
}