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

#ifndef GAME_NETWORK_ADDRESS_H
#define GAME_NETWORK_ADDRESS_H

//=====================================================================================================================
//=====================================================================================================================

namespace Game
{	
	//=================================================================================================================
	/// @brief		Class representing an IPV4 address and port.
	//=================================================================================================================

	class NetworkAddress
	{
		public:
			
			//=========================================================================================================
			// Functions
			//=========================================================================================================
		
				//=====================================================================================================
				/// @brief		Creates an address bound to the given host and port
				/// @param		host - name of the host to retrieve address for, or the ip address in dotted decimal
				///				format if known or a host name that is resolved using DNS
				/// @param		port - port to use
				/// @pre		Given host name or dotted decimal address must not be null
				//=====================================================================================================
				
				inline NetworkAddress( const char * host = "" , unsigned short port = 0xffff )
				{
					// Make sure ok:
					
					ENSURE( host , "Given host name or dotted decimal address must not be null" )
					
					// Clear to zero:
					
					memset(&m_address,0,sizeof(sockaddr_in));
					
					// Use internet addressing with IPV4 addresses
					
					m_address.sin_family = AF_INET;
				
					// Initialize to an invalid address and port
				
					SetHost(host); SetPort(port);
				}
				
				//=====================================================================================================
				/// @brief		Creates an address from an unsigned integer representing the ip and from a given 
				///				port number.
				/// @param		ip - ip of the address in integer format. First part of dotted decimal address is the 
				///				low order byte.
				/// @param		port - port to use
				//=====================================================================================================
				
				inline NetworkAddress( unsigned ip , unsigned short port = 10000 )
				{
					// Clear to zero:
					
					memset(&m_address,0,sizeof(sockaddr_in));				
				
					// Use internet addressing with IPV4 addresses
					
					m_address.sin_family = AF_INET;				
				
					// Initialize to an invalid address and port
				
					SetIP(ip); SetPort(port);
				}
				
				//=====================================================================================================
				/// @brief		Creates an address from a given set of bytes representing the ip and from a given 
				///				port number.
				/// @param		b1 - 1st part of the address xxx.?.?.?
				/// @param		b2 - 2nd part of the address ?.xxx.?.?
				/// @param		b3 - 3rd part of the address ?.?.xxx.?
				/// @param		b4 - 4th part of the address ?.?.?.xxx				
				/// @param		port - port to use
				/// @remarks	The xxx's indicate the part of the address each byte represents
				//=====================================================================================================
				
				inline NetworkAddress
				( 
					unsigned char b1 , 
					unsigned char b2 , 
					unsigned char b3 , 
					unsigned char b4 , 
					unsigned short port = 10000 
				)
				{	
					// Clear to zero:
					
					memset(&m_address,0,sizeof(sockaddr_in));				
				
					// Use internet addressing with IPV4 addresses
					
					m_address.sin_family = AF_INET;				
					
					// Initialize to an invalid address and port
				
					SetIP(b1,b2,b3,b4); SetPort(port);
				}						
				
				//=====================================================================================================
				/// @brief		Copy constructor. Makes this network address from another.
				/// @param		other - other object to assign from
				//=====================================================================================================
				
				inline NetworkAddress( const NetworkAddress & other )
				{
					// Copy the data:
					
					memcpy( &m_address , &other.m_address , sizeof(sockaddr_in) );
				}
				
				//=====================================================================================================
				/// @brief		Assignment operator. Assigns the other address to this one
				/// @param		other - other object to assign from
				/// @returns	This network address
				//=====================================================================================================
				
				inline NetworkAddress & operator = ( const NetworkAddress & other )
				{
					// Only if we are not the same:
					
					if ( &other != this )
					{				
						// Copy the data:
						
						memcpy( &m_address , &other.m_address , sizeof(sockaddr_in) );
					}
					
					// Return this address:
					
					return *this;
				}				
				
				//=====================================================================================================
				/// @brief		Tells if address is valid. 
				/// @remarks	An address of 255.255.255.255 or a port of 65535 is considered invalid
				//=====================================================================================================
				
				inline bool IsValid() const
				{ 
					// See if invalid address or port:
					
					if ( m_address.sin_addr.S_un.S_addr == 0xffffffff || m_address.sin_port == 0xffff )
					{
						// Invalid address:
						
						return false;
					}
					else
					{
						// Valid address 
						
						return true;
					}
				}
				
				//=====================================================================================================
				/// @brief		Compares two IP addresses for equality. 
				/// @param		other - other IP address to compare to.
				/// @returns	True if both IPs are considered equal
				/// @remarks	Even though their ports or IPs may differ, if two IP addresses are invalid they 
				///				considered to be equal by this function. This simplifies error checking and comparison
				///				with NetworkAddres::Invalid() 
				//=====================================================================================================
				
				inline bool operator == ( const NetworkAddress & other ) const
				{
					// See if we are invalid:
					
					if ( IsValid() == false )
					{
						// We are invalid: see if the other ip is invalid
						
						if ( other.IsValid() == false )
						{
							// Equality:
							
							return true;
						}
						else
						{
							// Not the same:
							
							return false;
						}
					}
					else
					{
						// We are not invalid: compare port and ip
						
						if ( ( GetPort() == other.GetPort() ) && ( GetIP() == other.GetIP() ) )
						{
							// Equality:
							
							return true;	
						}
						else
						{
							// Not the same:
							
							return false;
						}
					}
				}
				
				//=====================================================================================================
				/// @brief		Compares two IP addresses for inequality. 
				/// @param		other - other IP address to compare to.
				/// @returns	True if both IPs are considered not equal
				/// @remarks	Even though their ports or IPs may differ, if two IP addresses are invalid they 
				///				considered to be equal by this function. This simplifies error checking and comparison
				///				with NetworkAddres::Invalid() 
				//=====================================================================================================
				
				inline bool operator != ( const NetworkAddress & other ) const
				{
					// Do comparison:
					
					return ! ( (*this) == other );
				}
				
				//=====================================================================================================
				/// @brief		Gets the port of this ip address
				/// @returns	The port used by this ip address
				//=====================================================================================================
				
				inline unsigned short GetPort() const { return ntohs(m_address.sin_port); }
				
				//=====================================================================================================
				/// @brief		Sets the port used by this ip address
				/// @param		port - port to use
				//=====================================================================================================
				
				inline void SetPort( unsigned short port )
				{ 
					m_address.sin_port = htons(port);
				}
				
				//=====================================================================================================
				/// @brief		Sets the ip address of this network address as one unsigned integer.
				/// @param		address - ip address as one unsigned integer
				/// @remarks	High byte represents the first number in the dotted decimal notation. Low byte 
				///				represents the last number in the dotted decimal notation.
				//=====================================================================================================
				
				inline void SetIP( unsigned address )
				{				
					m_address.sin_addr.S_un.S_addr = htonl(address);
				}
				
				//=====================================================================================================
				/// @brief		Sets the ip address of this network address four bytes
				/// @param		b1 - 1st part of the address xxx.?.?.?
				/// @param		b2 - 2nd part of the address ?.xxx.?.?
				/// @param		b3 - 3rd part of the address ?.?.xxx.?
				/// @param		b4 - 4th part of the address ?.?.?.xxx
				/// @remarks	The xxx's indicate the part of the address each byte represents
				//=====================================================================================================
				
				inline void SetIP
				( 
					unsigned char b1 ,
					unsigned char b2 ,
					unsigned char b3 ,
					unsigned char b4 
				)
				{				
					// Makeup the integer for the entire address:
					
					unsigned i = b4;
					
					i |= b3 << 8;
					i |= b2 << 16;
					i |= b1 << 24;
					
					// Now set the ip:
					
					SetIP(i);
				}				
				
				//=====================================================================================================
				/// @brief		Sets the ip address based on a dotted decimal address string
				/// @param		address_string - a dotted decimal address string representing the ip address.
				/// @pre		The given address string must not be null
				//=====================================================================================================
				
				inline void SetIP( const char * address_string )
				{				
					// Make sure ok:
					
					ENSURE( address_string , "The given address string must not be null" )
					
					// If the string is zero length then use address 0xffffffff:
					
					if ( address_string[0] )
					{				
						// Convert to an integer:
						
						unsigned ip = (unsigned)( ntohl(inet_addr(address_string)) );
						
						// Now set the ip:
						
						SetIP(ip);
					}
					else
					{
						SetIP(0xffffffff);
					}
				}
				
				//=====================================================================================================
				/// @brief		Sets the ip address based on a dotted decimal address string
				/// @param		address_string - a dotted decimal address string representing the ip address.
				/// @pre		The given address string must not be null
				//=====================================================================================================
				
				inline void SetIP( const std::string & address_string )
				{				
					// Call the const char * version
					
					SetIP(address_string.c_str());
				}	
				
				//=====================================================================================================
				/// @brief		Sets the host name or dotted decimal ip for this network address. This sets the 
				///				ip address internally.
				/// @param		host_name - name or dotted decimal address of the host
				//=====================================================================================================
				
				inline void SetHost( const char * host_name ){ SetIP( GetHostIP(host_name) ); }
				
				//=====================================================================================================
				/// @brief		Sets the host for this address. This can either be an ip address in dotted decimal
				///				format or a host name. This in turn sets the IP address for this network address.
				/// @param		host_name - name or dotted decimal ip address of the host
				/// @remarks	If the host name cannot be resolved, an invalid IP address of 255.255.255.255 is used
				//=====================================================================================================
				
				inline void SetHost( const std::string & host_name ){ SetHost(host_name.c_str()); }
				
				//=====================================================================================================
				/// @brief		Attempts to get the name of the host attached to this ip address.
				/// @returns	The name of the host attached to this ip, or a blank string on failure
				/// @remarks	Host name lookup can be very slow (and blocks), so use sparingly
				//=====================================================================================================
				
				inline std::string GetHost(){ return GetHostName( GetIP() ); }
				
				//=====================================================================================================
				/// @brief		Gets the ip address of this network address as one unsigned integer.
				/// @returns	The ip address of this network address as one unsigned integer
				/// @remarks	High byte represents the first number in the dotted decimal notation. Low byte 
				///				represents the last number in the dotted decimal notation.
				//=====================================================================================================
				
				inline unsigned GetIP() const
				{				
					return ntohl(m_address.sin_addr.S_un.S_addr);
				}
				
				//=====================================================================================================
				/// @brief		Gets the ip address of this network address as a dotted decimal string.
				/// @returns	The ip address of this network address as a dotted decimal string.
				//=====================================================================================================
				
				inline std::string GetIPString() const 
				{
					// Make a buffer to hold the string:
					
					char buffer[512];
					
					// Print to the buffer:
					
					sprintf_s
					(
						buffer,
						511,
						"%u.%u.%u.%u",
						unsigned(GetIPElement(0)),
						unsigned(GetIPElement(1)),
						unsigned(GetIPElement(2)),
						unsigned(GetIPElement(3))
					);
					
					// Return the result:
					
					return std::string(buffer);
				}	
				
				//=====================================================================================================
				/// @brief		Gets one byte element in the ip address.
				/// @param		i - index of the ip address number to get. element 0 is the first part of the dotted
				///				decimal number for instance.
				/// @returns	The given part of the ip address
				/// @pre		The index 'i' must be between 0 and 3 inclusive.
				//=====================================================================================================
				
				inline unsigned char GetIPElement( int i ) const
				{				
					// Make sure ok:
					
					ENSURE( i >= 0 && i <= 3 , "The index 'i' must be between 0 and 3 inclusive." )
					
					// Get the net address as an unsigned:
					
					unsigned ip = GetIP();
					
					// Return the specified byte:
					
					return ( ip >> ( ( 3 - i ) * 8 ) ) & 0xff;
				}
				
				//=====================================================================================================
				/// @brief		Sets one byte element in the ip address.
				/// @param		i - index of the ip address number to set. element 0 is the first part of the dotted
				///				decimal number for instance.
				/// @param		v - value to set this address element to
				/// @returns	The given part of the ip address
				/// @pre		The index 'i' must be between 0 and 3 inclusive.
				//=====================================================================================================
				
				inline void SetIPElement( int i , unsigned char v )
				{				
					// Make sure ok:
					
					ENSURE( i >= 0 && i <= 3 , "The index 'i' must be between 0 and 3 inclusive." )
					
					// Get the net address as an unsigned:
					
					unsigned ip = GetIP();
					
					// Clear the bits of the specified element:
					
					ip &= ( ( 0xff ) << ( ( 3 - i ) * 8 ) );
					
					// Now set the bits of the specified element
					
					ip |= ( ( v    ) << ( ( 3 - i ) * 8 ) );					
					
					// Re-set the ip address:
					
					SetIP(ip);
				}				
				
				//=====================================================================================================
				/// @brief		Returns the winsock address of this ip and port
				/// @returns	The winsock representation of this ip and port
				//=====================================================================================================
				
				inline const sockaddr & GetWinsockAddress() const { return (sockaddr&)(m_address); }
				
				/// Returns the ip address of the given host
				
				static unsigned GetHostIP( const char * host_name );
				
				/// attempts to return the name of the host attached to the given ip address
				
				static std::string GetHostName( unsigned ip );
				
				/// Returns an invalid network address:
				
				static NetworkAddress InvalidAddress(){ return NetworkAddress(0xffffff,0xffff); }
				
				/// Returns the localhost or loop back address on a given port

				static NetworkAddress LocalHostAddress( unsigned short port ){ return NetworkAddress(127,0,0,1,port); }

		private:

			//=========================================================================================================
			// Variables
			//=========================================================================================================
			
				/// The actual winsock address of the IP address
				
				sockaddr_in m_address;
	};
};

#endif
