// Client / Server test of the ReplicaManager class.  See ReplicaManagerP2P for the peer to peer test.
// The ReplicaManager is a very lightweight and efficient system that provides object creation, destruction, and serialization updates such that
// You have all copies of all desired objects on all desired systems semi-automatically

#include "StringTable.h"
// Monster and player are the same, but monster implements the Replica virtual interfaces with a member object, while player derives from Replica
#include "Monster.h"
#include "Player.h"
#include "RakPeerInterface.h"
#include "RakNetworkFactory.h"
#include <stdio.h>
#include <conio.h>
#include <string.h>
#include "Rand.h" // RandomMT
#include "ReplicaMember.h"
#include "BitStream.h"
#include "PacketEnumerations.h"
#include "ReplicaEnums.h"
#include "Replica.h"
#include "ReplicaManager.h"

void ShowStatus(Monster *monster, Player *player);

bool isServer;
RakPeerInterface *rakPeer;

// ReplicaManager manages the object replication system
ReplicaManager replicaManager;
// My two game instances
Monster *monster;
Player *player;

#ifdef _COMPATIBILITY_1
#include "Compatibility1Includes.h" // Developers of a certain platform will know what to do here.
#elif defined(_WIN32)
#include <windows.h> // Sleep
#else
#include <unistd.h> // usleep
#include <cstdio>
#endif

// This is a required callback I registered to get called when a remote participant calls ReplicaManager::Register on a new object.
// It's equivalent to creating our local copy of an object when a new object is created on a remote system
ReplicaReturnResult ConstructionCB(RakNet::BitStream *inBitStream, RakNetTime timestamp, NetworkID networkID, PlayerID senderId, ReplicaManager *caller)
{
	char output[255];
	// Security - The server needs to pay attention to what objects the client asks to create and reject those that are invalid.
	// In this case the client isn't allowed to create anything
	if (isServer)
		return REPLICA_PROCESSING_DONE;

    // I encoded all the data in inBitStream in Monster::SendConstruction and Player::SendConstruction
	// I am using the string table to send strings, which is a way to send pre-known strings in a single byte.
	// I registered "Player" and "Monster" in main().
	// The stringTable system has the limitation that all systems must register all the same strings in the same order.
	// I could have also used stringCompressor, which would always work but is less efficient to use when we have known strings
	stringTable->DecodeString(output, 255, inBitStream);
	if (strcmp(output, "Player")==0)
	{
		// The system automatically prevents duplicate creations
		assert(player==0);

		player = new Player;
		// Calling SetNetworkID is important and necessary, because all calls other than construction rely on being able to lookup an object by its network ID
		// If we didn't do this, we would never get scope, serialize, or destruction calls because the ReplicaManager wouldn't be able to find this object
		// networkID was generated from ReplicaManager by calling GetNetworkID on the remote object and automatically including that value in this packet
		player->SetNetworkID(networkID);

		if (isServer==false)
		{
			// In order to get Replica::Receive packets the pointer must be registered.  Otherwise these calls are ignored as a security measure
			// Note: If you wanted to actually perform sends on this object to remote systems, call Construct with the isCopy parameter as true instead.
			// replicaManager.ReferencePointer(player);

			// If we want to update an object created remotely, we can call Construct with the last parameter isCopy as true.
			replicaManager.Construct(player, true, senderId, false);

			// Since SendConstruction is not called for copies and we were calling SetScope there, we need to call it here instead.
			replicaManager.SetScope(player, true, senderId, false);
		}
		
		printf("New player created\n");
	}
	else if (strcmp(output, "Monster")==0)
	{
		// The system automatically prevents duplicate creations
		assert(monster==0);

		monster = new Monster;
		// Calling SetNetworkID is important and necessary, because all calls other than construction rely on being able to lookup an object by its network ID
		// If we didn't do this, we would never get scope, serialize, or destruction calls because the ReplicaManager wouldn't be able to find this object
		// networkID was generated from ReplicaManager by calling GetNetworkID on the remote object and automatically including that value in this packet
		monster->replica->SetNetworkID(networkID);

		if (isServer==false)
		{
			// In order to get Replica::Receive packets the pointer must be registered.  Otherwise these calls are ignored as a security measure
			// Note: If you wanted to actually perform sends on this object to remote systems, call Construct with the isCopy parameter as true instead.
			//replicaManager.ReferencePointer(monster->replica);

			// If we want to update an object created remotely, we can call Construct with the last parameter isCopy as true.
			replicaManager.Construct(monster->replica, true, senderId, false);

			// Since SendConstruction is not called for copies and we were calling SetScope there, we need to call it here instead.
			replicaManager.SetScope(monster->replica, true, senderId, false);
		}		

		printf("New monster created\n");
	}
	else
	{
		// Unknown string
		assert(0);
	}
	return REPLICA_PROCESSING_DONE;
}

// This is an optional callback I registered to get called when all objects are sent to a new system
ReplicaReturnResult SendDownloadCompleteCB(RakNet::BitStream *outBitStream, RakNetTime currentTime, PlayerID senderId, ReplicaManager *caller)
{
	// I could write data to outBitStream if I wanted, and it would arrive in inBitStream in ReceiveDownloadCompleteCB
	return REPLICA_PROCESSING_DONE;
}

// This is an optional callback I registered to get called when I got all objects sent to me from a new system.
// This is called even if no objects were sent, so even though I only have the server sending objects here,
// the server will get it from the client and the client from the server.
ReplicaReturnResult ReceiveDownloadCompleteCB(RakNet::BitStream *inBitStream, PlayerID senderId, ReplicaManager *caller)
{
	if (isServer==false) // Server doesn't care about this message, although we get it anyway
		printf("Object downloads complete\n");
	return REPLICA_PROCESSING_DONE;
}

int main(void)
{
	char ch;
	char userInput[256];

	rakPeer = RakNetworkFactory::GetRakPeerInterface();

	// You have to attach ReplicaManager for it to work, as it is one of the RakNet plugins
	rakPeer->AttachPlugin(&replicaManager);

	// Anytime we get a new connection, call AddParticipant() on that connection
	replicaManager.SetAutoParticipateNewConnections(true);

	// Anytime we get a new participant, automatically call Construct() for them with all known objects
	replicaManager.SetAutoConstructToNewParticipants(true);

	// Note there is also replicaManager.SetDefaultScope(true); which I'm not using just for illustration
	// It eliminates the need to call replicaManager.SetScope(this, true, playerId); in Replica::SendConstruction.

	// Set the function that will get messages from Replica::SendConstruction
	replicaManager.SetReceiveConstructionCB(ConstructionCB);

	// Set the optional callbacks to send and receive download complete notifications
	replicaManager.SetDownloadCompleteCB(SendDownloadCompleteCB, ReceiveDownloadCompleteCB);

	// Here I use the string table class to efficiently send strings I know in advance.
	// The encoding is done in the monster and player SendConstruction functions
	// The decoding is done in the ConstructionCB callback
	// The stringTable class will also send strings that weren't registered but this just falls back
	// to the stringCompressor and wastes 1 extra bit on top of that
	stringTable->AddString("Player", false); // 2nd parameter of false means a static string so it's not necessary to copy it
	stringTable->AddString("Monster", false); // 2nd parameter of false means a static string so it's not necessary to copy it

	// Just start the client, or the server
	printf("Demonstration of ReplicaManager for client / server\n");
	printf("The replica manager provides a framework to make it easier to synchronize\n");
	printf("object creation, destruction, and member object updates\n");
	printf("Difficulty: Intermediate\n\n");
	printf("Run as (s)erver or (c)lient? ");
	gets(userInput);
	if (userInput[0]=='s' || userInput[0]=='S')
	{
		isServer=true;
		rakPeer->Initialize(8,60000,0,0);
		rakPeer->SetMaximumIncomingConnections(8);
		printf("Server started.\n");
	}
	else
	{
		isServer=false;
		rakPeer->Initialize(1,0,0,0);
		printf("Enter IP to connect to: ");
		gets(userInput);
		if (userInput[0]==0)
		{
			strcpy(userInput, "127.0.0.1");
			printf("%s\n", userInput);
		}
		if (!rakPeer->Connect(userInput, 60000, 0, 0))
		{
			printf("Connect call failed!\n");
			return 1;
		}
		printf("Connecting...\n");
	}

	printf("Commands:\n(Q)uit\n(Space) Show status\n(R)andomize health and position\n");
	if (isServer)
	{
		printf("Toggle (M)onster\nToggle (p)layer\n");
		printf("Toggle (S)cope of player\n");
	}

	Packet *p;

	// Main loop, go until user signals to quit
	while (1)
	{
		p = rakPeer->Receive();
		while (p)
		{
			if (p->data[0]==ID_DISCONNECTION_NOTIFICATION || p->data[0]==ID_CONNECTION_LOST)
			{
				if (isServer==false)
				{
					printf("Server connection lost.  Deleting objects\n");
					// Lets have the clients delete all their objects when they lose connection to the server
					if (monster)
					{
						delete monster; // Destructor of monster calls Dereplicate
					}
					if (player)
					{
						delete player; // Destructor of player calls Dereplicate
					}
				}
			}
			rakPeer->DeallocatePacket(p);
			p = rakPeer->Receive();
		}

		if (kbhit())
		{
			ch=getch();
			if (ch=='q' || ch=='Q')
			{
				printf("Quitting.\n");
				break;
			}
			else if (ch==' ')
				ShowStatus(monster, player);
			else if (ch=='r' || ch=='R')
			{
				// Randomize health and position of the player and monster
				if (player)
				{
					player->health=randomMT();
					player->position=randomMT();

					// This system requires that you tell the manager when object memory has changed, so it will queue up a serialize call
					replicaManager.SignalSerializeNeeded(player, UNASSIGNED_PLAYER_ID, true); // UNASSIGNED_PLAYER_ID, true means everybody
				}
				if (monster)
				{
					monster->health=randomMT();
					monster->position=randomMT();

					// This system requires that you tell the manager when object memory has changed, so it will queue up a serialize call
					replicaManager.SignalSerializeNeeded(monster->replica, UNASSIGNED_PLAYER_ID, true); // UNASSIGNED_PLAYER_ID, true means everybody
				}
				printf("Randomized player and monster health and position\n");
				ShowStatus(monster, player);
			}
			else if (isServer)
			{
				if (ch=='m' || ch=='M')
				{
					if (monster==0)
					{
						printf("Creating monster\n");
						monster = new Monster;
					}
					else
					{

						delete monster;
						printf("Deleted monster\n");
						monster=0;
					}
				}
				else if (ch=='p' || ch=='P')
				{
					if (player==0)
					{
						printf("Creating player\n");
						player = new Player;

					}
					else
					{
						delete player;
						printf("Deleted player\n");
						player=0;
					}
				}
				else if (ch=='s' || ch=='S')
				{
					if (player)
					{
						bool currentScope;
						currentScope=replicaManager.IsInScope(player, rakPeer->GetPlayerIDFromIndex(0));
						if (currentScope==false)
							printf("Setting scope for player to true for all remote systems.\n");
						else
							printf("Setting scope for player to false for all remote systems.\n");
						replicaManager.SetScope(player, !currentScope, UNASSIGNED_PLAYER_ID, true);
					}
					else
					{
						printf("No player to set scope for\n");
					}
				}
			}
		}
#ifdef _WIN32
		Sleep(30);
#else
		usleep(30 * 1000);
#endif
	}

	delete monster;
	delete player;
	RakNetworkFactory::DestroyRakPeerInterface(rakPeer);

	return 1;
}

void ShowStatus(Monster *monster, Player *player)
{
	printf("\nSTATUS:\n");
	if (monster)
		printf("Monster is at position %i with health %i\n", monster->position, monster->health);
	else
		printf("There is no monster\n");

	if (player)
		printf("Player is at position %i with health %i\n", player->position, player->health);
	else
		printf("There is no player\n");

	if (isServer)
	{
		bool monsterInScope, playerInScope;
		if (monster)
		{
			// Note that when using a member object, I pass that member object to the ReplicaManager functions
			monsterInScope=replicaManager.IsInScope(monster->replica, rakPeer->GetPlayerIDFromIndex(0));

			// If an object is in scope we will send memory updates for that object to that player.
			if (monsterInScope)
				printf("Monster in scope to system 0.\n");
			else
				printf("Monster NOT in scope to system 0.\nChanges to monster variables will not be sent to that system.\n");
		}
		if (player)
		{
			// For regular inheritance I don't have to do that.
			playerInScope=replicaManager.IsInScope(player, rakPeer->GetPlayerIDFromIndex(0));

			if (playerInScope)
				printf("Player in scope to system 0.\n");
			else
				printf("Player NOT in scope to system 0.\nChanges to player variables will not be sent to that system.\n");
		}
	}
	printf("\n");
}


syntax highlighted by Code2HTML, v. 0.9.1