/*
	Lan Wars.
	Copyright (c) 2009, Darragh Coy.
	All rights reserved.
	Original work(s), source code & author(s) details available at: http://www.darraghcoy.com

	Redistribution and use in source and binary forms, with or without modification, are permitted 
	provided that the following conditions are met:

		* 	Redistributions of source code must retain the above copyright notice, this list of 
			conditions and the following disclaimer.
		* 	Redistributions in binary form must reproduce the above copyright notice, this list of 
			conditions and the following disclaimer in the documentation.
		* 	Neither the name of the author nor the names of contributors may be used to endorse or 
			promote products derived from this software without specific prior written permission.
		* 	The use of any third party libraries, modules or data files provided with the source 
			form of the program must follow and respect the individual licensing terms of these 
			components. You do not have permission to modify or circumvent the licensing terms for 
			provided third party components unless the third party licenses specifically allow this. 
			If you do not agree to the terms of provided third party components then you do not have 
			permission to use them in your derived work(s).
		* 	The terms of this license do not apply to separate (but related) distributions if supplied 
			by the author(s), only to the contents of this source distribution, derived works, and the 
			binaries produced by either modified or unmodified versions of the source code. The terms 
			of this license do not also apply to any third party libraries or components provided with 
			the source distribution. Such third party elements are licensed under their own separate 
			agreements.

	THIS SOFTWARE IS PROVIDED BY DARRAGH COY ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 
	BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
	ARE DISCLAIMED. IN NO EVENT SHALL DARRAGH COY BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
	EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 
	OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 
	OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 
	IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

//=================================================================================================================
/// @brief		Class representing a packet that can be sent by the game. Each packet is of fixed size with 32K
///				of data (32768 bytes) with 8 bytes reserved for a packet id and packet size, and packet number. 
///				When the packet is sent over the network however it may be made smaller depending on how much 
///				of the buffer space is filled up.
//=================================================================================================================

#include "Precompiled.h"
#include "Game\GamePacket.h"
#include "Network\DatagramSocket.h"
#include "Network\NetworkAddress.h"

using namespace Game;

//=====================================================================================================================
/// @brief		Writes the data of the packet to the given datagram socket
/// @param		socket - socket to write to
/// @returns	True if the data was written successfully, false if not
/// @pre		The given socket must not be null
/// @pre		The checksum for the packet must be valid
/// @remarks	This function will not write a packet to an invalid address
//=====================================================================================================================

bool GamePacket::WriteToSocket( DatagramSocket * socket ) const
{
	// Make sure ok:
	
	ENSURE( HasValidChecksum() , "The checksum for the packet must be valid" )

	// Make sure the destination address is valid:
	
	if ( m_address.IsValid() == false ) return false;

	// Make sure the socket is ok:
	
	if ( socket->IsValid() )
	{
		// Figure out how much data we will write
		
		unsigned write_size = sizeof(GamePacket::Data::Header) + m_data.m_header.m_size;
		
		// Do the write and save the number of bytes written:
		
		unsigned written = socket->Write( (const void*) &m_data , write_size , m_address );
		
		// See if the number of bytes written is ok:
		
		if ( written == write_size ) 
		{
			// Success 
			
			return true;
		}
		else
		{
			// Failure
			
			return false;
		}
	}
	else
	{
		// Failed to write
		
		return false;
	}
}

//=====================================================================================================================
/// @brief		Reads the data of the packet from the given datagram socket
/// @param		socket - socket to read from
/// @param		from - the address of who we received from is saved here
/// @returns	True if the data was read successfully, false if not
/// @pre		The given socket must not be null
/// @remarks	If the read attempt failed the packet size is set to invalid and the packet is given an the id
///				PKID_INVALID. The address of the sender is also set to an invalid address.
//=====================================================================================================================

bool GamePacket::ReadFromSocket( DatagramSocket * socket )
{
	// Make sure the socket is ok:
	
	if ( socket->IsValid() )
	{
		// Read in the packet from the socket: attempt to read the size of a full packet
		
		unsigned bytes_read = socket->Read( &m_data , sizeof(Data) , m_address );
		
		// See if we read at least the size of the id and size fields
		
		if ( bytes_read >= ( sizeof( unsigned __int16) * 2 ) )
		{
			// Ok: we read something. Make sure the size field is within range
			
			if ( m_data.m_header.m_size <= PAYLOAD_SIZE )
			{
				// Make sure the checksum for the packet is ok:
				
				if ( HasValidChecksum() )
				{
					// Make sure we read the correct amount of data:
					
					if ( bytes_read == sizeof(GamePacket::Data::Header) + GetByteSize() )
					{
						// Make sure it is from a valid address:
						
						if ( m_address.IsValid() )
						{
							// All ok: 
							
							return true;
						}		
					}	
				}
			}
		}
	}
	
	// Packet is corrupt:
	
	m_data.m_header.m_checksum		= 0xffff;
	m_data.m_header.m_size			= 0;
	m_data.m_header.m_packet_num	= 0xffffffff;
	m_address						= NetworkAddress::InvalidAddress();
	
	// Return false for failure
	
	return false;
}

//=====================================================================================================================
/// @brief		Computes the checksum and returns it
/// @returns	The computed 16 bit checksum
/// @pre		Size of the packet payload must not be marked as being greater than the maximum payload size
//=====================================================================================================================

unsigned __int16 GamePacket::ComputeChecksum() const
{
	// Make sure ok:
	
	ENSURE( m_data.m_header.m_size <= PAYLOAD_SIZE , "Size of the packet payload must not be marked as being greater than the maximum payload size" )

	// Store the checksum here:
	
	unsigned checksum = 0;
	
	// Get our current checksum
	
	unsigned __int16 old_checksum = GetChecksum();
	
	// Bend the rules a little: we need a non-const reference to us to clear the checksum so it wont affect to computation
	
	GamePacket & this_non_const = const_cast<GamePacket&>(*this);
	
	// We need to clear our checksum temoparily for this to work correctly: 
	
	this_non_const.SetChecksum(0);
	
	// This is the amount of data to go through in bytes:
	
	unsigned num_bytes = sizeof(GamePacket::Data::Header) + m_data.m_header.m_size;
	
	// Go through each byte in the packet data:
	
	unsigned char * byte = (unsigned char*)(&(this_non_const.m_data.m_header));
	
	// Do it:
	
	for ( unsigned i = 0 ; i < num_bytes ; ++i )
	{
		// See if this is an add or even byte: if odd then shift it up 8 bits
		
		if ( ( i & 1 ) == 1 )
		{
			checksum += ( * byte ) << 8;
		}
		else
		{
			checksum += ( * byte );
		}
		
		// Next byte:
		
		byte++;
	}
	
	// Restore the previous checksum:
	
	this_non_const.SetChecksum(old_checksum);
	
	// Return the checksum
	
	return (unsigned __int16)( checksum & 0xffff );
}

//=====================================================================================================================
/// @brief		Writes a specified amount of bits to the packet
/// @param		bits - the source of the data
/// @param		num_bits - the amount of bits to write
/// @returns	True if the write was successful
/// @pre		Some bits must be written
//=====================================================================================================================

bool GamePacket::WriteBits( const void * bits , unsigned num_bits )
{
	// Make sure ok:

	ENSURE( num_bits > 0 , "Some bits must be written" )

	// Make sure we would not overflow by getting our index in bytes aftewards:

	unsigned ib_after = ( m_current_bit + num_bits ) / 8;

	// If not on byte boundaries then we need to add 1 to the count:

	if ( (( m_current_bit + num_bits ) & 7 ) != 0 )
	{
		ib_after += 1;
	}

	// See if we have enough room left in the packet:

	if ( ib_after <= PAYLOAD_SIZE )
	{
		// We're ok: write bits one by one

		for ( unsigned i = 0 ; i < num_bits ; ++i )
		{
			// Get the address of the byte we're on in the payload

			volatile unsigned char * dst_byte = m_data.m_payload + ( m_current_bit / 8 );

			// Clear the current bit within the byte

			* dst_byte &= (unsigned char)( ~ ( 1 << ( m_current_bit & 7 ) ) );
			
			// Get the byte we are on in the source data:

			unsigned char src_byte = *( ((const unsigned char *)(bits)) + (i/8) );

			// See if the bit we are looking for is set:

			if ( ( src_byte & ( 1 << ( i & 7 ) ) ) != 0 )
			{
				// The bit is set in the source byte: set the bit in the destination

				* dst_byte |= (unsigned char)( 1 << ( m_current_bit & 7 ) );
			}

			// Move onto the next bit in the stream

			m_current_bit++;
		}

		// Calculate how many bits of the last byte were not written to:

		unsigned leftover_bits = ( 8 - ( m_current_bit & 7 ) ) & 7;

		// See if there were any:

		if ( leftover_bits > 0 )
		{
			// Get the address of the byte we're on in the payload

			volatile unsigned char * dst_byte = m_data.m_payload + ( m_current_bit / 8 );

			// Set the leftover bits to zero:

			for ( unsigned i = 0 ; i < leftover_bits ; ++i )
			{
				* dst_byte &= (unsigned char)( 0x7f >> i );
			}
		}

		// Update the size field of the packet:

		m_data.m_header.m_size = (unsigned __int16)(ib_after);

		// Success:

		return true;
	}
	else
	{
		// Failed, not enough bits in the packet:

		return false;
	}
}

//=====================================================================================================================
/// @brief		Reads a specified amount of bits from the packet:
/// @param		bits - where to save the bits to
/// @param		num_bits - the number of bits to read
/// @pre		Some bits must be read
//=====================================================================================================================

bool GamePacket::ReadBits( void * bits , unsigned num_bits )
{
	// Make sure ok:

	ENSURE( num_bits > 0 , "Some bits must be read" )

	// Make sure we would not overflow by getting our index in bytes aftewards:

	unsigned ib_after = ( m_current_bit + num_bits ) / 8;

	// If not on byte boundaries then we need to add 1 to the count:

	if ( (( m_current_bit + num_bits ) & 7 ) != 0 )
	{
		ib_after += 1;
	}

	// See if we have enough room left in the packet:

	if ( ib_after <= m_data.m_header.m_size )
	{
		// We're ok: read bits one by one

		for ( unsigned i = 0 ; i < num_bits ; ++i )
		{
			// Get the byte we are on in the payload

			unsigned char src_byte = * ( m_data.m_payload + ( m_current_bit / 8 ) );
			
			// Get the address of the byte in the destination

			unsigned char * dst_byte = ( (unsigned char*)(bits) ) + (i/8);

			// Clear the bit we are on in the destination byte:

			* dst_byte &= (unsigned char)( ~ ( 1 << ( i & 7 ) ) );

			// If the bit is set in the payload then re-set it in the destination:

			if ( ( src_byte & ( 1 << ( m_current_bit & 7 ) ) ) != 0 )
			{
				// The bit is set in the source byte: set the bit in the destination

				* dst_byte |= (unsigned char)( 1 << ( i & 7 ) );
			}

			// Move onto the next bit in the stream

			m_current_bit++;
		}		

		// Success:

		return true;
	}
	else
	{
		// Failed: return false for failure

		return false;
	}
}
