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

namespace System.ServiceModel
{
    using System.ServiceModel.Administration;
    using System.ServiceModel.Channels;
    using System.ServiceModel.Dispatcher;
    using System.ServiceModel.Description;
    using System.ServiceModel.Configuration;
    using System.Runtime.Serialization;
    using System.Collections.ObjectModel;
    using System.Collections.Generic;
    using System.Threading;
    using System.Transactions;
    using System.Runtime.CompilerServices;
    using System.Globalization;

    [AttributeUsage(ServiceModelAttributeTargets.CallbackBehavior)]
    public sealed class CallbackBehaviorAttribute : Attribute, IEndpointBehavior
    {
        ConcurrencyMode concurrencyMode = ConcurrencyMode.Single;
        bool includeExceptionDetailInFaults = false;
        bool validateMustUnderstand = true;
        bool ignoreExtensionDataObject = DataContractSerializerDefaults.IgnoreExtensionDataObject;
        int maxItemsInObjectGraph = DataContractSerializerDefaults.MaxItemsInObjectGraph;
        bool automaticSessionShutdown = true;
        bool useSynchronizationContext = true;
        internal static IsolationLevel DefaultIsolationLevel = IsolationLevel.Unspecified;
        IsolationLevel transactionIsolationLevel = DefaultIsolationLevel;
        bool isolationLevelSet = false;
        TimeSpan transactionTimeout = TimeSpan.Zero;
        string transactionTimeoutString;
        bool transactionTimeoutSet = false;

        public bool AutomaticSessionShutdown
        {
            get { return this.automaticSessionShutdown; }
            set { this.automaticSessionShutdown = value; }
        }

        public IsolationLevel TransactionIsolationLevel
        {
            get { return this.transactionIsolationLevel; }
            set
            {
                switch (value)
                {
                    case IsolationLevel.Serializable:
                    case IsolationLevel.RepeatableRead:
                    case IsolationLevel.ReadCommitted:
                    case IsolationLevel.ReadUncommitted:
                    case IsolationLevel.Unspecified:
                    case IsolationLevel.Chaos:
                    case IsolationLevel.Snapshot:
                        break;

                    default:
                        throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("value"));
                }

                this.transactionIsolationLevel = value;
                isolationLevelSet = true;
            }
        }

        internal bool IsolationLevelSet
        {
            get { return this.isolationLevelSet; }
        }

        public bool IncludeExceptionDetailInFaults
        {
            get { return this.includeExceptionDetailInFaults; }
            set { this.includeExceptionDetailInFaults = value; }
        }

        public ConcurrencyMode ConcurrencyMode
        {
            get { return this.concurrencyMode; }
            set
            {
                if (!ConcurrencyModeHelper.IsDefined(value))
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("value"));
                }

                this.concurrencyMode = value;
            }
        }

        public string TransactionTimeout
        {
            get { return transactionTimeoutString; }
            set
            {
                if (value == null)
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentNullException("value"));
                }

                try
                {
                    TimeSpan timeout = TimeSpan.Parse(value, CultureInfo.InvariantCulture);

                    if (timeout < TimeSpan.Zero)
                    {
                        string message = SR.GetString(SR.SFxTimeoutOutOfRange0);
                        throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("value", value, message));
                    }

                    this.transactionTimeout = timeout;
                    this.transactionTimeoutString = value;
                    this.transactionTimeoutSet = true;
                }
                catch (FormatException e)
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentException(SR.GetString(SR.SFxTimeoutInvalidStringFormat), "value", e));
                }
                catch (OverflowException)
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("value"));
                }
            }
        }

        internal bool TransactionTimeoutSet
        {
            get { return this.transactionTimeoutSet; }
        }

        public bool UseSynchronizationContext
        {
            get { return this.useSynchronizationContext; }
            set { this.useSynchronizationContext = value; }
        }

        public bool ValidateMustUnderstand
        {
            get { return validateMustUnderstand; }
            set { validateMustUnderstand = value; }
        }

        public bool IgnoreExtensionDataObject
        {
            get { return ignoreExtensionDataObject; }
            set { ignoreExtensionDataObject = value; }
        }

        public int MaxItemsInObjectGraph
        {
            get { return maxItemsInObjectGraph; }
            set { maxItemsInObjectGraph = value; }
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        void SetIsolationLevel(ChannelDispatcher channelDispatcher)
        {
            if (channelDispatcher == null)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("channelDispatcher");
            }

            channelDispatcher.TransactionIsolationLevel = this.transactionIsolationLevel;
        }

        void IEndpointBehavior.Validate(ServiceEndpoint serviceEndpoint)
        {
        }

        void IEndpointBehavior.AddBindingParameters(ServiceEndpoint serviceEndpoint, BindingParameterCollection parameters)
        {
        }

        void IEndpointBehavior.ApplyClientBehavior(ServiceEndpoint serviceEndpoint, ClientRuntime clientRuntime)
        {
            if (!serviceEndpoint.Contract.IsDuplex())
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(
                    SR.SFxCallbackBehaviorAttributeOnlyOnDuplex, serviceEndpoint.Contract.Name)));
            }
            DispatchRuntime dispatchRuntime = clientRuntime.DispatchRuntime;
            dispatchRuntime.ValidateMustUnderstand = validateMustUnderstand;
            dispatchRuntime.ConcurrencyMode = this.concurrencyMode;
            dispatchRuntime.ChannelDispatcher.IncludeExceptionDetailInFaults = this.includeExceptionDetailInFaults;
            dispatchRuntime.AutomaticInputSessionShutdown = this.automaticSessionShutdown;
            if (!this.useSynchronizationContext)
            {
                dispatchRuntime.SynchronizationContext = null;
            }

            dispatchRuntime.ChannelDispatcher.TransactionTimeout = transactionTimeout;

            if (isolationLevelSet)
            {
                SetIsolationLevel(dispatchRuntime.ChannelDispatcher);
            }

            DataContractSerializerServiceBehavior.ApplySerializationSettings(serviceEndpoint, this.ignoreExtensionDataObject, this.maxItemsInObjectGraph);
        }

        void IEndpointBehavior.ApplyDispatchBehavior(ServiceEndpoint serviceEndpoint, EndpointDispatcher endpointDispatcher)
        {
            throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
                SR.GetString(SR.SFXEndpointBehaviorUsedOnWrongSide, typeof(CallbackBehaviorAttribute).Name)));
        }
    }
}