//----------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
//----------------------------------------------------------------------------
namespace System.ServiceModel.Channels
{
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Net;
    using System.Net.NetworkInformation;
    using System.Net.Sockets;
    using System.Runtime;
    using System.Security.Permissions;
    using System.ServiceModel;
    using System.ServiceModel.Dispatcher;
    using System.Text;

    static class DnsCache
    {
        const int mruWatermark = 64;
        static MruCache<string, DnsCacheEntry> resolveCache = new MruCache<string, DnsCacheEntry>(mruWatermark);
        static readonly TimeSpan cacheTimeout = TimeSpan.FromSeconds(2);

        // Double-checked locking pattern requires volatile for read/write synchronization
        static volatile string machineName;

        static object ThisLock
        {
            get
            {
                return resolveCache;
            }
        }

        public static string MachineName
        {
            get
            {
                if (machineName == null)
                {
                    lock (ThisLock)
                    {
                        if (machineName == null)
                        {
                            try
                            {
                                machineName = Dns.GetHostEntry(String.Empty).HostName;
                            }
                            catch (SocketException exception)
                            {
                                DiagnosticUtility.TraceHandledException(exception,
                                        TraceEventType.Information);                                
                                // we fall back to the NetBios machine if Dns fails
                                machineName = UnsafeNativeMethods.GetComputerName(ComputerNameFormat.PhysicalNetBIOS);
                            }
                        }
                    }
                }

                return machineName;
            }
        }

        public static IPHostEntry Resolve(Uri uri)
        {
            string hostName = uri.DnsSafeHost;
            IPHostEntry hostEntry = null;
            DateTime now = DateTime.UtcNow;

            lock (ThisLock)
            {
                DnsCacheEntry cacheEntry;
                if (resolveCache.TryGetValue(hostName, out cacheEntry))
                {
                    if (now.Subtract(cacheEntry.TimeStamp) > cacheTimeout)
                    {
                        resolveCache.Remove(hostName);
                    }
                    else
                    {
                        if (cacheEntry.HostEntry == null)
                        {
                            throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
                                new EndpointNotFoundException(SR.GetString(SR.DnsResolveFailed, hostName)));
                        }
                        hostEntry = cacheEntry.HostEntry;
                    }
                }
            }

            if (hostEntry == null)
            {
                SocketException dnsException = null;
                try
                {
                    hostEntry = Dns.GetHostEntry(hostName);
                }
                catch (SocketException e)
                {
                    dnsException = e;
                }

                lock (ThisLock)
                {
                    // MruCache doesn't have a this[] operator, so we first remove (just in case it exists already)
                    resolveCache.Remove(hostName);
                    resolveCache.Add(hostName, new DnsCacheEntry(hostEntry, now));
                }

                if (dnsException != null)
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
                        new EndpointNotFoundException(SR.GetString(SR.DnsResolveFailed, hostName), dnsException));
                }
            }

            return hostEntry;
        }

        class DnsCacheEntry
        {
            IPHostEntry hostEntry;
            DateTime timeStamp;

            public DnsCacheEntry(IPHostEntry hostEntry, DateTime timeStamp)
            {
                this.hostEntry = hostEntry;
                this.timeStamp = timeStamp;
            }

            public IPHostEntry HostEntry
            {
                get
                {
                    return hostEntry;
                }
            }

            public DateTime TimeStamp
            {
                get
                {
                    return timeStamp;
                }
            }
        }
    }
}