/*
 *  Original work: Copyright (c) 2014, Oculus VR, Inc.
 *  All rights reserved.
 *
 *  This source code is licensed under the BSD-style license found in the
 *  RakNet License.txt file in the licenses directory of this source tree. An additional grant 
 *  of patent rights can be found in the RakNet Patents.txt file in the same directory.
 *
 *
 *  Modified work: Copyright (c) 2016-2017, SLikeSoft UG (haftungsbeschränkt)
 *
 *  This source code was modified by SLikeSoft. Modifications are licensed under the MIT-style
 *  license found in the license.txt file in the root directory of this source tree.
 */

#include <stdio.h>
#include <stdlib.h>
#include "Lobby2Client_Steam.h" // If Lobby2Client_Steam.h is included before SocketLayer.h, then it will use the steam send functions
#include "Lobby2Message_Steam.h"
#include "slikenet/time.h"
#include "slikenet/sleep.h"
#include "slikenet/types.h"
#include "slikenet/peerinterface.h"
#include "slikenet/GetTime.h"
#include "slikenet/MessageIdentifiers.h"
#include <windows.h>
#include <slikenet/Kbhit.h>
#include "slikenet/Gets.h"
#include "slikenet/FullyConnectedMesh2.h"
#pragma warning( push )
#pragma warning(disable:4127)	// conditional expression is constant (with Steamworks 1.23a)
#include "steam_api.h"
#pragma warning( pop )

using namespace SLNet;

Lobby2MessageFactory_Steam *messageFactory;
Lobby2Client_Steam *lobby2Client;
SLNet::RakPeerInterface *rakPeer;
SLNet::FullyConnectedMesh2 *fcm2;
uint64_t lastRoom;

void PrintCommands(void)
{
	printf("a. GetRoomList\n");
	printf("b. LeaveRoom\n");
	printf("c. CreateRoom\n");
	printf("d. JoinRoom\n");
	printf("e. RefreshRoom\n");
	printf("f. SendRoomChatMessage\n");
	printf("g. ListRoomMembers\n");
	printf("?. Help\n");
	printf("(Escape). Quit\n");
}

struct SteamResults : public SLNet::Lobby2Callbacks
{
	virtual void MessageResult(SLNet::Notification_Console_MemberJoinedRoom *message)
	{
		SLNet::Notification_Console_MemberJoinedRoom_Steam *msgSteam = (SLNet::Notification_Console_MemberJoinedRoom_Steam *) message;
		SLNet::RakString msg;
		msgSteam->DebugMsg(msg);
		printf("%s\n", msg.C_String());
		// Guy with the lower ID connects to the guy with the higher ID
		uint64_t mySteamId=SteamUser()->GetSteamID().ConvertToUint64();
		if (mySteamId < msgSteam->srcMemberId)
		{
			// Steam's NAT punch is implicit, so it takes a long time to connect. Give it extra time
			unsigned int sendConnectionAttemptCount=24;
			unsigned int timeBetweenSendConnectionAttemptsMS=500;
			SLNET_VERIFY(rakPeer->Connect(msgSteam->remoteSystem.ToString(false), msgSteam->remoteSystem.GetPort(), 0, 0, 0, 0, sendConnectionAttemptCount, timeBetweenSendConnectionAttemptsMS) == CONNECTION_ATTEMPT_STARTED);
		}
	}

	virtual void MessageResult(SLNet::Console_SearchRooms *message)
	{
		SLNet::Console_SearchRooms_Steam *msgSteam = (SLNet::Console_SearchRooms_Steam *) message;
		SLNet::RakString msg;
		msgSteam->DebugMsg(msg);
		printf("%s\n", msg.C_String());
		if (msgSteam->roomIds.GetSize()>0)
		{
			lastRoom=msgSteam->roomIds[0];
		}
	}

	virtual void ExecuteDefaultResult(SLNet::Lobby2Message *message)
	{
		SLNet::RakString out;
		message->DebugMsg(out);
		printf("%s\n", out.C_String());
	}
};

int main(int argc, char **argv)
{
	if (argc>1)
	{
		printf("Command arguments:\n");
		for (int i=1; i < argc; i++)
		{
			printf("%i. %s\n", i, argv[i]);
		}
	}

	SteamResults steamResults;
	rakPeer = SLNet::RakPeerInterface::GetInstance();
	fcm2 = SLNet::FullyConnectedMesh2::GetInstance();
	messageFactory = new Lobby2MessageFactory_Steam;
	lobby2Client = Lobby2Client_Steam::GetInstance();
	lobby2Client->AddCallbackInterface(&steamResults);
	lobby2Client->SetMessageFactory(messageFactory);
	SocketDescriptor sd(1234,0);
	rakPeer->Startup(32,&sd,1);
	rakPeer->SetMaximumIncomingConnections(32);
	rakPeer->AttachPlugin(fcm2);
	rakPeer->AttachPlugin(lobby2Client);
	// Connect manually in Notification_Console_MemberJoinedRoom
	fcm2->SetConnectOnNewRemoteConnection(false, "");
	SLNet::Lobby2Message* msg = messageFactory->Alloc(SLNet::L2MID_Client_Login);
	lobby2Client->SendMsg(msg);
	if (msg->resultCode!=L2RC_PROCESSING && msg->resultCode!=L2RC_SUCCESS)
	{
		printf("Steam must be running to play this game (SteamAPI_Init() failed).\n");
		printf("If this fails, steam_appid.txt was probably not in the working directory.\n");
		messageFactory->Dealloc(msg);
		return -1;
	}
	messageFactory->Dealloc(msg);
	
	PrintCommands();

	bool quit=false;
	char ch;
	while(!quit)
	{
		SLNet::Packet *packet;
		for (packet=rakPeer->Receive(); packet; rakPeer->DeallocatePacket(packet), packet=rakPeer->Receive())
		{
			switch (packet->data[0])
			{
			case ID_DISCONNECTION_NOTIFICATION:
				// Connection lost normally
				printf("ID_DISCONNECTION_NOTIFICATION\n");
				break;
			case ID_ALREADY_CONNECTED:
				// Connection lost normally
				printf("ID_ALREADY_CONNECTED\n");
				break;
			case ID_REMOTE_DISCONNECTION_NOTIFICATION: // Server telling the clients of another client disconnecting gracefully.  You can manually broadcast this in a peer to peer enviroment if you want.
				printf("ID_REMOTE_DISCONNECTION_NOTIFICATION\n");
				break;
			case ID_REMOTE_CONNECTION_LOST: // Server telling the clients of another client disconnecting forcefully.  You can manually broadcast this in a peer to peer enviroment if you want.
				printf("ID_REMOTE_CONNECTION_LOST\n");
				break;
			case ID_REMOTE_NEW_INCOMING_CONNECTION: // Server telling the clients of another client connecting.  You can manually broadcast this in a peer to peer enviroment if you want.
				printf("ID_REMOTE_NEW_INCOMING_CONNECTION\n");
				break;
			case ID_CONNECTION_BANNED: // Banned from this server
				printf("We are banned from this server.\n");
				break;			
			case ID_CONNECTION_ATTEMPT_FAILED:
				printf("Connection attempt failed\n");
				break;
			case ID_NO_FREE_INCOMING_CONNECTIONS:
				// Sorry, the server is full.  I don't do anything here but
				// A real app should tell the user
				printf("ID_NO_FREE_INCOMING_CONNECTIONS\n");
				break;
			case ID_INVALID_PASSWORD:
				printf("ID_INVALID_PASSWORD\n");
				break;

			case ID_CONNECTION_LOST:
				// Couldn't deliver a reliable packet - i.e. the other system was abnormally
				// terminated
				printf("ID_CONNECTION_LOST\n");
				break;

			case ID_CONNECTION_REQUEST_ACCEPTED:
				// This tells the client they have connected
				printf("ID_CONNECTION_REQUEST_ACCEPTED to %s with GUID %s\n", packet->systemAddress.ToString(), packet->guid.ToString());
				break;

			case ID_NEW_INCOMING_CONNECTION:
				printf("ID_NEW_INCOMING_CONNECTION\n");
				break;

			case ID_FCM2_NEW_HOST:
				{
					if (packet->systemAddress== SLNet::UNASSIGNED_SYSTEM_ADDRESS)
						printf("Got new host (ourselves)");
					else
						printf("Got new host %s, GUID=%s", packet->systemAddress.ToString(true), packet->guid.ToString());
					SLNet::BitStream bs(packet->data,packet->length,false);
					bs.IgnoreBytes(1);
					RakNetGUID oldHost;
					bs.Read(oldHost);
					// If the old host is different, then this message was due to losing connection to the host.
					if (oldHost!=packet->guid)
						printf(". Oldhost Guid=%s\n", oldHost.ToString());
					else
						printf("\n");
				}
				break;

			default:
				// It's a client, so just show the message
				printf("Unknown Message ID %i\n", packet->data[0]);
				break;
			}

		}
		if (_kbhit())
		{
			ch=(char)_getch();

			switch (ch)
			{
			case 'a':
				{

					SLNet::Lobby2Message* logoffMsg = messageFactory->Alloc(SLNet::L2MID_Console_SearchRooms);
					lobby2Client->SendMsg(logoffMsg);
					messageFactory->Dealloc(logoffMsg);
				}
				break;

			case 'b':
				{
					if (lobby2Client->GetRoomID()==0)
					{
						printf("Not in a room\n");
						break;
					}
					SLNet::Console_LeaveRoom_Steam* leaveroomMsg = (SLNet::Console_LeaveRoom_Steam*) messageFactory->Alloc(SLNet::L2MID_Console_LeaveRoom);
					leaveroomMsg->roomId=lobby2Client->GetRoomID();
					lobby2Client->SendMsg(leaveroomMsg);
					messageFactory->Dealloc(leaveroomMsg);
				}
				break;

			case 'c':
				{
					if (lobby2Client->GetRoomID()!=0)
					{
						printf("Already in a room\n");
						break;
					}

					SLNet::Console_CreateRoom_Steam* createroomMsg = (SLNet::Console_CreateRoom_Steam*) messageFactory->Alloc(SLNet::L2MID_Console_CreateRoom);
					// set the name of the lobby if it's ours
					char rgchLobbyName[256];
					createroomMsg->roomIsPublic=true;
					_snprintf( rgchLobbyName, sizeof( rgchLobbyName ), "%s's lobby", SteamFriends()->GetPersonaName() );
					createroomMsg->roomName=rgchLobbyName;
					createroomMsg->publicSlots=8;
					lobby2Client->SendMsg(createroomMsg);
					messageFactory->Dealloc(createroomMsg);

				}
				break;

			case 'd':
				{
					if (lobby2Client->GetRoomID()!=0)
					{
						printf("Already in a room\n");
						break;
					}

					SLNet::Console_JoinRoom_Steam* joinroomMsg = (SLNet::Console_JoinRoom_Steam*) messageFactory->Alloc(SLNet::L2MID_Console_JoinRoom);
					printf("Enter room id, or enter for %" PRINTF_64_BIT_MODIFIER "u: ", lastRoom);
					char str[256];
					Gets(str, sizeof(str));
					if (str[0]==0)
					{
						joinroomMsg->roomId=lastRoom;
					}
					else
					{
						joinroomMsg->roomId=_atoi64(str);
					}
					lobby2Client->SendMsg(joinroomMsg);
					messageFactory->Dealloc(joinroomMsg);
				}
				break;

			case 'e':
				{
					if (lobby2Client->GetRoomID()==0)
					{
						printf("Not in a room\n");
						break;
					}

					SLNet::Console_GetRoomDetails_Steam* roomdetailsMsg = (SLNet::Console_GetRoomDetails_Steam*) messageFactory->Alloc(SLNet::L2MID_Console_GetRoomDetails);
					roomdetailsMsg->roomId=lobby2Client->GetRoomID();
					lobby2Client->SendMsg(roomdetailsMsg);
					messageFactory->Dealloc(roomdetailsMsg);
				}
				break;

			case 'f':
				{
					if (lobby2Client->GetRoomID()==0)
					{
						printf("Not in a room\n");
						break;

					}
					SLNet::Console_SendRoomChatMessage_Steam* roomchatMsg = (SLNet::Console_SendRoomChatMessage_Steam*) messageFactory->Alloc(SLNet::L2MID_Console_SendRoomChatMessage);
					roomchatMsg->message="Test chat message.";
					roomchatMsg->roomId=lobby2Client->GetRoomID();
					lobby2Client->SendMsg(roomchatMsg);
					messageFactory->Dealloc(roomchatMsg);

				}
				break;

			case 'g':
				{
					DataStructures::OrderedList<uint64_t, uint64_t> roomMembers;
					lobby2Client->GetRoomMembers(roomMembers);
					for (unsigned int i=0; i < roomMembers.Size(); i++)
					{
						printf("%i. %s ID=%" PRINTF_64_BIT_MODIFIER "u\n", i+1, lobby2Client->GetRoomMemberName(roomMembers[i]), roomMembers[i]);
					}
				}
				break;


			case '?':
				{
					PrintCommands();

				}
				break;

			case 27:
				{
					quit=true;
				}
				break;
			}
		}

		RakSleep(30);
	}
	
	SLNet::Lobby2Message* logoffMsg = messageFactory->Alloc(SLNet::L2MID_Client_Logoff);
	lobby2Client->SendMsg(logoffMsg);
	messageFactory->Dealloc(logoffMsg);
	rakPeer->DetachPlugin(lobby2Client);
	rakPeer->DetachPlugin(fcm2);
	SLNet::RakPeerInterface::DestroyInstance(rakPeer);
	Lobby2Client_Steam::DestroyInstance(lobby2Client);
	SLNet::FullyConnectedMesh2::DestroyInstance(fcm2);

	return 1;
}
