/* 
*  This file is part of the Unity networking tutorial by M2H (http://www.M2H.nl)
*  The original author of this code is Mike Hergaarden, even though some small parts 
*  are copied from the Unity tutorials/manuals.
*  Feel free to use this code for your own projects, drop us a line if you made something exciting! 
*/
#pragma strict

public var gameName = "Example4";
public var serverPort =  35001;

public var hostData : HostData[];

private var natCapable : ConnectionTesterStatus = ConnectionTesterStatus.Undetermined;
public var filterNATHosts = false;
private var probingPublicIP = false;
private var doneTestingNAT = false;
private var timer : float = 0.0;

private var hideTest = false;
private var testMessage = "Undetermined NAT capabilities";

private var tryingToConnectPlayNow : boolean = false;
public var tryingToConnectPlayNowNumber : int = 0;

private var remotePort : int[] = new int[3];
private var remoteIP : String[] = new String[3];
public var connectionInfo : String = "";

public var lastMSConnectionAttemptForcedNat : boolean= false;
private var NAToptionWasSwitchedForTesting : boolean = false;
private var officialNATstatus : boolean = Network.useNat;
public var errorMessage : String = "";
private var lastPlayNowConnectionTime : float;

public var nowConnecting : boolean = false;

function Awake ()
{
	sortedHostList = new Array ();
	
	// Start connection test
	natCapable = Network.TestConnection();
		
	// What kind of IP does this machine have? TestConnection also indicates this in the
	// test results
	if (Network.HavePublicAddress()){
		Debug.Log("This machine has a public IP address");
	}else{
		Debug.Log("This machine has a private IP address");
	}	
	
	/* //If you dont want to use the Unity masterserver..
	Network.natFacilitatorIP = myMasterServerIP;
	Network.natFacilitatorPort = 11111;//Change this
	MasterServer.ipAddress = myMasterServerIP;
	MasterServer.port = 22222;//Change this
	Network.connectionTesterIP = myMasterServerIP;
	Network.connectionTesterPort = 33333;//Change this
	*/
	
	
}

function Start(){//must be in start because of coroutine
		
	yield WaitForSeconds(0.5);
	var tries : int=0;
	while(tries<=10){		
		if(hostData && hostData.length>0){
			//Waiting for hostData
		}else{
			FetchHostList(true);
		}
		yield WaitForSeconds(0.5);
		tries++;
	}
}


function OnFailedToConnectToMasterServer(info: NetworkConnectionError)
{
	//Yikes
}

function OnFailedToConnect(info: NetworkConnectionError)
{
	Debug.Log("FailedToConnect info:"+info);
	FailedConnRetry(info);		
}

function Connect(ip : String, port : int, usenat : boolean){
	// Enable NAT functionality based on what the hosts if configured to do
	Network.useNat = usenat;
	lastMSConnectionAttemptForcedNat = usenat;
	
	Debug.Log("Connecting to "+ip+":"+port+" NAT:"+usenat);
	Network.Connect(ip, port);		
	nowConnecting=true;	
}

//This second definition of Connect can handle the ip string[] passed by the masterserver
function Connect(ip : String[], port : int, usenat : boolean){
	// Enable NAT functionality based on what the hosts if configured to do
	Network.useNat = usenat;
	lastMSConnectionAttemptForcedNat = usenat;
	
	Debug.Log("Connecting to "+ip[0]+":"+port+" NAT:"+usenat);
	Network.Connect(ip, port);		
	nowConnecting=true;	
}

function StartHost(players : int, port : int){
	if(players<=1){
		players=1;
	}
	//Network.InitializeSecurity();
	Network.InitializeServer(players, port);
}

function OnConnectedToServer(){
	//Stop communication until in the game
	Network.isMessageQueueRunning = false;

	//Save these details so we can use it in the next scene
	PlayerPrefs.SetString("connectIP", Network.connections[0].ipAddress);
	PlayerPrefs.SetInt("connectPort", Network.connections[0].port);
}

function FailedConnRetry(info: NetworkConnectionError){
	if(info == NetworkConnectionError.InvalidPassword){
		mayRetry=false;
	}
	
	
	nowConnecting=false;
	
	//Quickplay
	if(tryingToConnectPlayNow){
		//Try again without NAT if we used NAT
		if(mayRetry && Network.useNat && lastMSConnectionAttemptForcedNat){
			Debug.Log("Failed connect 1A: retry without NAT");
		
			remotePort[0]=serverPort;//Fall back to default server port
			Connect(remoteIP, remotePort[0], false);
			lastPlayNowConnectionTime=Time.time;
		}else{
			//We didn't use NAT and failed
			Debug.Log("Failed connect 1B: Don't retry");
		
			//Reset NAT to org. value
			Network.useNat=officialNATstatus;
						
			//Connect to next playnow/quickplay host
			tryingToConnectPlayNowNumber++;
			tryingToConnectPlayNow=false;
		}
	}else{
		//Direct connect or via host list manually
		connectionInfo="Failed to connect!";
		
		if(mayRetry && Network.useNat && lastMSConnectionAttemptForcedNat){
			//Since the last connect forced NAT usage,
			// let's try again without NAT.		
			Network.useNat=false;
			Network.Connect(remoteIP, remotePort[0]);
			nowConnecting=true;
			lastPlayNowConnectionTime=Time.time;
			
		}else{
			Debug.Log("Failed 2b");
		
			if(info == NetworkConnectionError.InvalidPassword){
				errorMessage="Failed to connect: Wrong password supplied";
			}else if(info == NetworkConnectionError.TooManyConnectedPlayers){
				errorMessage="Failed to connect: Server is full";
			}else{
				errorMessage="Failed to connect";
			}
			
			//reset to default port
			remotePort[0]=serverPort;
			
			//Reset nat to tested value
			Network.useNat=officialNATstatus;
			
		}
	}	
}

public var CONNECT_TIMEOUT : float = 0.75;
public var CONNECT_NAT_TIMEOUT : float = 5.00;

//Our quickplay function: Go trough the gameslist and try to connect to all games
function PlayNow(timeStarted : float ){
	
		var i : int=0;
		
		for (var myElement in sortedHostList)
		{
			var element=hostData[myElement];
		
			// Do not try NAT enabled games if we cannot do NAT punchthrough
			// Do not try connecting to password protected games
			if ( !(filterNATHosts && element.useNat) && !element.passwordProtected  )
			{
				aHost=1;
								
				if(element.connectedPlayers<element.playerLimit)
				{					
					if(tryingToConnectPlayNow){
						var natText;
						if(Network.useNat){
							natText=" with option 1/2";
						}else{
							natText=" with option 2/2";
						}
						if((!Network.useNat && lastPlayNowConnectionTime+CONNECT_TIMEOUT<=Time.time) || (Network.useNat  && lastPlayNowConnectionTime+CONNECT_NAT_TIMEOUT<=Time.time)){
							Debug.Log("Interrupted by timer, NAT:"+Network.useNat);
							FailedConnRetry(NetworkConnectionError.ConnectionFailed);							
						}
						return "Trying to connect to host "+(tryingToConnectPlayNowNumber+1)+"/"+sortedHostList.length+" "+natText;	
					}		
					if(!tryingToConnectPlayNow && tryingToConnectPlayNowNumber<=i){
						Debug.Log("Trying to connect to game NR "+i+" & "+tryingToConnectPlayNowNumber);
						tryingToConnectPlayNow=true;
						tryingToConnectPlayNowNumber=i;
						
						// Enable NAT functionality based on what the hosts if configured to do
						lastMSConnectionAttemptForcedNat=element.useNat;
						Network.useNat = element.useNat;
						var connectPort : int=element.port;
						if (Network.useNat){
							print("Using Nat punchthrough to connect");
						}else{
							//connectPort=serverPort; //bad idea!
							print("Connecting directly to host");
						}
						Debug.Log("connecting to "+element.gameName+" "+element.ip+":"+connectPort);
						Network.Connect(element.ip, connectPort);	
						lastPlayNowConnectionTime=Time.time;
					}
					i++;		
				}
			}			
		}
		
		//If we reach this point then either we've parsed the whole list OR the list is still empty
		
		//Dont give up yet: Give MS 7 seconds to feed the list
		if(Time.time<timeStarted+7){
			FetchHostList(true);	
			return "Waiting for masterserver..."+Mathf.Ceil((timeStarted+7)-Time.time);	
		}
		
		if(!tryingToConnectPlayNow){
			return "failed";
		}
		
	
}


function Update() {
	// If network test is undetermined, keep running
	if (!doneTestingNAT) {
		TestConnection();
	}
}

function TestConnection() {
	// Start/Poll the connection test, report the results in a label and react to the results accordingly
	natCapable = Network.TestConnection();
	switch (natCapable) {
		case ConnectionTesterStatus.Error: 
			testMessage = "Problem determining NAT capabilities";
			doneTestingNAT = true;
			break;
			
		case ConnectionTesterStatus.Undetermined: 
			testMessage = "Undetermined NAT capabilities";
			doneTestingNAT = false;
			break;
			
		case ConnectionTesterStatus.PrivateIPNoNATPunchthrough: 
			testMessage = "Cannot do NAT punchthrough, filtering NAT enabled hosts for client connections, local LAN games only.";
			filterNATHosts = true;
			Network.useNat = true;
			doneTestingNAT = true;
			break;
			
		case ConnectionTesterStatus.PrivateIPHasNATPunchThrough:
			if (probingPublicIP)
				testMessage = "Non-connectable public IP address (port "+ serverPort +" blocked), NAT punchthrough can circumvent the firewall.";
			else
				testMessage = "NAT punchthrough capable. Enabling NAT punchthrough functionality.";
			// NAT functionality is enabled in case a server is started,
			// clients should enable this based on if the host requires it
			Network.useNat = true;
			doneTestingNAT = true;
			break;
			
		case ConnectionTesterStatus.PublicIPIsConnectable:
			testMessage = "Directly connectable public IP address.";
			Network.useNat = false;
			doneTestingNAT = true;
			break;
			
		// This case is a bit special as we now need to check if we can 
		// cicrumvent the blocking by using NAT punchthrough
		case ConnectionTesterStatus.PublicIPPortBlocked:
			testMessage = "Non-connectble public IP address (port " + serverPort +" blocked), running a server is impossible.";
			Network.useNat = false;
			// If no NAT punchthrough test has been performed on this public IP, force a test
			if (!probingPublicIP)
			{
				Debug.Log("Testing if firewall can be circumnvented");
				natCapable = Network.TestConnectionNAT();
				probingPublicIP = true;
				timer = Time.time + 10;
			}
			// NAT punchthrough test was performed but we still get blocked
			else if (Time.time > timer)
			{
				probingPublicIP = false; 		// reset
				Network.useNat = true;
				doneTestingNAT = true;
			}
			break;
		case ConnectionTesterStatus.PublicIPNoServerStarted:
			testMessage = "Public IP address but server not initialized, it must be started to check server accessibility. Restart connection test when ready.";
			break;
		default: 
			testMessage = "Error in test routine, got " + natCapable;
	}
	officialNATstatus=Network.useNat;
	if(doneTestingNAT){
		Debug.Log("TestConn:"+testMessage);
		Debug.Log("TestConn:"+natCapable + " " + probingPublicIP + " " + doneTestingNAT);
	}
}

private var lastHostListRequest : float = 0;

//Request the host limit, but we limit these requests
//Max once every two minutes automatically, max once every 5 seconds when manually requested
function FetchHostList(manual : boolean){
	var timeout : int = 120;
	if(manual){
		timeout=5;
	}
	
	if(lastHostListRequest==0 || Time.realtimeSinceStartup > lastHostListRequest + timeout){
			lastHostListRequest = Time.realtimeSinceStartup;
			MasterServer.RequestHostList (gameName);			
			yield WaitForSeconds(1);//We gotta wait :/			
			hostData = MasterServer.PollHostList();
			yield WaitForSeconds(1);//We gotta wait :/	
			CreateSortedArray();
			Debug.Log("Requested new host list, got: "+hostData.length);
	}
	
	
		
}

//The code below is all about sorting the game list by playeramount

public var sortedHostList : Array;

function CreateSortedArray(){
	
	sortedHostList = new Array();	
	
	var i : int=0;
	var data : HostData[] = hostData;
	for (var element in data)
	{
		AddToArray(i);
		i++;		
	}			
}

function AddToArray(nr : int){
	sortedHostList.Add (nr);	
	SortLastItem();
}

function SortLastItem(){
	if(sortedHostList.length<=1){
		return;
	}
	for(var i=sortedHostList.length-1;i>0;i--){
	var value1 : int= hostData[sortedHostList[i-1]].connectedPlayers;
	var value2 : int = hostData[sortedHostList[i]].connectedPlayers;
		if(value1<value2){
			SwapArrayItem((i-1), i);
		}else{
			//Sorted!
			return;
		}
	}
}

function SwapArrayItem(nr1, nr2){
	var tmp=sortedHostList[nr1];
	sortedHostList[nr1]=sortedHostList[nr2];
	sortedHostList[nr2]=tmp;
}