/*
	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_NET_OBJECT_H
#define GAME_NET_OBJECT_H

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

#include "Base/ObjectBase.h"
#include "Game/GameSession.h"
#include "Game/GamePacket.h"
#include "Game/GamePeer.h"

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

namespace Game
{
	//=================================================================================================================
	/// @brief		Interface class for all game objects that can be sent over the network.
	//=================================================================================================================

	class NetObject : public ObjectBase
	{
		public:

			//=========================================================================================================
			// Constants
			//=========================================================================================================
			
				/// If an object has this NET ID then it is invalid and we cannot identify the object across the network
				
				static const unsigned NET_ID_INVALID = 0xffffffff;
				
				//'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
				/// If an object has this net ID then it is local to the client/server and is not involved in network 
				/// transmission. These are for objects that generally do not affect the gameplay of the game.
				/// This might be used for example to create explosion objects on the client.
				//'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
				
				static const unsigned NET_ID_NONE = 0;
				
				/// If an object has this owner ID then it is invalid
				
				static const unsigned OWNER_ID_INVALID = 0xffffffff;
				
				/// If an object has this owner ID then nothing owns it
				
				static const unsigned OWNER_ID_NONE = 0;
				
			//=========================================================================================================
			// Enums
			//=========================================================================================================
			
				/// Enum representing the context under which we are receiving a packet
				
				enum ReceiveContext
				{
					/// We are on the server and are receiving data from the client object
					
					RECV_CONTEXT_CLIENT_TO_SERVER = 0 , 
					
					/// We are a client and are receiving the result of our move from the server
					
					RECV_CONTEXT_SERVER_TO_CLIENT = 1 ,
					
					/// We are a client and are receiving data for an object we do not own from the server
					
					RECV_CONTEXT_REMOTE_TO_CLIENT = 2
				};
				
				/// Enum representing the context under which we are sending a packet
				
				enum SendContext
				{
					/// We are a locally controlled object on the client and need to send our move to the server
					
					SEND_CONTEXT_CLIENT_TO_SERVER = 0 ,
					
					/// We are a server and are sending the result of a move to the client
					
					SEND_CONTEXT_SERVER_TO_CLIENT = 1 ,
					
					/// We are a server and are sending data to a client for an object which it does not control
					
					SEND_CONTEXT_REMOTE_TO_CLIENT = 2
				};
		
			//=========================================================================================================
			// Functions
			//=========================================================================================================
			
				//=====================================================================================================
				/// @brief		Creates the net object.
				/// @param		owner_id - id of the client that owns the object. If this is zero then no client owns
				///				the object.
				/// @param		net_id - this is the network id of the object
				/// @remarks	If the object is on the server then it will assign itself a net ID automatically.
				/// @remarks	If a net ID of NET_ID_NONE is given on the server then the object will not be assigned
				///				a net ID, meaning it is a non networked object.
				/// @pre		If we are not on the server then we must be given a valid net id.
				/// @pre		We must have a valid owner id, or no owner
				/// @pre		If the object has no net id (not involved in network transmission) then it must have 
				///				an owner id of OWNER_ID_NONE
				//=====================================================================================================
				
				inline NetObject( unsigned owner_id = OWNER_ID_INVALID , unsigned net_id = NET_ID_INVALID )
				{ 
					// Make sure ok:
					
					#ifdef DEBUG
					
						if ( IsClient() )
						{
							// Make sure ok:
						
							ENSURE( net_id != NET_ID_INVALID , "If we are not on the server then we must be given a valid net id." )
						}
						
						// This can never be true:
						
						ENSURE( owner_id != OWNER_ID_INVALID , "We must have a valid owner id, or no owner" )
						
						// Make sure ok
						
						if ( net_id == NET_ID_NONE )
						{
							ENSURE
							( 
								owner_id == OWNER_ID_NONE , 
								"If the object has no net id (not involved in network transmission) then it must have an owner id of OWNER_ID_NONE"
							)
						}
					
					#endif
				
					// Save:
				
					m_owner_id = owner_id;
					
					// See if we are on the server:
					
					if ( IsServer() )
					{
						// Only if we did not specify that the object is non-networked:
						
						if ( net_id == NET_ID_NONE )
						{
							// Not a networked object
							
							m_net_id = NET_ID_NONE;
						}
						else
						{
							// Assign the net id automatically:
							
							m_net_id = s_next_net_id;
							
							// Increment the next net-id:
							
							s_next_net_id++;												
						}												
					}
					else
					{
						// Client: given a net id
					
						m_net_id = net_id;	
					}
					
					// Initially in scope
					
					m_has_scope = true;						
				}
				
				//=====================================================================================================
				/// @brief		Returns the owner id for this object. This is the id of the peer that owns this object.
				/// @pre		We must be still connected. This means nothing when we are disconnected.
				//=====================================================================================================
				
				inline unsigned GetOwnerPeerId() const
				{ 
					// Make sure ok:
					
					ENSURE( IsConnected() , "We must be still connected." )				
				
					// Return it
				
					return m_owner_id; 
				}
				
				//=====================================================================================================
				/// @brief		Tells if this object is a locally controlled object when we are on the client.
				/// @returns	True if this is a locally controlled object.
				/// @remarks	This always returns true for non-networked objects.
				/// @pre		We must be still connected. This means nothing when we are disconnected.
				/// @pre		This must not be called on the server. It has no meaning on the server.
				/// @pre		This must not be called for non-networked objects. It has no meaning then.
				//=====================================================================================================
				
				inline bool IsLocalObject() const
				{ 								
					// Make sure ok:
					
					ENSURE( IsConnected() , "We must be still connected." )
					
					// Make sure ok:
					
					ENSURE( IsServer() == false , "This must not be called on the server. It has no meaning on the server." )
					
					// Make sure ok:
					
					ENSURE( IsNetworkedObject() , "This must not be called for non-networked objects. It has no meaning then." )					
				
					// See if we are on the server:
					
					bool server = GameSession::GetSingleton().IsServer();
					
					// See if we are on the server:
					
					if ( server ) return true; else return GameSession::GetSingleton().GetPeerID() == m_owner_id;
				}
				
				//=====================================================================================================
				/// @brief		Tells if the object is controlled by another peer or client. 
				/// @returns	True if this object is controlled by another peer or client.
				/// @pre		We must be still connected. This means nothing when we are disconnected.
				/// @pre		This must not be called on the server. It has no meaning on the server.
				/// @pre		This must not be called for non-networked objects. It has no meaning then.
				//=====================================================================================================
				
				inline bool IsRemoteObject() const
				{				
					// Make sure ok:
					
					ENSURE( IsNetworkedObject() , "This must not be called for non-networked objects. It has no meaning then." )
				
					// Make sure ok:
					
					ENSURE( IsConnected() , "We must be still connected. This means nothing when we are disconnected." )
					
					// Make sure ok:
					
					ENSURE( IsServer() == false , "This must not be called on the server. It has no meaning on the server." )
					
					// Return the answer
									
					return ! IsLocalObject();
				}
				
				//=====================================================================================================
				/// @brief		Convience function for telling if we are on the server. Calls the GameSession class.
				/// @returns	True if we are on the server
				/// @pre		We must be still connected. This means nothing when we are disconnected.
				//=====================================================================================================
				
				static inline bool IsServer()
				{ 
					// Make sure ok:
					
					ENSURE( IsConnected() , "We must be still connected. This means nothing when we are disconnected." )				
				
					// Return the answer
				
					return GameSession::GetSingleton().IsServer(); 					
				}

				//=====================================================================================================
				/// @brief		Convience function for telling if we are on the client. Calls the GameSession class.
				/// @returns	True if we are on the client
				/// @pre		We must be still connected. This means nothing when we are disconnected.
				//=====================================================================================================
				
				static inline bool IsClient()
				{ 
					// Make sure ok:
					
					ENSURE( GameSession::GetSingleton().IsConnected() , "We must be still connected. This means nothing when we are disconnected." )
				
					// Return the answer
					
					return GameSession::GetSingleton().IsClient(); 
				}
				
				//=====================================================================================================
				/// @brief		Convience function for telling if we are currently connected to a game. 
				///				Calls the GameSession class.
				/// @returns	True if we are connected.
				//=====================================================================================================
				
				static inline bool IsConnected()
				{ 
					// Return the answer
					
					return GameSession::GetSingleton().IsConnected();
				}
				
				//=====================================================================================================
				/// @brief		Convience function for telling if we are currently disconnected from a game. 
				///				Calls the GameSession class.
				/// @returns	True if we are disconnected.
				//=====================================================================================================
				
				static inline bool IsDisconnected()
				{ 
					// Return the answer
					
					return ! GameSession::GetSingleton().IsConnected(); 
				}
				
				//=====================================================================================================
				/// @brief		Tells if a client owns this object.
				/// @remarks	This will never return true for non-networked objects.
				/// @returns	True if a client owns this object.
				/// @pre		We must be still connected. This means nothing when we are disconnected.
				/// @pre		This must not be called for non-networked objects. It has no meaning then.
				//=====================================================================================================
				
				inline bool IsOwned() const
				{ 				
					// Make sure ok:
					
					ENSURE( GameSession::GetSingleton().IsConnected() , "We must be still connected. This means nothing when we are disconnected." )
					
					// Make sure ok:
					
					ENSURE( IsNetworkedObject() , "This must not be called for non-networked objects. It has no meaning then." )					
				
					// Return the answer
				
					return m_owner_id != 0;
				}
				
				//=====================================================================================================
				/// @brief		Tells if no client owns this object.
				/// @returns	True if no client owns this object.
				/// @pre		We must be still connected. This means nothing when we are disconnected.
				/// @pre		This must not be called for non-networked objects. It has no meaning then.
				//=====================================================================================================
				
				inline bool IsUnowned() const
				{ 				
					// Make sure ok:
					
					ENSURE( GameSession::GetSingleton().IsConnected() , "We must be still connected. This means nothing when we are disconnected." )
					
					// Make sure ok:
					
					ENSURE( IsNetworkedObject() , "This must not be called for non-networked objects. It has no meaning then." )					
				
					// Return the answer
				
					return ! IsOwned();
				}
				
				//=====================================================================================================
				/// @brief		Tells if this object has scope.
				/// @returns	True if the object has scope, false otherwise.
				/// @pre		This function has no meaning on the server since the server knows all. 
				///				This function must not be called on the server.
				/// @pre		We must not be disconnected. This function has no meaning when we are disconnected.
				/// @pre		This function must not be called on non-networked objects. It has no meaning then.
				/// @post		If the object is controlled by this client, it should always have scope.
				//=====================================================================================================
				
				inline bool HasScope() const 
				{
					// Make sure ok:
					
					ENSURE( IsNetworkedObject() , "This function must not be called on non-networked objects. It has no meaning then." )				
				
					// Make sure ok:
					
					ENSURE
					( 
						IsClient() , 
						"This function has no meaning on the server since the server knows all."
						"This function must not be called on the server."
					)
					
					// Make sure ok:
					
					ENSURE( IsConnected() , "We must not be disconnected. This function has no meaning when we are disconnected." )
					
					// Make sure ok:
					
					#ifdef DEBUG
					
						if ( IsLocalObject() )
						{
							ENSURE( m_has_scope == true , "If the object is controlled by this client, it should always have scope." )
						}
					
					#endif
					
					// Return if we are in scope
					
					return m_has_scope;
				}
				
				//=====================================================================================================
				/// @brief		Sets the scope for this object.
				/// @param		in_scope - true if we are in scope.
				/// @pre		This function must not be called on the server. Everything is always in scope on the server.
				/// @pre		Changing of scope for locally controlled client objects is not allowed. Local client 
				///				objects must always be in scope.
				/// @pre		This function must not be called when we are disconnected. It has no meaning then.
				/// @pre		This function must not be called on non-networked objects. It has no meaning then.
				//=====================================================================================================
				
				inline void SetScope( bool in_scope ) 
				{
					// Make sure ok:
					
					ENSURE( IsNetworkedObject() , "This function must not be called on non-networked objects. It has no meaning then." )
				
					// Make sure ok:
					
					ENSURE
					(
						IsClient() , 
						"This function must not be called on the server. Everything is always in scope on the server." 
					)
					
					// Make sure ok:
					
					ENSURE( IsConnected() , "This function must not be called when we are disconnected. It has no meaning then." )
					
					// Make sure ok:
					
					ENSURE
					( 
						IsLocalObject() == false , 
						"Changing of scope for locally controlled client objects is not allowed. Local client "
						"objects must always be in scope."
					)
					
					// Save:
					
					m_has_scope = in_scope;
				}
				
				//=====================================================================================================
				/// @brief		Gets the net ID of the object. This is used by the server to identify objects on all 
				///				clients and all net IDs are assigned by the server.
				//=====================================================================================================
				
				inline unsigned GetNetId(){ return m_net_id; }
				
				//=====================================================================================================
				/// @brief		Server only function. Clears the next net to be used id back to zero.
				/// @pre		We must be disconnected to use this function
				//=====================================================================================================
				
				static inline void ResetNextNetID()
				{
					// Make sure ok
					
					ENSURE( IsConnected() == false , "We must be disconnected to use this function")
					
					// Do it:
					
					s_next_net_id = 1;
				}
				
				//=====================================================================================================
				/// @brief		If this is true then the object is involved in network transmission.
				///				If this is false then the object is local to this peer only.
				/// @returns	True if this object is involved in network transmission.
				/// @remarks	Non-networked objects can be objects such as explosions that are created on the client
				///				and which do not impact on the gameplay. The client simply creates these objects as
				///				graphical fuzz in reaction to a rocket being removed from the world.
				//=====================================================================================================
				
				inline bool IsNetworkedObject() const { return m_net_id != NET_ID_NONE; }
				
				//=====================================================================================================
				/// @brief		Network receive function. Called on each net object by the GameSession object as 
				///				required. The object MUST read it's data from this packet even if only to ignore it,
				///				since the reading of subsequent packets will depend on the correct read position in
				///				the given packet.
				/// @param		packet - packet we are receiving data from
				/// @param		context - context under which we are receiving the data.
				/// @returns	True if the data in the packet was successfully read
				//=====================================================================================================
				
				#pragma warning(disable:4100)
				
					inline virtual bool NetReceive( GamePacket & packet , ReceiveContext context ){ return false; }
					
				#pragma warning(default:4100)

				//=====================================================================================================
				/// @brief		Network send function. Called on each net object by the GameSession object as 
				///				required. 
				/// @param		packet - packet we are receiving data from
				/// @param		context - context under which we are receiving the data.
				/// @returns	True if we have written any data to the packet, false otherwise.
				/// @remarks	Objects that are owned must always send their data when requested. Only un-owned 
				///				objects like the map can refuse to send any data, since the map does not need to
				///				send anything.
				//=====================================================================================================
				
				#pragma warning(disable:4100)
				
					inline virtual bool NetSend( GamePacket & packet , SendContext context ){ return false; }
					
				#pragma warning(default:4100)

		private:
		
			//=========================================================================================================
			// Variables
			//=========================================================================================================
			
				/// This is the owner ID for the object. 0 is an unowned object. 
				
				unsigned m_owner_id;
				
				/// This is net ID assigned to the object. The server will pick these IDs
				
				unsigned m_net_id;
				
				/// If true then the object is in scope and is within this client's area of interest. Always true for servers.
				
				bool m_has_scope;
				
				/// This is the ID to assign to the next NetObject created
				
				static unsigned s_next_net_id;
	};
};

#endif
