#region Copyright & License Information
/*
 * Copyright 2007-2013 The OpenRA Developers (see AUTHORS)
 * This file is part of OpenRA, which is free software. It is made
 * available to you under the terms of the GNU General Public License
 * as published by the Free Software Foundation. For more information,
 * see COPYING.
 */
#endregion

using System;

using Mono.Nat;
using Mono.Nat.Pmp;
using Mono.Nat.Upnp;

namespace OpenRA.Network
{
	public class UPnP
	{
		public static INatDevice NatDevice;

		public static void TryNatDiscovery()
		{
			try
			{
				NatUtility.Logger = Log.Channels["server"].Writer;
				NatUtility.Verbose = Game.Settings.Server.VerboseNatDiscovery;
				NatUtility.DeviceFound += DeviceFound;
				NatUtility.DeviceLost += DeviceLost;
				Game.Settings.Server.NatDeviceAvailable = false;
				NatUtility.StartDiscovery();
				Log.Write("server", "NAT discovery started.");
			}
			catch (Exception e)
			{
				Log.Write("server", "Can't discover UPnP-enabled device: {0}", e);
				Game.Settings.Server.NatDeviceAvailable = false;
				Game.Settings.Server.AllowPortForward = false;
			}
		}

		public static void TryStoppingNatDiscovery()
		{
			Log.Write("server", "Stopping NAT discovery.");

			try
			{
				NatUtility.StopDiscovery();
			}
			catch (Exception e)
			{
				Log.Write("server", "Failed to stop NAT device discovery: {0}", e);
				Game.Settings.Server.NatDeviceAvailable = false;
				Game.Settings.Server.AllowPortForward = false;
			}
				
			if (NatDevice == null)
			{
				Log.Write("server", "No NAT devices with UPnP enabled found within {0} ms deadline. Disabling automatic port forwarding.".F(Game.Settings.Server.NatDiscoveryTimeout));
				Game.Settings.Server.NatDeviceAvailable = false;
				Game.Settings.Server.AllowPortForward = false;
			}
		}

		public static void DeviceFound(object sender, DeviceEventArgs args)
		{
			if (args.Device == null)
				return;
			
			Log.Write("server", "NAT device discovered.");
			
			Game.Settings.Server.NatDeviceAvailable = true;
			Game.Settings.Server.AllowPortForward = true;
			
			try
			{
				NatDevice = args.Device;
				Log.Write("server", "Type: {0}", NatDevice.GetType());
				Log.Write("server", "Your external IP is: {0}", NatDevice.GetExternalIP());
				
				foreach (var mp in NatDevice.GetAllMappings())
					Log.Write("server", "Existing port mapping: protocol={0}, public={1}, private={2}",
					          mp.Protocol, mp.PublicPort, mp.PrivatePort);
			}
			catch (Exception e)
			{
				Log.Write("server", "Can't fetch information from NAT device: {0}", e);
				
				Game.Settings.Server.NatDeviceAvailable = false;
				Game.Settings.Server.AllowPortForward = false;
			}
		}
		
		public static void DeviceLost(object sender, DeviceEventArgs args)
		{
			Log.Write("server", "NAT device lost.");
			
			if (args.Device == null)
				return;
			
			try
			{
				NatDevice = args.Device;
				Log.Write("server", "Type: {0}", NatDevice.GetType());
			}
			catch (Exception e)
			{
				Log.Write("server", "Can't fetch type from lost NAT device: {0}", e);
			}
			
			Game.Settings.Server.NatDeviceAvailable = false;
			Game.Settings.Server.AllowPortForward = false;
		}

		public static void ForwardPort()
		{
			try
			{
				var mapping = new Mapping(Protocol.Tcp, Game.Settings.Server.ExternalPort, Game.Settings.Server.ListenPort);
				NatDevice.CreatePortMap(mapping);
				Log.Write("server", "Create port mapping: protocol={0}, public={1}, private={2}", mapping.Protocol, mapping.PublicPort, mapping.PrivatePort);
			}
			catch (Exception e)
			{
				Log.Write("server", "Can not forward ports via UPnP: {0}", e);
				Game.Settings.Server.AllowPortForward = false;
			}
		}
		
		public static void RemovePortforward()
		{
			try
			{
				var mapping = new Mapping(Protocol.Tcp, Game.Settings.Server.ExternalPort, Game.Settings.Server.ListenPort);
				NatDevice.DeletePortMap(mapping);
				Log.Write("server", "Remove port mapping: protocol={0}, public={1}, private={2}", mapping.Protocol, mapping.PublicPort, mapping.PrivatePort);
			}
			catch (Exception e)
			{
				Log.Write("server", "Can not remove UPnP portforwarding rules: {0}", e);
				Game.Settings.Server.AllowPortForward = false;
			}
		}
	}
}