//------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
//------------------------------------------------------------

namespace System.ServiceModel.Channels
{
    using System.Runtime;
    using System.ServiceModel.Dispatcher;

    sealed class InternalDuplexBindingElement : BindingElement
    {
        InputChannelDemuxer clientChannelDemuxer;
        bool providesCorrelation;

        public InternalDuplexBindingElement()
            : this(false)
        {
        }

        // 


        internal InternalDuplexBindingElement(bool providesCorrelation)
            : base()
        {
            this.providesCorrelation = providesCorrelation;
        }

        InternalDuplexBindingElement(InternalDuplexBindingElement elementToBeCloned)
            : base(elementToBeCloned)
        {
            this.clientChannelDemuxer = elementToBeCloned.ClientChannelDemuxer;
            this.providesCorrelation = elementToBeCloned.ProvidesCorrelation;
        }

        internal InputChannelDemuxer ClientChannelDemuxer
        {
            get { return this.clientChannelDemuxer; }
        }

        internal bool ProvidesCorrelation
        {
            get { return this.providesCorrelation; }
        }

        public override BindingElement Clone()
        {
            return new InternalDuplexBindingElement(this);
        }

        public override IChannelFactory<TChannel> BuildChannelFactory<TChannel>(BindingContext context)
        {
            if (context == null)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("context");
            }

            if (!this.CanBuildChannelFactory<TChannel>(context))
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument(
                    "TChannel", SR.GetString(SR.ChannelTypeNotSupported, typeof(TChannel)));
            }

            IChannelFactory<IOutputChannel> innerChannelFactory = context.Clone().BuildInnerChannelFactory<IOutputChannel>();

            if (this.clientChannelDemuxer == null)
            {
                this.clientChannelDemuxer = new InputChannelDemuxer(context);
            }
            else
            {
#pragma warning suppress 56506 // [....], context.RemainingBindingElements will never be null
                context.RemainingBindingElements.Clear();
            }
            LocalAddressProvider localAddressProvider = context.BindingParameters.Remove<LocalAddressProvider>();
            return (IChannelFactory<TChannel>)(object)
                new InternalDuplexChannelFactory(this, context, this.clientChannelDemuxer, innerChannelFactory, localAddressProvider);
        }

        public override IChannelListener<TChannel> BuildChannelListener<TChannel>(BindingContext context)
        {
            if (context == null)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("context");
            }

            if (typeof(TChannel) != typeof(IDuplexChannel))
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("TChannel",
                    SR.GetString(SR.ChannelTypeNotSupported, typeof(TChannel)));
            }

            return (IChannelListener<TChannel>)(object)new InternalDuplexChannelListener(this, context);
        }

        public override bool CanBuildChannelFactory<TChannel>(BindingContext context)
        {
            if (context == null)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("context");

            return typeof(TChannel) == typeof(IDuplexChannel)
                && context.CanBuildInnerChannelFactory<IOutputChannel>()
                && context.CanBuildInnerChannelListener<IInputChannel>();
        }

        public override bool CanBuildChannelListener<TChannel>(BindingContext context)
        {
            if (context == null)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("context");

            return typeof(TChannel) == typeof(IDuplexChannel)
                && context.CanBuildInnerChannelFactory<IOutputChannel>()
                && context.CanBuildInnerChannelListener<IInputChannel>();
        }

        internal static T GetSecurityCapabilities<T>(ISecurityCapabilities lowerCapabilities)
        {
            Fx.Assert(typeof(T) == typeof(ISecurityCapabilities), "Can only be used with ISecurityCapabilities");
            if (lowerCapabilities != null)
            {
                // composite duplex cannot ensure that messages it receives are from the part it sends
                // messages to. So it cannot offer server auth
                return (T)(object)(new SecurityCapabilities(lowerCapabilities.SupportsClientAuthentication,
                    false, lowerCapabilities.SupportsClientWindowsIdentity, lowerCapabilities.SupportedRequestProtectionLevel,
                    System.Net.Security.ProtectionLevel.None));
            }
            else
            {
                return (T)(object)null;
            }
        }

        public override T GetProperty<T>(BindingContext context)
        {
            if (typeof(T) == typeof(ISecurityCapabilities) && !this.ProvidesCorrelation)
            {
                return InternalDuplexBindingElement.GetSecurityCapabilities<T>(context.GetInnerProperty<ISecurityCapabilities>());
            }
            else
            {
                return context.GetInnerProperty<T>();
            }
        }

        internal override bool IsMatch(BindingElement b)
        {
            if (b == null)
                return false;
            return (b is InternalDuplexBindingElement);
        }

        public static void AddDuplexFactorySupport(BindingContext context, ref InternalDuplexBindingElement internalDuplexBindingElement)
        {
            if (context.CanBuildInnerChannelFactory<IDuplexChannel>())
                return;
            if (context.RemainingBindingElements.Find<CompositeDuplexBindingElement>() == null)
                return;

            if (context.CanBuildInnerChannelFactory<IOutputChannel>() &&
                context.CanBuildInnerChannelListener<IInputChannel>())
            {
                if (context.CanBuildInnerChannelFactory<IRequestChannel>())
                    return;
                if (context.CanBuildInnerChannelFactory<IRequestSessionChannel>())
                    return;
                if (context.CanBuildInnerChannelFactory<IOutputSessionChannel>())
                    return;
                if (context.CanBuildInnerChannelFactory<IDuplexSessionChannel>())
                    return;

                if (internalDuplexBindingElement == null)
                    internalDuplexBindingElement = new InternalDuplexBindingElement();
                context.RemainingBindingElements.Insert(0, internalDuplexBindingElement);
            }
        }

        public static void AddDuplexListenerSupport(BindingContext context, ref InternalDuplexBindingElement internalDuplexBindingElement)
        {
            if (context.CanBuildInnerChannelListener<IDuplexChannel>())
                return;
            if (context.RemainingBindingElements.Find<CompositeDuplexBindingElement>() == null)
                return;

            if (context.CanBuildInnerChannelFactory<IOutputChannel>() &&
                context.CanBuildInnerChannelListener<IInputChannel>())
            {
                if (context.CanBuildInnerChannelListener<IReplyChannel>())
                    return;
                if (context.CanBuildInnerChannelListener<IReplySessionChannel>())
                    return;
                if (context.CanBuildInnerChannelListener<IInputSessionChannel>())
                    return;
                if (context.CanBuildInnerChannelListener<IDuplexSessionChannel>())
                    return;

                if (internalDuplexBindingElement == null)
                    internalDuplexBindingElement = new InternalDuplexBindingElement();
                context.RemainingBindingElements.Insert(0, internalDuplexBindingElement);
            }
        }

        public static void AddDuplexListenerSupport(CustomBinding binding, ref InternalDuplexBindingElement internalDuplexBindingElement)
        {
            if (binding.CanBuildChannelListener<IDuplexChannel>())
                return;
            if (binding.Elements.Find<CompositeDuplexBindingElement>() == null)
                return;

            if (binding.CanBuildChannelFactory<IOutputChannel>() &&
                binding.CanBuildChannelListener<IInputChannel>())
            {
                if (binding.CanBuildChannelListener<IReplyChannel>())
                    return;
                if (binding.CanBuildChannelListener<IReplySessionChannel>())
                    return;
                if (binding.CanBuildChannelListener<IInputSessionChannel>())
                    return;
                if (binding.CanBuildChannelListener<IDuplexSessionChannel>())
                    return;

                if (internalDuplexBindingElement == null)
                    internalDuplexBindingElement = new InternalDuplexBindingElement();
                binding.Elements.Insert(0, internalDuplexBindingElement);
            }
        }
    }

    class LocalAddressProvider
    {
        EndpointAddress localAddress;
        MessageFilter filter;
        int priority;

        public LocalAddressProvider(EndpointAddress localAddress, MessageFilter filter)
        {
            if (localAddress == null)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("localAddress");
            }
            if (filter == null)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("filter");
            }
            this.localAddress = localAddress;
            this.filter = filter;

            if (localAddress.Headers.FindHeader(XD.UtilityDictionary.UniqueEndpointHeaderName.Value,
                    XD.UtilityDictionary.UniqueEndpointHeaderNamespace.Value) == null)
            {
                this.priority = Int32.MaxValue - 1;
            }
            else
            {
                this.priority = Int32.MaxValue;
            }
        }

        public EndpointAddress LocalAddress
        {
            get { return this.localAddress; }
        }

        public MessageFilter Filter
        {
            get { return this.filter; }
        }

        public int Priority
        {
            get { return this.priority; }
        }
    }
}
