
using System.IO;
using System.Text;
using System.Security;
using System.Diagnostics;
using System.Collections;
using System.Globalization;
using System.Security.Permissions;
using System.Xml.Schema;
using System.Runtime.Versioning;

using System.Threading.Tasks;

#if SILVERLIGHT
using BufferBuilder=System.Xml.BufferBuilder;
#else
using BufferBuilder = System.Text.StringBuilder;
#endif

namespace System.Xml {

    // Represents a reader that provides fast, non-cached forward only stream access to XML data. 
#if !SILVERLIGHT // This is used for displaying the state of the XmlReader in Watch/Locals windows in the Visual Studio during debugging
    [DebuggerDisplay("{debuggerDisplayProxy}")]
#endif
    public abstract partial class XmlReader : IDisposable {

        public virtual Task<string> GetValueAsync() {
            throw new NotImplementedException();
        }

        // Concatenates values of textual nodes of the current content, ignoring comments and PIs, expanding entity references, 
        // and returns the content as the most appropriate type (by default as string). Stops at start tags and end tags.
        public virtual async Task< object > ReadContentAsObjectAsync() {
            if (!CanReadContentAs()) {
                throw CreateReadContentAsException("ReadContentAsObject");
            }
            return await InternalReadContentAsStringAsync().ConfigureAwait(false);
        }

        // Concatenates values of textual nodes of the current content, ignoring comments and PIs, expanding entity references, 
        // and returns the content as a string. Stops at start tags and end tags.
        public virtual Task< string > ReadContentAsStringAsync() {
            if (!CanReadContentAs()) {
                throw CreateReadContentAsException("ReadContentAsString");
            }
            return InternalReadContentAsStringAsync();
        }

        // Concatenates values of textual nodes of the current content, ignoring comments and PIs, expanding entity references, 
        // and converts the content to the requested type. Stops at start tags and end tags.
        public virtual async Task< object > ReadContentAsAsync(Type returnType, IXmlNamespaceResolver namespaceResolver) {
            if (!CanReadContentAs()) {
                throw CreateReadContentAsException("ReadContentAs");
            }

            string strContentValue = await InternalReadContentAsStringAsync().ConfigureAwait(false);
            if (returnType == typeof(string)) {
                return strContentValue;
            }
            else {
                try {
#if SILVERLIGHT 
                    return XmlUntypedStringConverter.Instance.FromString(strContentValue, returnType, (namespaceResolver == null ? this as IXmlNamespaceResolver : namespaceResolver));
#else
                    return XmlUntypedConverter.Untyped.ChangeType(strContentValue, returnType, (namespaceResolver == null ? this as IXmlNamespaceResolver : namespaceResolver));
#endif
                }
                catch (FormatException e) {
                    throw new XmlException(Res.Xml_ReadContentAsFormatException, returnType.ToString(), e, this as IXmlLineInfo);
                }
                catch (InvalidCastException e) {
                    throw new XmlException(Res.Xml_ReadContentAsFormatException, returnType.ToString(), e, this as IXmlLineInfo);
                }
            }
        }

        // Returns the content of the current element as the most appropriate type. Moves to the node following the element's end tag.
        public virtual async Task< object > ReadElementContentAsObjectAsync() {
            if (await SetupReadElementContentAsXxxAsync("ReadElementContentAsObject").ConfigureAwait(false)) {
                object value = await ReadContentAsObjectAsync().ConfigureAwait(false);
                await FinishReadElementContentAsXxxAsync().ConfigureAwait(false);
                return value;
            }
            return string.Empty;
        }

        // Returns the content of the current element as a string. Moves to the node following the element's end tag.
        public virtual async Task< string > ReadElementContentAsStringAsync() {
            if (await SetupReadElementContentAsXxxAsync("ReadElementContentAsString").ConfigureAwait(false)) {
                string value = await ReadContentAsStringAsync().ConfigureAwait(false);
                await FinishReadElementContentAsXxxAsync().ConfigureAwait(false);
                return value;
            }
            return string.Empty;
        }

        // Returns the content of the current element as the requested type. Moves to the node following the element's end tag.
        public virtual async Task< object > ReadElementContentAsAsync(Type returnType, IXmlNamespaceResolver namespaceResolver) {
            if (await SetupReadElementContentAsXxxAsync("ReadElementContentAs").ConfigureAwait(false)) {
                object value = await ReadContentAsAsync(returnType, namespaceResolver).ConfigureAwait(false);
                await FinishReadElementContentAsXxxAsync().ConfigureAwait(false);
                return value;
            }
#if SILVERLIGHT
            return (returnType == typeof(string)) ? string.Empty : XmlUntypedStringConverter.Instance.FromString(string.Empty, returnType, namespaceResolver);
#else
            return (returnType == typeof(string)) ? string.Empty : XmlUntypedConverter.Untyped.ChangeType(string.Empty, returnType, namespaceResolver);
#endif
        }

        // Moving through the Stream
        // Reads the next node from the stream.

        public virtual Task< bool > ReadAsync() {
            throw new NotImplementedException();
        }

        // Skips to the end tag of the current element.
        public virtual Task SkipAsync() {
            if (ReadState != ReadState.Interactive) {

                return AsyncHelper.DoneTask;

            }
            return SkipSubtreeAsync();
        }

        // Returns decoded bytes of the current base64 text content. Call this methods until it returns 0 to get all the data.
        public virtual Task< int > ReadContentAsBase64Async(byte[] buffer, int index, int count) {
            throw new NotSupportedException(Res.GetString(Res.Xml_ReadBinaryContentNotSupported, "ReadContentAsBase64"));
        }

        // Returns decoded bytes of the current base64 element content. Call this methods until it returns 0 to get all the data.
        public virtual Task< int > ReadElementContentAsBase64Async(byte[] buffer, int index, int count) {
            throw new NotSupportedException(Res.GetString(Res.Xml_ReadBinaryContentNotSupported, "ReadElementContentAsBase64"));
        }

        // Returns decoded bytes of the current binhex text content. Call this methods until it returns 0 to get all the data.
        public virtual Task< int > ReadContentAsBinHexAsync(byte[] buffer, int index, int count) {
            throw new NotSupportedException(Res.GetString(Res.Xml_ReadBinaryContentNotSupported, "ReadContentAsBinHex"));
        }

        // Returns decoded bytes of the current binhex element content. Call this methods until it returns 0 to get all the data.
        public virtual Task< int > ReadElementContentAsBinHexAsync(byte[] buffer, int index, int count) {
            throw new NotSupportedException(Res.GetString(Res.Xml_ReadBinaryContentNotSupported, "ReadElementContentAsBinHex"));
        }

        // Returns a chunk of the value of the current node. Call this method in a loop to get all the data. 
        // Use this method to get a streaming access to the value of the current node.
        public virtual Task< int > ReadValueChunkAsync(char[] buffer, int index, int count) {
            throw new NotSupportedException(Res.GetString(Res.Xml_ReadValueChunkNotSupported));
        }

        // Checks whether the current node is a content (non-whitespace text, CDATA, Element, EndElement, EntityReference
        // or EndEntity) node. If the node is not a content node, then the method skips ahead to the next content node or 
        // end of file. Skips over nodes of type ProcessingInstruction, DocumentType, Comment, Whitespace and SignificantWhitespace.
        public virtual async Task< XmlNodeType > MoveToContentAsync() {
            do {
                switch (this.NodeType) {
                    case XmlNodeType.Attribute:
                        MoveToElement();
                        goto case XmlNodeType.Element;
                    case XmlNodeType.Element:
                    case XmlNodeType.EndElement:
                    case XmlNodeType.CDATA:
                    case XmlNodeType.Text:
                    case XmlNodeType.EntityReference:
                    case XmlNodeType.EndEntity:
                        return this.NodeType;
                }
            } while (await ReadAsync().ConfigureAwait(false));
            return this.NodeType;
        }

        // Returns the inner content (including markup) of an element or attribute as a string.
        public virtual async Task< string > ReadInnerXmlAsync() {
            if (ReadState != ReadState.Interactive) {
                return string.Empty;
            }
            if ((this.NodeType != XmlNodeType.Attribute) && (this.NodeType != XmlNodeType.Element)) {
                await ReadAsync().ConfigureAwait(false);
                return string.Empty;
            }

            StringWriter sw = new StringWriter(CultureInfo.InvariantCulture);
            XmlWriter xtw = CreateWriterForInnerOuterXml(sw);

            try {
                if (this.NodeType == XmlNodeType.Attribute) {
#if !SILVERLIGHT // Removing dependency on XmlTextWriter
                    ((XmlTextWriter)xtw).QuoteChar = this.QuoteChar;
#endif
                    WriteAttributeValue(xtw);
                }
                if (this.NodeType == XmlNodeType.Element) {
                    await this.WriteNodeAsync(xtw, false).ConfigureAwait(false);
                }
            }
            finally {
                xtw.Close();
            }
            return sw.ToString();
        }

        // Writes the content (inner XML) of the current node into the provided XmlWriter.
        private async Task WriteNodeAsync(XmlWriter xtw, bool defattr) {
#if !SILVERLIGHT
            Debug.Assert(xtw is XmlTextWriter);
#endif
            int d = this.NodeType == XmlNodeType.None ? -1 : this.Depth;
            while (await this.ReadAsync().ConfigureAwait(false) && (d < this.Depth)) {
                switch (this.NodeType) {
                    case XmlNodeType.Element:
                        xtw.WriteStartElement(this.Prefix, this.LocalName, this.NamespaceURI);
#if !SILVERLIGHT // Removing dependency on XmlTextWriter
                        ((XmlTextWriter)xtw).QuoteChar = this.QuoteChar;
#endif
                        xtw.WriteAttributes(this, defattr);
                        if (this.IsEmptyElement) {
                            xtw.WriteEndElement();
                        }
                        break;
                    case XmlNodeType.Text:
                        xtw.WriteString(await this.GetValueAsync().ConfigureAwait(false));
                        break;
                    case XmlNodeType.Whitespace:
                    case XmlNodeType.SignificantWhitespace:
                        xtw.WriteWhitespace(await this.GetValueAsync().ConfigureAwait(false));
                        break;
                    case XmlNodeType.CDATA:
                        xtw.WriteCData(this.Value);
                        break;
                    case XmlNodeType.EntityReference:
                        xtw.WriteEntityRef(this.Name);
                        break;
                    case XmlNodeType.XmlDeclaration:
                    case XmlNodeType.ProcessingInstruction:
                        xtw.WriteProcessingInstruction(this.Name, this.Value);
                        break;
                    case XmlNodeType.DocumentType:
                        xtw.WriteDocType(this.Name, this.GetAttribute("PUBLIC"), this.GetAttribute("SYSTEM"), this.Value);
                        break;
                    case XmlNodeType.Comment:
                        xtw.WriteComment(this.Value);
                        break;
                    case XmlNodeType.EndElement:
                        xtw.WriteFullEndElement();
                        break;
                }
            }
            if (d == this.Depth && this.NodeType == XmlNodeType.EndElement) {
                await ReadAsync().ConfigureAwait(false);
            }
        }

        // Returns the current element and its descendants or an attribute as a string.
        public virtual async Task< string > ReadOuterXmlAsync() {
            if (ReadState != ReadState.Interactive) {
                return string.Empty;
            }
            if ((this.NodeType != XmlNodeType.Attribute) && (this.NodeType != XmlNodeType.Element)) {
                await ReadAsync().ConfigureAwait(false);
                return string.Empty;
            }

            StringWriter sw = new StringWriter(CultureInfo.InvariantCulture);
            XmlWriter xtw = CreateWriterForInnerOuterXml(sw);

            try {
                if (this.NodeType == XmlNodeType.Attribute) {
                    xtw.WriteStartAttribute(this.Prefix, this.LocalName, this.NamespaceURI);
                    WriteAttributeValue(xtw);
                    xtw.WriteEndAttribute();
                }
                else {
                    xtw.WriteNode(this, false);
                }
            }
            finally {
                xtw.Close();
            }
            return sw.ToString();
        }

        //
        // Private methods
        //
        //SkipSubTree is called whenever validation of the skipped subtree is required on a reader with XsdValidation
        private async Task< bool > SkipSubtreeAsync() {
            MoveToElement();
            if (NodeType == XmlNodeType.Element && !IsEmptyElement) {
                int depth = Depth;

                while (await ReadAsync().ConfigureAwait(false) && depth < Depth) {
                    // Nothing, just read on
                }

                // consume end tag
                if (NodeType == XmlNodeType.EndElement)
                    return await ReadAsync().ConfigureAwait(false);
            }
            else {
                return await ReadAsync().ConfigureAwait(false);
            }

            return false;
        }

        internal async Task< string > InternalReadContentAsStringAsync() {
            string value = string.Empty;
            BufferBuilder sb = null;
            do {
                switch (this.NodeType) {
                    case XmlNodeType.Attribute:
                        return this.Value;
                    case XmlNodeType.Text:
                    case XmlNodeType.Whitespace:
                    case XmlNodeType.SignificantWhitespace:
                    case XmlNodeType.CDATA:
                        // merge text content
                        if (value.Length == 0) {
                            value = await this.GetValueAsync().ConfigureAwait(false);
                        }
                        else {
                            if (sb == null) {
                                sb = new BufferBuilder();
                                sb.Append(value);
                            }
                            sb.Append(await this.GetValueAsync().ConfigureAwait(false));
                        }
                        break;
                    case XmlNodeType.ProcessingInstruction:
                    case XmlNodeType.Comment:
                    case XmlNodeType.EndEntity:
                        // skip comments, pis and end entity nodes
                        break;
                    case XmlNodeType.EntityReference:
                        if (this.CanResolveEntity) {
                            this.ResolveEntity();
                            break;
                        }
                        goto default;
                    case XmlNodeType.EndElement:
                    default:
                        goto ReturnContent;
                }
            } while ((this.AttributeCount != 0) ? this.ReadAttributeValue() : await this.ReadAsync().ConfigureAwait(false));

        ReturnContent:
            return (sb == null) ? value : sb.ToString();
        }

        private async Task< bool > SetupReadElementContentAsXxxAsync(string methodName) {
            if (this.NodeType != XmlNodeType.Element) {
                throw CreateReadElementContentAsException(methodName);
            }

            bool isEmptyElement = this.IsEmptyElement;

            // move to content or beyond the empty element
            await this.ReadAsync().ConfigureAwait(false);

            if (isEmptyElement) {
                return false;
            }

            XmlNodeType nodeType = this.NodeType;
            if (nodeType == XmlNodeType.EndElement) {
                await this.ReadAsync().ConfigureAwait(false);
                return false;
            }
            else if (nodeType == XmlNodeType.Element) {
                throw new XmlException(Res.Xml_MixedReadElementContentAs, string.Empty, this as IXmlLineInfo);
            }
            return true;
        }

        private Task FinishReadElementContentAsXxxAsync() {
            if (this.NodeType != XmlNodeType.EndElement) {
                throw new XmlException(Res.Xml_InvalidNodeType, this.NodeType.ToString());
            }
            return this.ReadAsync();
        }

    }
}

