/*
*   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.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;
*   char[] k = hello;
*   
*   p.set(i, k, j); //Set the data in the packet
*   
*   int a, b;
*   char[] 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();
    *   
    *   char[] str1 = "Hi";
    *   char[] str2 = "Hello";
    *   
    *   p.set(str1, str2);
    *   
    *   // Retrieve str1 from packet    
    *   char[] 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];
    }

    
    /**
    *   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, char[] and wchar[] 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, char[] and wchar[] 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 char[] data)
    {
        scope char[] temp = new char[sfPacket_GetDataSize(m_ptr)];
        sfPacket_ReadString(m_ptr, temp.ptr);
        size_t l = fromStringz(temp.ptr).length;
        data = new char[l];
        data[] = temp[0 .. l];
    }
    
    void internalGet(ref wchar[] data)
    {
        scope wchar[] temp = new wchar[sfPacket_GetDataSize(m_ptr)];
        sfPacket_ReadWideString(m_ptr, temp.ptr);
        size_t l = fromStringz(temp.ptr).length;
        data = new wchar[l];
        data[] = 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(char[] data)
    {
        sfPacket_WriteString(m_ptr, toStringz(data));
    }

    void internalSet(wchar[] data)
    {
        sfPacket_WriteWideString(m_ptr, toStringz(data));
    }

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

    extern (C)
    {
        typedef void* function() pf_sfPacket_Create;
    	typedef void function(void*) pf_sfPacket_Destroy;
    	typedef void function(void*, void*, size_t) pf_sfPacket_Append;
    	typedef void function(void*) pf_sfPacket_Clear;
    	typedef byte* function(void*) pf_sfPacket_GetData;
    	typedef uint function(void*) pf_sfPacket_GetDataSize;
    	typedef int function(void*) pf_sfPacket_EndOfPacket;
    	typedef int function(void*) pf_sfPacket_CanRead;
    	typedef byte function(void*) pf_sfPacket_ReadInt8;
    	typedef ubyte function(void*) pf_sfPacket_ReadUint8;
    	typedef short function(void*) pf_sfPacket_ReadInt16;
    	typedef ushort function(void*) pf_sfPacket_ReadUint16;
    	typedef int function(void*) pf_sfPacket_ReadInt32;
    	typedef uint function(void*) pf_sfPacket_ReadUint32;
    	typedef float function(void*) pf_sfPacket_ReadFloat;
    	typedef double function(void*) pf_sfPacket_ReadDouble;
    	typedef void function(void*, char*) pf_sfPacket_ReadString;
    	typedef void function(void*, wchar*) pf_sfPacket_ReadWideString;
    	typedef void function(void*, byte) pf_sfPacket_WriteInt8;
    	typedef void function(void*, ubyte) pf_sfPacket_WriteUint8;
    	typedef void function(void*, short) pf_sfPacket_WriteInt16;
    	typedef void function(void*, ushort) pf_sfPacket_WriteUint16;
    	typedef void function(void*, int) pf_sfPacket_WriteInt32;
    	typedef void function(void*, uint) pf_sfPacket_WriteUint32;
    	typedef void function(void*, float) pf_sfPacket_WriteFloat;
    	typedef void function(void*, double) pf_sfPacket_WriteDouble;
    	typedef void function(void*, char*) pf_sfPacket_WriteString;
    	typedef void function(void*, wchar*) 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()
    {
        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");
    }
}