using System;
using System.Collections.Generic;
using System.Net;
using System.Threading;
using System.Text;
using TCMPortMapper.Helpers;


namespace TCMPortMapper
{
    public class ExistingUPnPPortMapping
    {
        private IPAddress localAddress;
        private UInt16 localPort;
        private UInt16 externalPort;
        private PortMappingTransportProtocol transportProtocol;
        private String description;

        public ExistingUPnPPortMapping(IPAddress localAddress, UInt16 localPort, UInt16 externalPort,
                                       PortMappingTransportProtocol transportProtocol, String description)
        {
            this.localAddress = localAddress;
            this.localPort = localPort;
            this.externalPort = externalPort;
            this.transportProtocol = transportProtocol;
            this.description = description;
        }

        public IPAddress LocalAddress
        {
            get { return localAddress; }
        }

        public UInt16 LocalPort
        {
            get { return localPort; }
        }

        public UInt16 ExternalPort
        {
            get { return externalPort; }
        }

        public PortMappingTransportProtocol TransportProtocol
        {
            get { return transportProtocol; }
        }

        public String Description
        {
            get { return description; }
        }
    }

    class UPnPPortMapper
    {
        public delegate void PMDidFail(UPnPPortMapper sender);
        public delegate void PMDidGetExternalIPAddress(UPnPPortMapper sender, IPAddress ip, bool isFromRouter);
        public delegate void PMDidBeginWorking(UPnPPortMapper sender);
        public delegate void PMDidEndWorking(UPnPPortMapper sender);

        public event PMDidFail DidFail;
        public event PMDidGetExternalIPAddress DidGetExternalIPAddress;
        public event PMDidBeginWorking DidBeginWorking;
        public event PMDidEndWorking DidEndWorking;

        private Object multiThreadLock = new Object();
        private Object singleThreadLock = new Object();

        private volatile ThreadID threadID;
        private volatile ThreadFlags refreshExternalIPThreadFlags;
        private volatile ThreadFlags updatePortMappingsThreadFlags;

        private List<ExistingUPnPPortMapping> existingUPnPPortMappings;
        private Dictionary<UInt16, ExistingUPnPPortMapping> existingUPnPPortMappingsUdpDict;
        private Dictionary<UInt16, ExistingUPnPPortMapping> existingUPnPPortMappingsTcpDict;

        private MiniUPnP.UPNPUrls urls = new MiniUPnP.UPNPUrls();
        private MiniUPnP.IGDdatas igddata = new MiniUPnP.IGDdatas();

        private enum ThreadID
        {
            None = 0,
            RefreshExternalIP = 1,
            UpdatePortMappings = 2
        }

        [Flags]
        private enum ThreadFlags
        {
            None = 0,
            ShouldQuit = 1,
            ShouldRestart = 2
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////////////////////
        #region Public API
        ///////////////////////////////////////////////////////////////////////////////////////////////////////////////

        public UPnPPortMapper()
        {
            // Nothing to do here
        }

        public void Refresh()
        {
            refreshExternalIPThreadFlags = ThreadFlags.None;
            updatePortMappingsThreadFlags = ThreadFlags.None;

            //threadID = ThreadID.RefreshExternalIP;

            RefreshExternalIPThread();

            //if (threadID == ThreadID.RefreshExternalIP)
            //{
            //    refreshExternalIPThreadFlags = ThreadFlags.ShouldQuit | ThreadFlags.ShouldRestart;
            //}
            //else if (threadID == ThreadID.UpdatePortMappings)
            //{
            //    updatePortMappingsThreadFlags = ThreadFlags.ShouldQuit;
            //}
        }

        public void UpdatePortMappings()
        {
            updatePortMappingsThreadFlags = ThreadFlags.None;

            //threadID = ThreadID.UpdatePortMappings;

            UpdatePortMappingsThread();

            //if (threadID == ThreadID.UpdatePortMappings)
            //{
            //    updatePortMappingsThreadFlags = ThreadFlags.ShouldQuit | ThreadFlags.ShouldRestart;
            //}
        }

        public void Stop()
        {
            // Restart update to remove mappings before stopping
            UpdatePortMappings();
        }

        public void StopBlocking()
        {
            refreshExternalIPThreadFlags = ThreadFlags.ShouldQuit;
            updatePortMappingsThreadFlags = ThreadFlags.ShouldQuit;

            DoUpdateExistingUPnPPortMappings();

            List<PortMapping> mappingsToRemove = PortMapper.SharedInstance.PortMappingsToRemove;
            while (mappingsToRemove.Count > 0)
            {
                PortMapping pm = mappingsToRemove[0];

                if (pm.MappingStatus == PortMappingStatus.Mapped)
                {
                    RemovePortMapping(pm);
                }

                mappingsToRemove.RemoveAt(0);
            }

            List<PortMapping> mappingsToStop = PortMapper.SharedInstance.PortMappings;
            for (int i = 0; i < mappingsToStop.Count; i++)
            {
                PortMapping pm = mappingsToStop[i];

                if (pm.MappingStatus == PortMappingStatus.Mapped)
                {
                    RemovePortMapping(pm);
                }
            }
        }

        public void UpdateExistingUPnPPortMappings()
        {
            // All public API methods are wrapped in a single thread lock.
            // This frees users to invoke the public API from multiple threads, but provides us a bit of sanity.
            //lock (singleThreadLock)
            //{
            //Thread bgThread = new Thread(new ThreadStart(UpdateExistingUPnPMappingsThread));
            //bgThread.Start();
            //}
            UpdateExistingUPnPMappingsThread();
        }

        public List<ExistingUPnPPortMapping> ExistingUPnPPortMappings
        {
            get { return existingUPnPPortMappings; }
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////////////////////
        #endregion
        ///////////////////////////////////////////////////////////////////////////////////////////////////////////////

        ///////////////////////////////////////////////////////////////////////////////////////////////////////////////
        #region Delegate Methods
        ///////////////////////////////////////////////////////////////////////////////////////////////////////////////

        protected virtual void OnDidFail()
        {
            if (DidFail != null)
            {
                DidFail(this);
                //PortMapper.SharedInstance.Invoke(DidFail, this);
            }
        }

        protected virtual void OnDidGetExternalIPAddress(IPAddress ip, bool isFromRouter)
        {
            if (DidGetExternalIPAddress != null)
            {
                DidGetExternalIPAddress(this, ip, isFromRouter);
                //PortMapper.SharedInstance.Invoke(DidGetExternalIPAddress, this, ip, isFromRouter);
            }
        }

        protected virtual void OnDidBeginWorking()
        {
            if (DidBeginWorking != null)
            {
                // This is thread safe, so there's no need to Invoke it
                DidBeginWorking(this);
            }
        }

        protected virtual void OnDidEndWorking()
        {
            if (DidEndWorking != null)
            {
                // This is thread safe, so there's no need to Invoke it
                DidEndWorking(this);
            }
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////////////////////
        #endregion
        ///////////////////////////////////////////////////////////////////////////////////////////////////////////////

        ///////////////////////////////////////////////////////////////////////////////////////////////////////////////
        #region Private API
        ///////////////////////////////////////////////////////////////////////////////////////////////////////////////

        private String GetPortMappingDescription()
        {
            String machineName = Environment.MachineName;
            String userName = Environment.UserName;

            System.Diagnostics.Process currentProcess = System.Diagnostics.Process.GetCurrentProcess();

            String processName = currentProcess.ProcessName;
            int processId = currentProcess.Id;


            return machineName + "/" + userName + "/" + processName + "/" + processId;
        }

        private void DoUpdateExistingUPnPPortMappings()
        {
            List<ExistingUPnPPortMapping> existingMappings = new List<ExistingUPnPPortMapping>();

            Dictionary<UInt16, ExistingUPnPPortMapping> existingMappingsUdpDict =
                new Dictionary<UInt16, ExistingUPnPPortMapping>();
            Dictionary<UInt16, ExistingUPnPPortMapping> existingMappingsTcpDict =
                new Dictionary<UInt16, ExistingUPnPPortMapping>();

            int r = 0;
            int i = 0;
            int j = 0;
            byte[] index = new byte[6];
            byte[] intClient = new byte[16];
            byte[] intPort = new byte[6];
            byte[] extPort = new byte[6];
            byte[] protocol = new byte[4];
            byte[] desc = new byte[80];
            byte[] enabled = new byte[6];
            byte[] rHost = new byte[64];
            byte[] duration = new byte[16];

            do
            {
                // Convert "int i" to a null-terminated char array
                String iStr = i.ToString();
                int maxCount = Math.Min(iStr.Length, 5);
                System.Text.Encoding.ASCII.GetBytes(iStr, 0, maxCount, index, 0);

                // Reset all the other null-terminated char arrays
                intClient[0] = 0;
                intPort[0] = 0;
                extPort[0] = 0;
                protocol[0] = 0;  // Warning - not in Cocoa version
                desc[0] = 0;
                enabled[0] = 0;
                rHost[0] = 0;
                duration[0] = 0;

                r = MiniUPnP.UPNPCOMMAND_UNKNOWN_ERROR;
                try
                {
                    r = MiniUPnP.UPNP_GetGenericPortMappingEntry(urls.controlURL, igddata.ServiceType,
                                                                 index,
                                                                 extPort, intClient, intPort,
                                                                 protocol, desc, enabled,
                                                                 rHost, duration);
                }
                catch (AccessViolationException)
                {
                    // I have no idea why the above method sometimes throws an AccessException.
                    // The odd part about it is that all the data gets marshaled over and back properly.
                    // So the exception can safely be ignored, it just bugs me because it feels like a hack.
                    DebugLog.WriteLine("Ignoring exception from method MiniUPnP.UPNP_GetGenericPortMappingEntry");

                    r = MiniUPnP.UPNPCOMMAND_SUCCESS;
                }

                if (r == MiniUPnP.UPNPCOMMAND_SUCCESS)
                {
                    IPAddress iAddr;
                    IPAddress.TryParse(MiniUPnP.NullTerminatedArrayToString(intClient), out iAddr);

                    UInt16 iPort;
                    UInt16.TryParse(MiniUPnP.NullTerminatedArrayToString(intPort), out iPort);

                    UInt16 ePort;
                    UInt16.TryParse(MiniUPnP.NullTerminatedArrayToString(extPort), out ePort);

                    PortMappingTransportProtocol transportProtocol = 0;
                    String protocolStr = MiniUPnP.NullTerminatedArrayToString(protocol);
                    if (protocolStr.Equals("UDP", StringComparison.OrdinalIgnoreCase))
                    {
                        transportProtocol |= PortMappingTransportProtocol.UDP;
                    }
                    if (protocolStr.Equals("TCP", StringComparison.OrdinalIgnoreCase))
                    {
                        transportProtocol |= PortMappingTransportProtocol.TCP;
                    }

                    String description = MiniUPnP.NullTerminatedArrayToString(desc);

                    ExistingUPnPPortMapping existingPM;
                    existingPM = new ExistingUPnPPortMapping(iAddr, iPort, ePort, transportProtocol, description);

                    existingMappings.Add(existingPM);

                    if ((transportProtocol & PortMappingTransportProtocol.UDP) > 0)
                    {
                        existingMappingsUdpDict[ePort] = existingPM;
                    }
                    if ((transportProtocol & PortMappingTransportProtocol.TCP) > 0)
                    {
                        existingMappingsTcpDict[ePort] = existingPM;
                    }

                    DebugLog.WriteLine("Existing UPnP: {0}: {1} {2}->{3}:{4} ({5})",
                                                        i, protocolStr, ePort, iAddr, iPort, description);
                }

                i++;
                j++;
            } while ((r == MiniUPnP.UPNPCOMMAND_SUCCESS) && (j < 10));

            // Update stored list of existing mappings
            existingUPnPPortMappings = existingMappings;
            existingUPnPPortMappingsUdpDict = existingMappingsUdpDict;
            existingUPnPPortMappingsTcpDict = existingMappingsTcpDict;
        }

        private bool AddPortMapping(PortMapping portMapping)
        {
            portMapping.SetMappingStatus(PortMappingStatus.Trying);

            String intPortStr = portMapping.LocalPort.ToString();
            String intClient = PortMapper.SharedInstance.LocalIPAddress.ToString();
            //String description = GetPortMappingDescription();
            String description = portMapping.Description;

            bool done = false;
            int attemptCount = 0;
            do
            {
                int udpErrCode = 0;
                int tcpErrCode = 0;

                bool udpResult = true;
                bool tcpResult = true;

                UInt16 extPort;
                if (portMapping.DesiredExternalPort < (65535 - 40))
                    extPort = (UInt16)(portMapping.DesiredExternalPort + attemptCount);
                else
                    extPort = (UInt16)(portMapping.DesiredExternalPort - attemptCount);

                String extPortStr = extPort.ToString();

                if ((portMapping.TransportProtocol & PortMappingTransportProtocol.UDP) > 0)
                {
                    ExistingUPnPPortMapping existingPM;
                    if (existingUPnPPortMappingsUdpDict.TryGetValue(extPort, out existingPM))
                    {
                        udpErrCode = 718;
                        DebugLog.WriteLine("UPnP: AddPortMapping: UDP: mapping already exists");
                    }
                    else
                    {
                        udpErrCode = MiniUPnP.UPNP_AddPortMapping(urls.controlURL, igddata.ServiceType,
                                                                  extPortStr, intPortStr, intClient, description, "UDP");
                        DebugLog.WriteLine("UPnP: AddPortMapping: UDP: result = {0}", udpErrCode);
                    }

                    udpResult = (udpErrCode == MiniUPnP.UPNPCOMMAND_SUCCESS);
                }
                if ((portMapping.TransportProtocol & PortMappingTransportProtocol.TCP) > 0)
                {
                    ExistingUPnPPortMapping existingPM;
                    if (existingUPnPPortMappingsTcpDict.TryGetValue(extPort, out existingPM))
                    {
                        tcpErrCode = 718;
                        DebugLog.WriteLine("UPnP: AddPortMapping: TCP: mapping already exists");
                    }
                    else
                    {
                        tcpErrCode = MiniUPnP.UPNP_AddPortMapping(urls.controlURL, igddata.ServiceType,
                                                                  extPortStr, intPortStr, intClient, description, "TCP");
                        DebugLog.WriteLine("UPnP: AddPortMapping: TCP: result = {0}", tcpErrCode);
                    }

                    tcpResult = (tcpErrCode == MiniUPnP.UPNPCOMMAND_SUCCESS);
                }

                if (udpResult && !tcpResult)
                {
                    DebugLog.WriteLine("Deleting UDP mapping");
                    try
                    {
                        MiniUPnP.UPNP_DeletePortMapping(urls.controlURL, igddata.ServiceType, extPortStr, "UDP");
                    }
                    catch (AccessViolationException)
                    {
                        // I have no idea why the above method sometimes throws an AccessException.
                        // The odd part about it is that it works perfect, except for the stupid exception.
                        // So the exception can safely be ignored, it just bugs me because it feels like a hack.
                        DebugLog.WriteLine("Ignoring exception from method MiniUPnP.UPNP_DeletePortMapping");
                    }
                }
                if (tcpResult && !udpResult)
                {
                    DebugLog.WriteLine("Deleting TCP mapping");
                    try
                    {
                        MiniUPnP.UPNP_DeletePortMapping(urls.controlURL, igddata.ServiceType, extPortStr, "TCP");
                    }
                    catch (AccessViolationException)
                    {
                        // I have no idea why the above method sometimes throws an AccessException.
                        // The odd part about it is that it works perfect, except for the stupid exception.
                        // So the exception can safely be ignored, it just bugs me because it feels like a hack.
                        DebugLog.WriteLine("Ignoring exception from method MiniUPnP.UPNP_DeletePortMapping");
                    }
                }

                if (udpResult && tcpResult)
                {
                    // All attempted port mappings were successful
                    portMapping.SetExternalPort(extPort);
                    portMapping.SetMappingStatus(PortMappingStatus.Mapped);
                    return true;
                }

                attemptCount++;
                //if (attemptCount >= 10)
                if (attemptCount >= 100)
                {
                    // We've tried 100 different mappings and still no success
                    done = true;
                }
                else if (!udpResult && udpErrCode != 718)
                {
                    // We received non-conflict error
                    done = true;
                }
                else if (!tcpResult && tcpErrCode != 718)
                {
                    // We received non-conflict error
                    done = true;
                }

            } while (!done);

            portMapping.SetMappingStatus(PortMappingStatus.Unmapped);
            return false;
        }

        private bool RemovePortMapping(PortMapping portMapping)
        {
            // Make sure the mapping still belongs to us
            IPAddress ourIP = PortMapper.SharedInstance.LocalIPAddress;
            String ourDescription = GetPortMappingDescription();

            bool udpMappingStolen = false;
            bool tcpMappingStolen = false;

            if ((portMapping.TransportProtocol & PortMappingTransportProtocol.UDP) > 0)
            {
                ExistingUPnPPortMapping existingPM;
                if (existingUPnPPortMappingsUdpDict.TryGetValue(portMapping.ExternalPort, out existingPM))
                {
                    if (!existingPM.LocalAddress.Equals(ourIP) || !existingPM.Description.Equals(ourDescription))
                    {
                        // The mapping was stolen by another machine or process
                        // Do not remove it, but for our purposes we can consider it removed

                        DebugLog.WriteLine("UPnP: RemovePortMapping: UDP mapping stolen");
                        udpMappingStolen = true;
                    }
                }
            }
            if ((portMapping.TransportProtocol & PortMappingTransportProtocol.TCP) > 0)
            {
                ExistingUPnPPortMapping existingPM;
                if (existingUPnPPortMappingsTcpDict.TryGetValue(portMapping.ExternalPort, out existingPM))
                {
                    if (!existingPM.LocalAddress.Equals(ourIP) || !existingPM.Description.Equals(ourDescription))
                    {
                        // The mapping was stolen by another machine or process
                        // Do not remove it, but for our purposes we can consider it removed

                        DebugLog.WriteLine("UPnP: RemovePortMapping: TCM mapping stolen");
                        tcpMappingStolen = true;
                    }
                }
            }

            int result = MiniUPnP.UPNPCOMMAND_SUCCESS;

            bool udpResult = true;
            bool tcpResult = true;

            String extPortStr = portMapping.ExternalPort.ToString();

            if ((portMapping.TransportProtocol & PortMappingTransportProtocol.UDP) > 0 && !udpMappingStolen)
            {
                try
                {
                    result = MiniUPnP.UPNP_DeletePortMapping(urls.controlURL, igddata.ServiceType, extPortStr, "UDP");
                }
                catch (AccessViolationException)
                {
                    // I have no idea why the above method sometimes throws an AccessException.
                    // The odd part about it is that it works perfect, except for the stupid exception.
                    // So the exception can safely be ignored, it just bugs me because it feels like a hack.
                    DebugLog.WriteLine("Ignoring exception from method MiniUPnP.UPNP_DeletePortMapping");
                }

                DebugLog.WriteLine("UPnP: RemovePortMapping: UDP: result = {0}", result);
                udpResult = (result == MiniUPnP.UPNPCOMMAND_SUCCESS);
            }
            if ((portMapping.TransportProtocol & PortMappingTransportProtocol.TCP) > 0 && !tcpMappingStolen)
            {
                try
                {
                    result = MiniUPnP.UPNP_DeletePortMapping(urls.controlURL, igddata.ServiceType, extPortStr, "TCP");
                }
                catch (AccessViolationException)
                {
                    // I have no idea why the above method sometimes throws an AccessException.
                    // The odd part about it is that it works perfect, except for the stupid exception.
                    // So the exception can safely be ignored, it just bugs me because it feels like a hack.
                    DebugLog.WriteLine("Ignoring exception from method MiniUPnP.UPNP_DeletePortMapping");
                }

                DebugLog.WriteLine("UPnP: RemovePortMapping: TCP: result = {0}", result);
                tcpResult = (result == MiniUPnP.UPNPCOMMAND_SUCCESS);
            }

            portMapping.SetMappingStatus(PortMappingStatus.Unmapped);

            return (udpResult && tcpResult);
        }

        private bool RemovePortMapping(ExistingUPnPPortMapping portMapping)
        {
            int result = MiniUPnP.UPNPCOMMAND_SUCCESS;

            bool udpResult = true;
            bool tcpResult = true;

            String extPortStr = portMapping.ExternalPort.ToString();

            if ((portMapping.TransportProtocol & PortMappingTransportProtocol.UDP) > 0)
            {
                try
                {
                    result = MiniUPnP.UPNP_DeletePortMapping(urls.controlURL, igddata.ServiceType, extPortStr, "UDP");
                }
                catch (AccessViolationException)
                {
                    // I have no idea why the above method sometimes throws an AccessException.
                    // The odd part about it is that it works perfect, except for the stupid exception.
                    // So the exception can safely be ignored, it just bugs me because it feels like a hack.
                    DebugLog.WriteLine("Ignoring exception from method MiniUPnP.UPNP_DeletePortMapping");
                }

                DebugLog.WriteLine("UPnP: RemovePortMapping: UDP: result = {0}", result);
                udpResult = (result == MiniUPnP.UPNPCOMMAND_SUCCESS);
            }

            if ((portMapping.TransportProtocol & PortMappingTransportProtocol.TCP) > 0)
            {
                try
                {
                    result = MiniUPnP.UPNP_DeletePortMapping(urls.controlURL, igddata.ServiceType, extPortStr, "TCP");
                }
                catch (AccessViolationException)
                {
                    // I have no idea why the above method sometimes throws an AccessException.
                    // The odd part about it is that it works perfect, except for the stupid exception.
                    // So the exception can safely be ignored, it just bugs me because it feels like a hack.
                    DebugLog.WriteLine("Ignoring exception from method MiniUPnP.UPNP_DeletePortMapping");
                }

                DebugLog.WriteLine("UPnP: RemovePortMapping: TCP: result = {0}", result);
                tcpResult = (result == MiniUPnP.UPNPCOMMAND_SUCCESS);
            }

            return (udpResult && tcpResult);
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////////////////////
        #endregion
        ///////////////////////////////////////////////////////////////////////////////////////////////////////////////

        ///////////////////////////////////////////////////////////////////////////////////////////////////////////////
        #region Refresh External IP Thread
        ///////////////////////////////////////////////////////////////////////////////////////////////////////////////

        private void RefreshExternalIPThread()
        {
            OnDidBeginWorking();

            IPAddress externalIP;
            IntPtr devlistP = IntPtr.Zero;
            byte[] lanAddr = new byte[16];
            byte[] externalAddr = new byte[16];

            bool didFail = false;

            devlistP = MiniUPnP.upnpDiscover(2500, IntPtr.Zero, IntPtr.Zero);
            if (devlistP == IntPtr.Zero)
            {
                //First do a last ditch effort check for an external IP
                //in case the server is not connected to a router, but
                //directly to a modem or is external facing already
                var ipAddress = Utility.GetExternalIP(10000, true);
                if (String.IsNullOrEmpty(ipAddress))
                {
                    DebugLog.WriteLine("UPnP: No IDG Device found on the network (1)");
                    didFail = true;
                }
                else
                {
                    IPAddress.TryParse(ipAddress, out externalIP);

                    if (externalIP != null)
                    {
                        OnDidGetExternalIPAddress(externalIP, false);

                        didFail = false;
                    }
                }
            }
            else
            {
                MiniUPnP.UPNPDev devlist = MiniUPnP.PtrToUPNPDev(devlistP);

                // Check all of the devices for reachability
                bool foundIDGDevice = false;
                MiniUPnP.UPNPDev device = devlist;

                IPAddress routerIP = PortMapper.SharedInstance.RouterIPAddress;

                List<String> descURLs = new List<String>();

                bool done = false;
                while (!done)
                {
                    try
                    {
                        Uri uri = new Uri(device.descURL);

                        if (routerIP != null)
                        {
                            if (uri.Host == routerIP.ToString())
                            {
                                descURLs.Insert(0, device.descURL);
                            }
                            else
                            {
                                descURLs.Add(device.descURL);
                            }
                        }
                        else
                        {
                            descURLs.Add(device.descURL);
                        }
                    }
                    catch (Exception e)
                    {
                        DebugLog.WriteLine("UPnP: Error while inspecting url: {0}", device.descURL);
                        DebugLog.WriteLine("UPnP: Exception: {0}", e);
                    }

                    if (device.pNext == IntPtr.Zero)
                        done = true;
                    else
                        device = device.Next;
                }

                for (int i = 0; i < descURLs.Count && !foundIDGDevice; i++)
                {
                    String url = descURLs[i];
                    DebugLog.WriteLine("UPnP: Trying URL: {0}", url);

                    // Reset service type.
                    // This will help us determine if the exception below can safely be ignored.
                    igddata.ServiceType = null;

                    int r = 0;
                    try
                    {
                        //	r = MiniUPnP.UPNP_GetIGDFromUrl(url, ref urls, ref igddata, lanAddr, lanAddr.Length);

                        MiniUPnP.UPNPUrls_2 urls_2 = new MiniUPnP.UPNPUrls_2();
                        unsafe
                        {
                            r = MiniUPnP.UPNP_GetIGDFromUrl(url, &urls_2, ref igddata, lanAddr, lanAddr.Length);
                            MiniUPnP.FreeUPNPUrls(&urls_2);
                        }

                        // Find urls
                        GetUPNPUrls(url);
                    }
                    catch (AccessViolationException)
                    {
                        // I have no idea why the above method sometimes throws an AccessException.
                        // The odd part about it is that all the data gets marshaled over and back properly.
                        // So the exception can safely be ignored, it just bugs me because it feels like a hack.
                        DebugLog.WriteLine("Ignoring exception from method MiniUPnP.UPNP_GetIGDFromUrl");

                        if (igddata.ServiceType != null)
                        {
                            r = 1;
                        }
                    }

                    if (r == 1)
                    {
                        r = MiniUPnP.UPNPCOMMAND_UNKNOWN_ERROR;
                        try
                        {
                            r = MiniUPnP.UPNP_GetExternalIPAddress(urls.controlURL, igddata.ServiceType, externalAddr);
                        }
                        catch (AccessViolationException)
                        {
                            // I have no idea why the above method sometimes throws an AccessException.
                            // The odd part about it is that all the data gets marshaled over and back properly.
                            // So the exception can safely be ignored, it just bugs me because it feels like a hack.
                            DebugLog.WriteLine("Ignoring exception from method MiniUPnP.UPNP_GetExternalIPAddress");

                            if (externalAddr[0] != 0)
                            {
                                r = MiniUPnP.UPNPCOMMAND_SUCCESS;
                            }
                        }

                        if (r != MiniUPnP.UPNPCOMMAND_SUCCESS)
                        {
                            DebugLog.WriteLine("UPnP: GetExternalIPAddress returned {0}", r);
                        }
                        else
                        {
                            IPAddress.TryParse(MiniUPnP.NullTerminatedArrayToString(externalAddr), out externalIP);

                            if (externalIP != null)
                            {
                                OnDidGetExternalIPAddress(externalIP, true);

                                foundIDGDevice = true;
                                didFail = false;
                            }
                        }
                    }
                }

                if (!foundIDGDevice)
                {
                    DebugLog.WriteLine("UPnP: No IDG Device found on the network (2)");
                    didFail = true;
                }

                try
                {
                    MiniUPnP.freeUPNPDevlist(devlistP);
                }
                catch (AccessViolationException)
                {
                    // I have no idea why the above method sometimes throws an AccessException.
                    // The odd part about it is that all the data gets marshaled over and back properly.
                    // So the exception can safely be ignored, it just bugs me because it feels like a hack.
                    DebugLog.WriteLine("Ignoring exception from method MiniUPnP.freeUPNPDevlist");
                }
            }

            if (refreshExternalIPThreadFlags != ThreadFlags.None)
            {
                if ((refreshExternalIPThreadFlags & ThreadFlags.ShouldRestart) > 0)
                {
                    Refresh();
                }
            }
            else
            {
                if (didFail)
                {
                    OnDidFail();
                }
                else
                {
                    UpdatePortMappings();
                }
            }
            OnDidEndWorking();
        }

        private void GetUPNPUrls(String url)
        {
            if (String.IsNullOrEmpty(igddata.urlbase))
                urls.ipcondescURL = url;
            else
                urls.ipcondescURL = igddata.urlbase;

            int index_fin_url = urls.ipcondescURL.IndexOf('/', 7); // 7 = http://

            if (index_fin_url >= 0)
            {
                urls.ipcondescURL = urls.ipcondescURL.Substring(0, index_fin_url);
            }

            urls.controlURL = urls.ipcondescURL + igddata.controlurl;
            urls.controlURL_CIF = urls.ipcondescURL + igddata.controlurl_CIF;
            urls.ipcondescURL += igddata.scpdurl;
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////////////////////
        #endregion
        ///////////////////////////////////////////////////////////////////////////////////////////////////////////////

        ///////////////////////////////////////////////////////////////////////////////////////////////////////////////
        #region Update Port Mappings Thread
        ///////////////////////////////////////////////////////////////////////////////////////////////////////////////

        private void UpdatePortMappingsThread()
        {
            OnDidBeginWorking();

            // Remove existing mappings scheduled for removal.
            // These are mappings that weren't created by us, but have been explicity set for removal.
            List<ExistingUPnPPortMapping> existingMappingsToRemove;
            existingMappingsToRemove = PortMapper.SharedInstance.ExistingUPnPPortMappingsToRemove;
            while ((existingMappingsToRemove.Count > 0) && (updatePortMappingsThreadFlags == ThreadFlags.None))
            {
                ExistingUPnPPortMapping existingMappingToRemove = existingMappingsToRemove[0];

                RemovePortMapping(existingMappingToRemove);

                existingMappingsToRemove.RemoveAt(0);
            }

            // We need to safeguard mappings that others might have made.
            // UPnP is quite generous in giving us what we want,
            // even if other mappings are there, especially from the same local machine.
            DoUpdateExistingUPnPPortMappings();

            // Remove mappings scheduled for removal
            List<PortMapping> mappingsToRemove = PortMapper.SharedInstance.PortMappingsToRemove;
            while ((mappingsToRemove.Count > 0) && (updatePortMappingsThreadFlags == ThreadFlags.None))
            {
                PortMapping mappingToRemove = mappingsToRemove[0];

                if (mappingToRemove.MappingStatus == PortMappingStatus.Mapped)
                {
                    RemovePortMapping(mappingToRemove);
                }

                mappingsToRemove.RemoveAt(0);
            }

            // If the port mapper is running:
            //   -Add new mappings
            // If the port mapper is stopped:
            //   -Remove any existing mappings
            List<PortMapping> mappings = PortMapper.SharedInstance.PortMappings;
            for (int i = 0; i < mappings.Count && updatePortMappingsThreadFlags == ThreadFlags.None; i++)
            {
                PortMapping currentMapping = mappings[i];
                bool isRunning = PortMapper.SharedInstance.IsRunning;

                if (currentMapping.MappingStatus == PortMappingStatus.Unmapped && isRunning)
                {
                    AddPortMapping(currentMapping);
                }
                else if (currentMapping.MappingStatus == PortMappingStatus.Mapped && !isRunning)
                {
                    RemovePortMapping(currentMapping);
                }
            }

            if (PortMapper.SharedInstance.IsRunning)
            {
                if ((updatePortMappingsThreadFlags & ThreadFlags.ShouldRestart) > 0)
                {
                    UpdatePortMappings();
                }
                else if ((updatePortMappingsThreadFlags & ThreadFlags.ShouldQuit) > 0)
                {
                    Refresh();
                }
            }

            OnDidEndWorking();
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////////////////////
        #endregion
        ///////////////////////////////////////////////////////////////////////////////////////////////////////////////

        ///////////////////////////////////////////////////////////////////////////////////////////////////////////////
        #region Update Existing UPnP Port Mappings Thread
        ///////////////////////////////////////////////////////////////////////////////////////////////////////////////

        private void UpdateExistingUPnPMappingsThread()
        {
            OnDidBeginWorking();

            DoUpdateExistingUPnPPortMappings();

            OnDidEndWorking();
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////////////////////
        #endregion
        ///////////////////////////////////////////////////////////////////////////////////////////////////////////////
    }
}
