// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.


using System.Collections.Generic;
using System.ComponentModel;
using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Net;
using System.Runtime;
using System.ServiceModel.Channels;
using System.ServiceModel.Security;
using System.Xml;

namespace System.ServiceModel.Dispatcher
{
    [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Compat", Justification = "Compat is an accepted abbreviation")]
    [EditorBrowsable(EditorBrowsableState.Never)]
    public class ClientRuntimeCompatBase
    {
        internal ClientRuntimeCompatBase() { }

        [EditorBrowsable(EditorBrowsableState.Never)]
        [Obsolete("This API supports the .NET Framework infrastructure and is not intended to be used directly from your code.", true)]
        public IList<IClientMessageInspector> MessageInspectors
        {
            get
            {
                return this.messageInspectors;
            }
        }

        [EditorBrowsable(EditorBrowsableState.Never)]
        [Obsolete("This API supports the .NET Framework infrastructure and is not intended to be used directly from your code.", true)]
        public KeyedCollection<string, ClientOperation> Operations
        {
            get
            {
                return this.compatOperations;
            }
        }
        internal SynchronizedCollection<IClientMessageInspector> messageInspectors;
        internal SynchronizedKeyedCollection<string, ClientOperation> operations;
        internal KeyedCollection<string, ClientOperation> compatOperations;
    }

    public sealed class ClientRuntime : ClientRuntimeCompatBase
    {
        private bool _addTransactionFlowProperties = true;
        private Type _callbackProxyType;
        private ProxyBehaviorCollection<IChannelInitializer> _channelInitializers;
        private string _contractName;
        private string _contractNamespace;
        private Type _contractProxyType;
        private DispatchRuntime _dispatchRuntime;
        private IdentityVerifier _identityVerifier;
        private ProxyBehaviorCollection<IInteractiveChannelInitializer> _interactiveChannelInitializers;

        private IClientOperationSelector _operationSelector;
        private ImmutableClientRuntime _runtime;
        private ClientOperation _unhandled;
        private bool _useSynchronizationContext = true;
        private Uri _via;
        private SharedRuntimeState _shared;
        private int _maxFaultSize;
        private bool _messageVersionNoneFaultsEnabled;

        internal ClientRuntime(DispatchRuntime dispatchRuntime, SharedRuntimeState shared)
            : this(dispatchRuntime.EndpointDispatcher.ContractName,
                   dispatchRuntime.EndpointDispatcher.ContractNamespace,
                   shared)
        {
            if (dispatchRuntime == null)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("dispatchRuntime");

            _dispatchRuntime = dispatchRuntime;
            _shared = shared;

            Fx.Assert(shared.IsOnServer, "Server constructor called on client?");
        }

        internal ClientRuntime(string contractName, string contractNamespace)
            : this(contractName, contractNamespace, new SharedRuntimeState(false))
        {
            Fx.Assert(!_shared.IsOnServer, "Client constructor called on server?");
        }

        private ClientRuntime(string contractName, string contractNamespace, SharedRuntimeState shared)
        {
            _contractName = contractName;
            _contractNamespace = contractNamespace;
            _shared = shared;

            OperationCollection operations = new OperationCollection(this);
            this.operations = operations;
            this.compatOperations = new OperationCollectionWrapper(operations);
            _channelInitializers = new ProxyBehaviorCollection<IChannelInitializer>(this);
            this.messageInspectors = new ProxyBehaviorCollection<IClientMessageInspector>(this);
            _interactiveChannelInitializers = new ProxyBehaviorCollection<IInteractiveChannelInitializer>(this);

            _unhandled = new ClientOperation(this, "*", MessageHeaders.WildcardAction, MessageHeaders.WildcardAction);
            _unhandled.InternalFormatter = new MessageOperationFormatter();
            _maxFaultSize = TransportDefaults.MaxFaultSize;
        }

        internal bool AddTransactionFlowProperties
        {
            get { return _addTransactionFlowProperties; }
            set
            {
                lock (this.ThisLock)
                {
                    this.InvalidateRuntime();
                    _addTransactionFlowProperties = value;
                }
            }
        }

        public Type CallbackClientType
        {
            get { return _callbackProxyType; }
            set
            {
                lock (this.ThisLock)
                {
                    this.InvalidateRuntime();
                    _callbackProxyType = value;
                }
            }
        }

        public SynchronizedCollection<IChannelInitializer> ChannelInitializers
        {
            get { return _channelInitializers; }
        }

        public string ContractName
        {
            get { return _contractName; }
        }

        public string ContractNamespace
        {
            get { return _contractNamespace; }
        }

        public Type ContractClientType
        {
            get { return _contractProxyType; }
            set
            {
                lock (this.ThisLock)
                {
                    this.InvalidateRuntime();
                    _contractProxyType = value;
                }
            }
        }

        internal IdentityVerifier IdentityVerifier
        {
            get
            {
                if (_identityVerifier == null)
                {
                    _identityVerifier = IdentityVerifier.CreateDefault();
                }

                return _identityVerifier;
            }
            set
            {
                if (value == null)
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("value");
                }
                this.InvalidateRuntime();

                _identityVerifier = value;
            }
        }

        public Uri Via
        {
            get { return _via; }
            set
            {
                lock (this.ThisLock)
                {
                    this.InvalidateRuntime();
                    _via = value;
                }
            }
        }

        public bool ValidateMustUnderstand
        {
            get { return _shared.ValidateMustUnderstand; }
            set
            {
                lock (this.ThisLock)
                {
                    this.InvalidateRuntime();
                    _shared.ValidateMustUnderstand = value;
                }
            }
        }

        public bool MessageVersionNoneFaultsEnabled
        {
            get
            {
                return _messageVersionNoneFaultsEnabled;
            }
            set
            {
                this.InvalidateRuntime();
                _messageVersionNoneFaultsEnabled = value;
            }
        }

        public DispatchRuntime DispatchRuntime
        {
            get { return _dispatchRuntime; }
        }

        public DispatchRuntime CallbackDispatchRuntime
        {
            get
            {
                if (_dispatchRuntime == null)
                    _dispatchRuntime = new DispatchRuntime(this, _shared);

                return _dispatchRuntime;
            }
        }

        internal bool EnableFaults
        {
            get
            {
                if (this.IsOnServer)
                {
                    return _dispatchRuntime.EnableFaults;
                }
                else
                {
                    return _shared.EnableFaults;
                }
            }
            set
            {
                lock (this.ThisLock)
                {
                    if (this.IsOnServer)
                    {
                        string text = SR.SFxSetEnableFaultsOnChannelDispatcher0;
                        throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(text));
                    }
                    else
                    {
                        this.InvalidateRuntime();
                        _shared.EnableFaults = value;
                    }
                }
            }
        }

        public SynchronizedCollection<IInteractiveChannelInitializer> InteractiveChannelInitializers
        {
            get { return _interactiveChannelInitializers; }
        }

        public int MaxFaultSize
        {
            get
            {
                return _maxFaultSize;
            }
            set
            {
                this.InvalidateRuntime();
                _maxFaultSize = value;
            }
        }

        internal bool IsOnServer
        {
            get { return _shared.IsOnServer; }
        }

        public bool ManualAddressing
        {
            get
            {
                if (this.IsOnServer)
                {
                    return _dispatchRuntime.ManualAddressing;
                }
                else
                {
                    return _shared.ManualAddressing;
                }
            }
            set
            {
                lock (this.ThisLock)
                {
                    if (this.IsOnServer)
                    {
                        string text = SR.SFxSetManualAddressingOnChannelDispatcher0;
                        throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(text));
                    }
                    else
                    {
                        this.InvalidateRuntime();
                        _shared.ManualAddressing = value;
                    }
                }
            }
        }

        internal int MaxParameterInspectors
        {
            get
            {
                lock (this.ThisLock)
                {
                    int max = 0;

                    for (int i = 0; i < this.operations.Count; i++)
                        max = System.Math.Max(max, this.operations[i].ParameterInspectors.Count);

                    return max;
                }
            }
        }

        public ICollection<IClientMessageInspector> ClientMessageInspectors
        {
            get { return this.MessageInspectors; }
        }

        [EditorBrowsable(EditorBrowsableState.Never)]
        public new SynchronizedCollection<IClientMessageInspector> MessageInspectors
        {
            get { return this.messageInspectors; }
        }

        public ICollection<ClientOperation> ClientOperations
        {
            get { return this.Operations; }
        }

        [EditorBrowsable(EditorBrowsableState.Never)]
        public new SynchronizedKeyedCollection<string, ClientOperation> Operations
        {
            get { return this.operations; }
        }

        public IClientOperationSelector OperationSelector
        {
            get { return _operationSelector; }
            set
            {
                lock (this.ThisLock)
                {
                    this.InvalidateRuntime();
                    _operationSelector = value;
                }
            }
        }

        internal object ThisLock
        {
            get { return _shared; }
        }

        public ClientOperation UnhandledClientOperation
        {
            get { return _unhandled; }
        }

        internal bool UseSynchronizationContext
        {
            get { return _useSynchronizationContext; }
            set
            {
                lock (this.ThisLock)
                {
                    this.InvalidateRuntime();
                    _useSynchronizationContext = value;
                }
            }
        }

        internal T[] GetArray<T>(SynchronizedCollection<T> collection)
        {
            lock (collection.SyncRoot)
            {
                if (collection.Count == 0)
                {
                    return Array.Empty<T>();
                }
                else
                {
                    T[] array = new T[collection.Count];
                    collection.CopyTo(array, 0);
                    return array;
                }
            }
        }

        internal ImmutableClientRuntime GetRuntime()
        {
            lock (this.ThisLock)
            {
                if (_runtime == null)
                    _runtime = new ImmutableClientRuntime(this);

                return _runtime;
            }
        }

        internal void InvalidateRuntime()
        {
            lock (this.ThisLock)
            {
                _shared.ThrowIfImmutable();
                _runtime = null;
            }
        }

        internal void LockDownProperties()
        {
            _shared.LockDownProperties();
        }

        internal SynchronizedCollection<T> NewBehaviorCollection<T>()
        {
            return new ProxyBehaviorCollection<T>(this);
        }

        internal bool IsFault(ref Message reply)
        {
            if (reply == null)
            {
                return false;
            }
            if (reply.IsFault)
            {
                return true;
            }
            if (this.MessageVersionNoneFaultsEnabled && IsMessageVersionNoneFault(ref reply, this.MaxFaultSize))
            {
                return true;
            }

            return false;
        }

        internal static bool IsMessageVersionNoneFault(ref Message message, int maxFaultSize)
        {
            if (message.Version != MessageVersion.None || message.IsEmpty)
            {
                return false;
            }
            HttpResponseMessageProperty prop = message.Properties[HttpResponseMessageProperty.Name] as HttpResponseMessageProperty;
            if (prop == null || prop.StatusCode != HttpStatusCode.InternalServerError)
            {
                return false;
            }
            using (MessageBuffer buffer = message.CreateBufferedCopy(maxFaultSize))
            {
                message.Close();
                message = buffer.CreateMessage();
                using (Message copy = buffer.CreateMessage())
                {
                    using (XmlDictionaryReader reader = copy.GetReaderAtBodyContents())
                    {
                        return reader.IsStartElement(XD.MessageDictionary.Fault, MessageVersion.None.Envelope.DictionaryNamespace);
                    }
                }
            }
        }

        internal class ProxyBehaviorCollection<T> : SynchronizedCollection<T>
        {
            private ClientRuntime _outer;

            internal ProxyBehaviorCollection(ClientRuntime outer)
                : base(outer.ThisLock)
            {
                _outer = outer;
            }

            protected override void ClearItems()
            {
                _outer.InvalidateRuntime();
                base.ClearItems();
            }

            protected override void InsertItem(int index, T item)
            {
                if (item == null)
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("item");
                }

                _outer.InvalidateRuntime();
                base.InsertItem(index, item);
            }

            protected override void RemoveItem(int index)
            {
                _outer.InvalidateRuntime();
                base.RemoveItem(index);
            }

            protected override void SetItem(int index, T item)
            {
                if (item == null)
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("item");
                }

                _outer.InvalidateRuntime();
                base.SetItem(index, item);
            }
        }

        internal class OperationCollection : SynchronizedKeyedCollection<string, ClientOperation>
        {
            private ClientRuntime _outer;

            internal OperationCollection(ClientRuntime outer)
                : base(outer.ThisLock)
            {
                _outer = outer;
            }

            protected override void ClearItems()
            {
                _outer.InvalidateRuntime();
                base.ClearItems();
            }

            protected override string GetKeyForItem(ClientOperation item)
            {
                return item.Name;
            }

            protected override void InsertItem(int index, ClientOperation item)
            {
                if (item == null)
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("item");
                if (item.Parent != _outer)
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument(SR.SFxMismatchedOperationParent);

                _outer.InvalidateRuntime();
                base.InsertItem(index, item);
            }

            protected override void RemoveItem(int index)
            {
                _outer.InvalidateRuntime();
                base.RemoveItem(index);
            }

            protected override void SetItem(int index, ClientOperation item)
            {
                if (item == null)
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("item");
                if (item.Parent != _outer)
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument(SR.SFxMismatchedOperationParent);

                _outer.InvalidateRuntime();
                base.SetItem(index, item);
            }

            internal void InternalClearItems() { this.ClearItems(); }
            internal string InternalGetKeyForItem(ClientOperation item) { return this.GetKeyForItem(item); }
            internal void InternalInsertItem(int index, ClientOperation item) { this.InsertItem(index, item); }
            internal void InternalRemoveItem(int index) { this.RemoveItem(index); }
            internal void InternalSetItem(int index, ClientOperation item) { this.SetItem(index, item); }
        }


        internal class OperationCollectionWrapper : KeyedCollection<string, ClientOperation>
        {
            private OperationCollection _inner;
            internal OperationCollectionWrapper(OperationCollection inner) { _inner = inner; }
            protected override void ClearItems() { _inner.InternalClearItems(); }
            protected override string GetKeyForItem(ClientOperation item) { return _inner.InternalGetKeyForItem(item); }
            protected override void InsertItem(int index, ClientOperation item) { _inner.InternalInsertItem(index, item); }
            protected override void RemoveItem(int index) { _inner.InternalRemoveItem(index); }
            protected override void SetItem(int index, ClientOperation item) { _inner.InternalSetItem(index, item); }
        }
    }
}
