// 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.Runtime;
using System.Runtime.Serialization;
using System.ServiceModel.Dispatcher;
using System.Text;
using System.Xml;

namespace System.ServiceModel.Channels
{
    public abstract class AddressHeader
    {
        private ParameterHeader _header;

        protected AddressHeader()
        {
        }

        internal bool IsReferenceProperty
        {
            get
            {
                BufferedAddressHeader bah = this as BufferedAddressHeader;
                return bah != null && bah.IsReferencePropertyHeader;
            }
        }

        public abstract string Name { get; }
        public abstract string Namespace { get; }

        public static AddressHeader CreateAddressHeader(object value)
        {
            Type type = GetObjectType(value);
            return CreateAddressHeader(value, DataContractSerializerDefaults.CreateSerializer(type, int.MaxValue/*maxItems*/));
        }

        public static AddressHeader CreateAddressHeader(object value, XmlObjectSerializer serializer)
        {
            if (serializer == null)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentNullException("serializer"));
            return new XmlObjectSerializerAddressHeader(value, serializer);
        }

        public static AddressHeader CreateAddressHeader(string name, string ns, object value)
        {
            return CreateAddressHeader(name, ns, value, DataContractSerializerDefaults.CreateSerializer(GetObjectType(value), name, ns, int.MaxValue/*maxItems*/));
        }

        internal static AddressHeader CreateAddressHeader(XmlDictionaryString name, XmlDictionaryString ns, object value)
        {
            return new DictionaryAddressHeader(name, ns, value);
        }

        public static AddressHeader CreateAddressHeader(string name, string ns, object value, XmlObjectSerializer serializer)
        {
            if (serializer == null)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentNullException("serializer"));
            return new XmlObjectSerializerAddressHeader(name, ns, value, serializer);
        }

        private static Type GetObjectType(object value)
        {
            return (value == null) ? typeof(object) : value.GetType();
        }

        public override bool Equals(object obj)
        {
            AddressHeader hdr = obj as AddressHeader;
            if (hdr == null)
                return false;

            StringBuilder builder = new StringBuilder();
            string hdr1 = GetComparableForm(builder);

            builder.Remove(0, builder.Length);
            string hdr2 = hdr.GetComparableForm(builder);

            if (hdr1.Length != hdr2.Length)
                return false;

            if (string.CompareOrdinal(hdr1, hdr2) != 0)
                return false;

            return true;
        }

        internal string GetComparableForm()
        {
            return GetComparableForm(new StringBuilder());
        }

        internal string GetComparableForm(StringBuilder builder)
        {
            return EndpointAddressProcessor.GetComparableForm(builder, GetComparableReader());
        }

        public override int GetHashCode()
        {
            return GetComparableForm().GetHashCode();
        }

        public T GetValue<T>()
        {
            return GetValue<T>(DataContractSerializerDefaults.CreateSerializer(typeof(T), this.Name, this.Namespace, int.MaxValue/*maxItems*/));
        }

        public T GetValue<T>(XmlObjectSerializer serializer)
        {
            if (serializer == null)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentNullException("serializer"));
            using (XmlDictionaryReader reader = GetAddressHeaderReader())
            {
                if (serializer.IsStartObject(reader))
                    return (T)serializer.ReadObject(reader);
                else
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.Format(SR.ExpectedElementMissing, Name, Namespace)));
            }
        }

        public virtual XmlDictionaryReader GetAddressHeaderReader()
        {
            XmlBuffer buffer = new XmlBuffer(int.MaxValue);
            XmlDictionaryWriter writer = buffer.OpenSection(XmlDictionaryReaderQuotas.Max);
            WriteAddressHeader(writer);
            buffer.CloseSection();
            buffer.Close();
            return buffer.GetReader(0);
        }

        private XmlDictionaryReader GetComparableReader()
        {
            throw ExceptionHelper.PlatformNotSupported();
        }

        protected virtual void OnWriteStartAddressHeader(XmlDictionaryWriter writer)
        {
            writer.WriteStartElement(Name, Namespace);
        }

        protected abstract void OnWriteAddressHeaderContents(XmlDictionaryWriter writer);

        public MessageHeader ToMessageHeader()
        {
            if (_header == null)
                _header = new ParameterHeader(this);
            return _header;
        }

        public void WriteAddressHeader(XmlWriter writer)
        {
            WriteAddressHeader(XmlDictionaryWriter.CreateDictionaryWriter(writer));
        }

        public void WriteAddressHeader(XmlDictionaryWriter writer)
        {
            if (writer == null)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentNullException("writer"));
            WriteStartAddressHeader(writer);
            WriteAddressHeaderContents(writer);
            writer.WriteEndElement();
        }

        public void WriteStartAddressHeader(XmlDictionaryWriter writer)
        {
            if (writer == null)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentNullException("writer"));
            OnWriteStartAddressHeader(writer);
        }

        public void WriteAddressHeaderContents(XmlDictionaryWriter writer)
        {
            if (writer == null)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentNullException("writer"));
            OnWriteAddressHeaderContents(writer);
        }

        internal class ParameterHeader : MessageHeader
        {
            private AddressHeader _parameter;

            public override bool IsReferenceParameter
            {
                get { return true; }
            }

            public override string Name
            {
                get { return _parameter.Name; }
            }

            public override string Namespace
            {
                get { return _parameter.Namespace; }
            }

            public ParameterHeader(AddressHeader parameter)
            {
                _parameter = parameter;
            }

            protected override void OnWriteStartHeader(XmlDictionaryWriter writer, MessageVersion messageVersion)
            {
                if (messageVersion == null)
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentNullException("messageVersion"));

                WriteStartHeader(writer, _parameter, messageVersion.Addressing);
            }

            protected override void OnWriteHeaderContents(XmlDictionaryWriter writer, MessageVersion messageVersion)
            {
                WriteHeaderContents(writer, _parameter);
            }

            internal static void WriteStartHeader(XmlDictionaryWriter writer, AddressHeader parameter, AddressingVersion addressingVersion)
            {
                parameter.WriteStartAddressHeader(writer);
                if (addressingVersion == AddressingVersion.WSAddressing10)
                {
                    writer.WriteAttributeString(XD.AddressingDictionary.IsReferenceParameter, XD.Addressing10Dictionary.Namespace, "true");
                }
            }

            internal static void WriteHeaderContents(XmlDictionaryWriter writer, AddressHeader parameter)
            {
                parameter.WriteAddressHeaderContents(writer);
            }
        }

        internal class XmlObjectSerializerAddressHeader : AddressHeader
        {
            private XmlObjectSerializer _serializer;
            private object _objectToSerialize;
            private string _name;
            private string _ns;

            public XmlObjectSerializerAddressHeader(object objectToSerialize, XmlObjectSerializer serializer)
            {
                _serializer = serializer;
                _objectToSerialize = objectToSerialize;

                throw ExceptionHelper.PlatformNotSupported();
            }

            public XmlObjectSerializerAddressHeader(string name, string ns, object objectToSerialize, XmlObjectSerializer serializer)
            {
                if ((null == name) || (name.Length == 0))
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentNullException("name"));
                }

                _serializer = serializer;
                _objectToSerialize = objectToSerialize;
                _name = name;
                _ns = ns;
            }

            public override string Name
            {
                get { return _name; }
            }

            public override string Namespace
            {
                get { return _ns; }
            }

            private object ThisLock
            {
                get { return this; }
            }

            protected override void OnWriteAddressHeaderContents(XmlDictionaryWriter writer)
            {
                lock (ThisLock)
                {
                    _serializer.WriteObjectContent(writer, _objectToSerialize);
                }
            }
        }

        internal class DictionaryAddressHeader : XmlObjectSerializerAddressHeader
        {
            private XmlDictionaryString _name;
            private XmlDictionaryString _ns;

            public DictionaryAddressHeader(XmlDictionaryString name, XmlDictionaryString ns, object value)
                : base(name.Value, ns.Value, value, DataContractSerializerDefaults.CreateSerializer(GetObjectType(value), name, ns, int.MaxValue/*maxItems*/))
            {
                _name = name;
                _ns = ns;
            }

            protected override void OnWriteStartAddressHeader(XmlDictionaryWriter writer)
            {
                writer.WriteStartElement(_name, _ns);
            }
        }
    }

    internal class BufferedAddressHeader : AddressHeader
    {
        private string _name;
        private string _ns;
        private XmlBuffer _buffer;
        private bool _isReferenceProperty;

        public BufferedAddressHeader(XmlDictionaryReader reader)
        {
            _buffer = new XmlBuffer(int.MaxValue);
            XmlDictionaryWriter writer = _buffer.OpenSection(reader.Quotas);
            Fx.Assert(reader.NodeType == XmlNodeType.Element, "");
            _name = reader.LocalName;
            _ns = reader.NamespaceURI;
            Fx.Assert(_name != null, "");
            Fx.Assert(_ns != null, "");
            writer.WriteNode(reader, false);
            _buffer.CloseSection();
            _buffer.Close();
            _isReferenceProperty = false;
        }

        public BufferedAddressHeader(XmlDictionaryReader reader, bool isReferenceProperty)
            : this(reader)
        {
            _isReferenceProperty = isReferenceProperty;
        }

        public bool IsReferencePropertyHeader { get { return _isReferenceProperty; } }

        public override string Name
        {
            get { return _name; }
        }

        public override string Namespace
        {
            get { return _ns; }
        }

        public override XmlDictionaryReader GetAddressHeaderReader()
        {
            return _buffer.GetReader(0);
        }

        protected override void OnWriteStartAddressHeader(XmlDictionaryWriter writer)
        {
            XmlDictionaryReader reader = GetAddressHeaderReader();
            writer.WriteStartElement(reader.Prefix, reader.LocalName, reader.NamespaceURI);
            writer.WriteAttributes(reader, false);
            reader.Dispose();
        }

        protected override void OnWriteAddressHeaderContents(XmlDictionaryWriter writer)
        {
            XmlDictionaryReader reader = GetAddressHeaderReader();
            reader.ReadStartElement();
            while (reader.NodeType != XmlNodeType.EndElement)
                writer.WriteNode(reader, false);
            reader.ReadEndElement();
            reader.Dispose();
        }
    }
}
