// (c) 2005 Electronic Arts Inc.
//

using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;

using EA.GameTalkMessages;

namespace EA.GameTalkViewer
{
	/// <summary>
	/// Summary description for TargetManager.
	/// </summary>
	public class TargetManager
	{

		public readonly static int DEFAULT_PORT = 9876;

		static readonly private TargetManager _Instance = new TargetManager();

		public const string DefaultGameXenonString = "Game.Xenon";
		public const string DefaultGamePcString = "Game.PC";
		public const string DefaultGameXenonStringLower = "game.xenon";
		public const string XenonName = "xenon";

		public readonly string[] TargetNames = new string[] {
																"Game.PS2",
																"Game.PS3",
																DefaultGamePcString,
																"Game.GameCube",
																"Game.Revolution",
																DefaultGameXenonString};

		private readonly string _KeyName = @"Software\Electronic Arts Inc.\GameTalkServer\";

		private Dictionary<string, Target> _Targets = new Dictionary<string, Target>();

		public Target[] Targets
		{
			get
			{
				Target[] targets = new Target[_Targets.Count];
				int index = 0;
				foreach (Target target in _Targets.Values)
				{
					targets[index++] = target;
				}
				return targets;
			}
		}

		public bool ContainsTarget(string targetName, string ipAddress, int portNum)
		{
			if (_Targets.ContainsKey(targetName)) // This prohibits the user from adding targets with the same name - actually I don't know if we really should prevent this
				return true;

			foreach (Target target in _Targets.Values)
			{
				if ((target.IpAddress == ipAddress) && (target.Port == portNum))
					return true;
			}

			return false;
		}

		static public TargetManager Instance
		{
			get { return _Instance; }
		}

		private TargetManager()
		{
		}

		~TargetManager()
		{
		}

		public void Initialize()
		{
			_Targets.Clear();

			Target[] registeredTargets = Configuration.Instance.GetRegisteredTargets(DEFAULT_PORT);
			foreach (Target target in registeredTargets)
			{
				bool updateTarget = false;
				try
				{
					string dynamicIpAddress = target.IpAddress;
					if (target.AutoResolveIp)
					{
						dynamicIpAddress = GetIpAddress(target.Name, target.DnsName, target.Platform);
						if ((target.IpAddress != dynamicIpAddress) && (dynamicIpAddress != null))
						{
							// Update the IP address if the IP address has changed (exp - DHCP gives out 
							// a different IP) since the last time it was recorded in the registry.
							target.IpAddress = dynamicIpAddress;
							updateTarget = true;
						}
					}

					if (target.Platform == string.Empty)
					{
						// In earlier versions of GameTalk the platform wasn't defined - we'll try to parse the name for the platform
						if (target.Name.StartsWith("game."))
						{
							string possiblePlatform = target.Name.Substring(5);
							target.Platform = possiblePlatform;
							updateTarget = true;
						}
					}

					if (updateTarget)
						Configuration.Instance.AddOrUpdateRegisteredTarget(target);

				}
				catch
				{
				}
				_Targets.Add(target.Name, target);	
			}

			// Update xenon information to the default targets on the user's machine
			try
			{
				Target target = Configuration.Instance.GetRegisteredTarget(DefaultGameXenonStringLower, DEFAULT_PORT);
				if (target == null)
				{
					string dnsName = RegistrySettings.GetXenonDNSName();
					if (dnsName != null)
					{
						string xenonIpAddress = GetIpAddress(DefaultGameXenonStringLower, dnsName, XenonName);
						// If the target manager doesn't already have a xenon target - add one
						if (!ContainsTarget(DefaultGameXenonStringLower, xenonIpAddress, DEFAULT_PORT))
							this.Add(DefaultGameXenonStringLower, XenonName, dnsName, xenonIpAddress, DEFAULT_PORT, true);
					}
				}
			}
			catch { }
		}

		/// <summary>
		/// Not sure how to figure out the IP address or dns name of anything other than pc and default xenon
		/// </summary>
		/// <param name="targetName"></param>
		/// <returns></returns>
		public string GetDnsName(string targetName)
		{
			string dnsName = null;
			if (targetName.ToLower() == DefaultGameXenonString.ToLower())
				dnsName = RegistrySettings.GetXenonDNSName();
			else if (targetName.ToLower() == DefaultGamePcString.ToLower())
				dnsName = Dns.GetHostName();

			return dnsName;
		}

		/// <summary>
		/// If the xenon dll can't be loaded this method can throw a nasty exception:
		/// FileNotFoundException: Could not load file or assembly 'XDevkit
		/// probably should just handle it
		/// </summary>
		/// <param name="targetName"></param>
		/// <param name="dnsName"></param>
		/// <param name="platform"></param>
		/// <returns></returns>
		public string GetIpAddress(string targetName, string dnsName, string platform)
		{
			targetName = targetName.ToLower();
			string ipAddress = null;
			try
			{
				if (targetName.Equals(DefaultGameXenonStringLower))
				{
					XDevkit.XboxManagerClass xbm = new XDevkit.XboxManagerClass();
					string defaultConsoleId = xbm.DefaultConsole;
					if (string.IsNullOrEmpty(defaultConsoleId))
						return ipAddress;

					XDevkit.IXboxConsole xBoxConsole = xbm.OpenConsole(defaultConsoleId);
					byte[] ipAddressBytes = BitConverter.GetBytes(xBoxConsole.IPAddressTitle);
					ipAddress = ipAddressBytes[3] + "." + ipAddressBytes[2] + "." + ipAddressBytes[1] + "." + ipAddressBytes[0];
				}
				else if (platform != XenonName) // don't bother trying to look-up the IP address for the xenon
				{
					ipAddress = ResolveIpAddress(dnsName);
				}
			}
			catch { }
			return ipAddress;
		}

		public void Add(string targetName, string platform, string dnsName, string ipAddress,
			int port, bool autoResolveIpAddress)
		{
			Target t = new Target(targetName.ToLower(), platform.ToLower(), dnsName, ipAddress, autoResolveIpAddress, port);

			// Add to table
			if (_Targets.ContainsKey(t.Name))
			{
				_Targets[t.Name] = t;
			}
			else
			{
				_Targets.Add(t.Name, t);
			}

			Configuration.Instance.AddOrUpdateRegisteredTarget(t);
		}

		public void Update(string targetName, string platform, string dnsName, string ipAddress,
			int port, bool autoResolveIpAddress)
		{
			Target target;
			if (!_Targets.TryGetValue(targetName, out target))
			{
				Add(targetName, platform, dnsName, ipAddress, port, autoResolveIpAddress);
				return;
			}

			target.Name = targetName.ToLower();
			target.Platform = platform.ToLower();
			target.DnsName = dnsName;
			target.IpAddress = ipAddress;
			target.Port = port;
			target.AutoResolveIp = autoResolveIpAddress;

			Configuration.Instance.AddOrUpdateRegisteredTarget(target);
		}

		public void Remove(string targetName)
		{
			targetName = targetName.ToLower();
			// Remove from table
			if (_Targets.ContainsKey(targetName))
			{
				_Targets.Remove(targetName);
			}

			// Remove from registry
			try
			{
				RegistrySettings.RegistryKeyArea.DeleteSubKeyTree(_KeyName + targetName);
			}
			catch { }
		}

		#region Ip Address Management

		/// <summary>
		/// Convert a Dns name to an ip address.
        /// 
        /// NOTE: WE SHOULD REALLY GIVE GameTalkMonitor/GameTalkViewer ACCESS/DEPENDENCY ON THE NETWORK FUNCTIONS (LIKE
        /// THIS ONE) THAT ARE ALREADY AVAILABLE IN GameTalkMessages.  REDUCTION OF CODE MAINTENANCE!  BUGS HAVE BEEN
        /// FIXED IN BOTH VERSIONS!
		/// </summary>
		/// <param name="hostName">The host name to be resolved.</param>
		/// <returns>The ip address if resolved otherwise the host name is returned.</returns>
		private string ResolveIpAddress(string hostName)
		{
			// We set the following to null instead of hostName - so the invoker of this can 
			// determine when this lookup fails
			string ipAddress = null;
			try
			{
				// Then using host name, get the IP address list..
                // Important that we use Dns.GetHostAddresses instead of Dns.GetHostEntry - since if you give GetHostEntry
                // an IP address it can attempt a reverse look-up and end up with a host name that was unintended - this
                // only comes up once in a blue moon - but GetHostEntry will get the wrong address list if it is handed a raw IP address
                IPAddress[] addressList = Dns.GetHostAddresses(hostName);
				for (int i = 0; i < addressList.Length; i++)
				{
					Console.WriteLine("IP Address {0}: {1} ", i, addressList[i].ToString());
                    // We will force use of IPV4 - since IPV4 and IPV6 aren't compatible and we can't guarantee that all 
                    // computers interested in connecting over GameTalk will have IPV6 (AddressFamily.InterNetworkV6)
                    if (addressList[i].AddressFamily == AddressFamily.InterNetwork)
                    {
                        ipAddress = addressList[i].ToString();
                        break;
                    }
				}
			}
			catch (Exception e)
			{
				System.Diagnostics.Trace.WriteLine(e.Message + Environment.NewLine + e.InnerException);
			}

			return ipAddress;
		}

		#endregion
	}
}
