//------------------------------------------------------------------------------
// <copyright file="XmlWellFormedWriter.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
// <owner current="true" primary="true">[....]</owner>
//------------------------------------------------------------------------------

using System;
using System.IO;
using System.Text;
using System.Xml;
using System.Diagnostics;
using System.Collections;
using System.Globalization;
using System.Collections.Generic;

// OpenIssue : is it better to cache the current namespace decls for each elem
//  as the current code does, or should it just always walk the namespace stack?

namespace System.Xml {

    internal partial class XmlWellFormedWriter : XmlWriter {
        //
        // Private types used by the XmlWellFormedWriter are defined in XmlWellFormedWriterHelpers.cs
        //

        //
        // Fields
        //
        // underlying writer
        XmlWriter writer;
        XmlRawWriter rawWriter;  // writer as XmlRawWriter
        IXmlNamespaceResolver predefinedNamespaces; // writer as IXmlNamespaceResolver

        // namespace management
        Namespace[] nsStack;
        int nsTop;
        Dictionary<string, int> nsHashtable;
        bool useNsHashtable;

        // element scoping
        ElementScope[] elemScopeStack;
        int elemTop;

        // attribute tracking
        AttrName[] attrStack;
        int attrCount;
        Dictionary<string, int> attrHashTable;

        // special attribute caching (xmlns, xml:space, xml:lang)
        SpecialAttribute specAttr = SpecialAttribute.No;
        AttributeValueCache attrValueCache;
        string curDeclPrefix;

        // state machine
        State[] stateTable;
        State currentState;

        // settings
        bool checkCharacters;
        bool omitDuplNamespaces;
        bool writeEndDocumentOnClose;

        // actual conformance level
        ConformanceLevel conformanceLevel;

        // flags
        bool dtdWritten;
        bool xmlDeclFollows;

        // char type tables
        XmlCharType xmlCharType = XmlCharType.Instance;

        // hash randomizer
        SecureStringHasher hasher;


        //
        // Constants
        //
        const int ElementStackInitialSize = 8;
        const int NamespaceStackInitialSize = 8;
        const int AttributeArrayInitialSize = 8;
#if DEBUG
        const int MaxAttrDuplWalkCount = 2;
        const int MaxNamespacesWalkCount = 3;
#else
        const int MaxAttrDuplWalkCount = 14;
        const int MaxNamespacesWalkCount = 16;
#endif

        //
        // State tables
        //
        enum State {
            Start = 0,
            TopLevel = 1,
            Document = 2,
            Element = 3,
            Content = 4,
            B64Content = 5,
            B64Attribute = 6,
            AfterRootEle = 7,
            Attribute = 8,
            SpecialAttr = 9,
            EndDocument = 10,
            RootLevelAttr = 11,
            RootLevelSpecAttr = 12,
            RootLevelB64Attr = 13,
            AfterRootLevelAttr = 14,
            Closed = 15,
            Error = 16,

            StartContent = 101,
            StartContentEle = 102,
            StartContentB64 = 103,
            StartDoc = 104,
            StartDocEle = 106,
            EndAttrSEle = 107,
            EndAttrEEle = 108,
            EndAttrSCont = 109,
            EndAttrSAttr = 111,
            PostB64Cont = 112,
            PostB64Attr = 113,
            PostB64RootAttr = 114,
            StartFragEle = 115,
            StartFragCont = 116,
            StartFragB64 = 117,
            StartRootLevelAttr = 118,
        }

        enum Token {
            StartDocument,
            EndDocument,
            PI,
            Comment,
            Dtd,
            StartElement,
            EndElement,
            StartAttribute,
            EndAttribute,
            Text,
            CData,
            AtomicValue,
            Base64,
            RawData,
            Whitespace,
        }

        internal static readonly string[] stateName = {
            "Start",                     // State.Start                             
            "TopLevel",                  // State.TopLevel       
            "Document",                  // State.Document       
            "Element Start Tag",         // State.Element        
            "Element Content",           // State.Content        
            "Element Content",           // State.B64Content        
            "Attribute",                 // State.B64Attribute   
            "EndRootElement",            // State.AfterRootEle   
            "Attribute",                 // State.Attribute      
            "Special Attribute",         // State.SpecialAttr
            "End Document",              // State.EndDocument
            "Root Level Attribute Value",           // State.RootLevelAttr
            "Root Level Special Attribute Value",   // State.RootLevelSpecAttr
            "Root Level Base64 Attribute Value",    // State.RootLevelB64Attr
            "After Root Level Attribute",           // State.AfterRootLevelAttr
            "Closed",                    // State.Closed
            "Error",                     // State.Error
        };

        internal static readonly string[] tokenName = {
            "StartDocument",            // Token.StartDocument
            "EndDocument",              // Token.EndDocument
            "PI",                       // Token.PI
            "Comment",                  // Token.Comment
            "DTD",                      // Token.Dtd
            "StartElement",             // Token.StartElement
            "EndElement",               // Token.EndElement
            "StartAttribute",           // Token.StartAttribut
            "EndAttribute",             // Token.EndAttribute
            "Text",                     // Token.Text
            "CDATA",                    // Token.CData
            "Atomic value",             // Token.AtomicValue
            "Base64",                   // Token.Base64
            "RawData",                  // Token.RawData
            "Whitespace",               // Token.Whitespace
        };

        private static WriteState[] state2WriteState = {    
            WriteState.Start,       // State.Start       
            WriteState.Prolog,      // State.TopLevel       
            WriteState.Prolog,      // State.Document       
            WriteState.Element,     // State.Element        
            WriteState.Content,     // State.Content        
            WriteState.Content,     // State.B64Content        
            WriteState.Attribute,   // State.B64Attribute   
            WriteState.Content,     // State.AfterRootEle   
            WriteState.Attribute,   // State.Attribute      
            WriteState.Attribute,   // State.SpecialAttr
            WriteState.Content,     // State.EndDocument
            WriteState.Attribute,   // State.RootLevelAttr
            WriteState.Attribute,   // State.RootLevelSpecAttr
            WriteState.Attribute,   // State.RootLevelB64Attr
            WriteState.Attribute,   // State.AfterRootLevelAttr
            WriteState.Closed,      // State.Closed
            WriteState.Error,       // State.Error
        };

        private static readonly State[] StateTableDocument = {
    //                         State.Start           State.TopLevel   State.Document     State.Element          State.Content     State.B64Content      State.B64Attribute   State.AfterRootEle    State.Attribute,      State.SpecialAttr,   State.EndDocument,  State.RootLevelAttr,      State.RootLevelSpecAttr,  State.RootLevelB64Attr   State.AfterRootLevelAttr, // 16
    /* Token.StartDocument  */ State.Document,       State.Error,     State.Error,       State.Error,           State.Error,      State.PostB64Cont,    State.Error,         State.Error,          State.Error,          State.Error,         State.Error,        State.Error,              State.Error,              State.Error,             State.Error,              State.Error,
    /* Token.EndDocument    */ State.Error,          State.Error,     State.Error,       State.Error,           State.Error,      State.PostB64Cont,    State.Error,         State.EndDocument,    State.Error,          State.Error,         State.Error,        State.Error,              State.Error,              State.Error,             State.Error,              State.Error,
    /* Token.PI             */ State.StartDoc,       State.TopLevel,  State.Document,    State.StartContent,    State.Content,    State.PostB64Cont,    State.PostB64Attr,   State.AfterRootEle,   State.EndAttrSCont,   State.EndAttrSCont,  State.Error,        State.Error,              State.Error,              State.Error,             State.Error,              State.Error,
    /* Token.Comment        */ State.StartDoc,       State.TopLevel,  State.Document,    State.StartContent,    State.Content,    State.PostB64Cont,    State.PostB64Attr,   State.AfterRootEle,   State.EndAttrSCont,   State.EndAttrSCont,  State.Error,        State.Error,              State.Error,              State.Error,             State.Error,              State.Error,
    /* Token.Dtd            */ State.StartDoc,       State.TopLevel,  State.Document,    State.Error,           State.Error,      State.PostB64Cont,    State.PostB64Attr,   State.Error,          State.Error,          State.Error,         State.Error,        State.Error,              State.Error,              State.Error,             State.Error,              State.Error,
    /* Token.StartElement   */ State.StartDocEle,    State.Element,   State.Element,     State.StartContentEle, State.Element,    State.PostB64Cont,    State.PostB64Attr,   State.Error,          State.EndAttrSEle,    State.EndAttrSEle,   State.Error,        State.Error,              State.Error,              State.Error,             State.Error,              State.Error,
    /* Token.EndElement     */ State.Error,          State.Error,     State.Error,       State.StartContent,    State.Content,    State.PostB64Cont,    State.PostB64Attr,   State.Error,          State.EndAttrEEle,    State.EndAttrEEle,   State.Error,        State.Error,              State.Error,              State.Error,             State.Error,              State.Error,
    /* Token.StartAttribute */ State.Error,          State.Error,     State.Error,       State.Attribute,       State.Error,      State.PostB64Cont,    State.PostB64Attr,   State.Error,          State.EndAttrSAttr,   State.EndAttrSAttr,  State.Error,        State.Error,              State.Error,              State.Error,             State.Error,              State.Error,
    /* Token.EndAttribute   */ State.Error,          State.Error,     State.Error,       State.Error,           State.Error,      State.PostB64Cont,    State.PostB64Attr,   State.Error,          State.Element,        State.Element,       State.Error,        State.Error,              State.Error,              State.Error,             State.Error,              State.Error,
    /* Token.Text           */ State.Error,          State.Error,     State.Error,       State.StartContent,    State.Content,    State.PostB64Cont,    State.PostB64Attr,   State.Error,          State.Attribute,      State.SpecialAttr,   State.Error,        State.Error,              State.Error,              State.Error,             State.Error,              State.Error,
    /* Token.CData          */ State.Error,          State.Error,     State.Error,       State.StartContent,    State.Content,    State.PostB64Cont,    State.PostB64Attr,   State.Error,          State.EndAttrSCont,   State.EndAttrSCont,  State.Error,        State.Error,              State.Error,              State.Error,             State.Error,              State.Error,
    /* Token.AtomicValue    */ State.Error,          State.Error,     State.Error,       State.StartContent,    State.Content,    State.PostB64Cont,    State.PostB64Attr,   State.Error,          State.Attribute,      State.Error,         State.Error,        State.Error,              State.Error,              State.Error,             State.Error,              State.Error,
    /* Token.Base64         */ State.Error,          State.Error,     State.Error,       State.StartContentB64, State.B64Content, State.B64Content,     State.B64Attribute,  State.Error,          State.B64Attribute,   State.Error,         State.Error,        State.Error,              State.Error,              State.Error,             State.Error,              State.Error,
    /* Token.RawData        */ State.StartDoc,       State.Error,     State.Document,    State.StartContent,    State.Content,    State.PostB64Cont,    State.PostB64Attr,   State.AfterRootEle,   State.Attribute,      State.SpecialAttr,   State.Error,        State.Error,              State.Error,              State.Error,             State.Error,              State.Error,
    /* Token.Whitespace     */ State.StartDoc,       State.TopLevel,  State.Document,    State.StartContent,    State.Content,    State.PostB64Cont,    State.PostB64Attr,   State.AfterRootEle,   State.Attribute,      State.SpecialAttr,   State.Error,        State.Error,              State.Error,              State.Error,             State.Error,              State.Error
        };

        private static readonly State[] StateTableAuto = {                                                                                                                                                                                                                                                                                                                                      
    //                         State.Start           State.TopLevel       State.Document     State.Element          State.Content     State.B64Content      State.B64Attribute   State.AfterRootEle    State.Attribute,      State.SpecialAttr,   State.EndDocument,  State.RootLevelAttr,      State.RootLevelSpecAttr,  State.RootLevelB64Attr,  State.AfterRootLevelAttr  // 16
    /* Token.StartDocument  */ State.Document,       State.Error,         State.Error,       State.Error,           State.Error,      State.PostB64Cont,    State.Error,         State.Error,          State.Error,          State.Error,         State.Error,        State.Error,              State.Error,              State.Error,             State.Error,              State.Error, /* Token.StartDocument  */
    /* Token.EndDocument    */ State.Error,          State.Error,         State.Error,       State.Error,           State.Error,      State.PostB64Cont,    State.Error,         State.EndDocument,    State.Error,          State.Error,         State.Error,        State.Error,              State.Error,              State.Error,             State.Error,              State.Error, /* Token.EndDocument    */
    /* Token.PI             */ State.TopLevel,       State.TopLevel,      State.Error,       State.StartContent,    State.Content,    State.PostB64Cont,    State.PostB64Attr,   State.AfterRootEle,   State.EndAttrSCont,   State.EndAttrSCont,  State.Error,        State.Error,              State.Error,              State.Error,             State.Error,              State.Error, /* Token.PI             */
    /* Token.Comment        */ State.TopLevel,       State.TopLevel,      State.Error,       State.StartContent,    State.Content,    State.PostB64Cont,    State.PostB64Attr,   State.AfterRootEle,   State.EndAttrSCont,   State.EndAttrSCont,  State.Error,        State.Error,              State.Error,              State.Error,             State.Error,              State.Error, /* Token.Comment        */
    /* Token.Dtd            */ State.StartDoc,       State.TopLevel,      State.Error,       State.Error,           State.Error,      State.PostB64Cont,    State.PostB64Attr,   State.Error,          State.Error,          State.Error,         State.Error,        State.Error,              State.Error,              State.Error,             State.Error,              State.Error, /* Token.Dtd            */
    /* Token.StartElement   */ State.StartFragEle,   State.Element,       State.Error,       State.StartContentEle, State.Element,    State.PostB64Cont,    State.PostB64Attr,   State.Element,        State.EndAttrSEle,    State.EndAttrSEle,   State.Error,        State.Error,              State.Error,              State.Error,             State.Error,              State.Error, /* Token.StartElement   */
    /* Token.EndElement     */ State.Error,          State.Error,         State.Error,       State.StartContent,    State.Content,    State.PostB64Cont,    State.PostB64Attr,   State.Error,          State.EndAttrEEle,    State.EndAttrEEle,   State.Error,        State.Error,              State.Error,              State.Error,             State.Error,              State.Error, /* Token.EndElement     */
    /* Token.StartAttribute */ State.RootLevelAttr,  State.Error,         State.Error,       State.Attribute,       State.Error,      State.PostB64Cont,    State.PostB64Attr,   State.Error,          State.EndAttrSAttr,   State.EndAttrSAttr,  State.Error,        State.StartRootLevelAttr, State.StartRootLevelAttr, State.PostB64RootAttr,   State.RootLevelAttr,      State.Error, /* Token.StartAttribute */
    /* Token.EndAttribute   */ State.Error,          State.Error,         State.Error,       State.Error,           State.Error,      State.PostB64Cont,    State.PostB64Attr,   State.Error,          State.Element,        State.Element,       State.Error,        State.AfterRootLevelAttr, State.AfterRootLevelAttr, State.PostB64RootAttr,   State.Error,              State.Error, /* Token.EndAttribute   */
    /* Token.Text           */ State.StartFragCont,  State.StartFragCont, State.Error,       State.StartContent,    State.Content,    State.PostB64Cont,    State.PostB64Attr,   State.Content,        State.Attribute,      State.SpecialAttr,   State.Error,        State.RootLevelAttr,      State.RootLevelSpecAttr,  State.PostB64RootAttr,   State.Error,              State.Error, /* Token.Text           */
    /* Token.CData          */ State.StartFragCont,  State.StartFragCont, State.Error,       State.StartContent,    State.Content,    State.PostB64Cont,    State.PostB64Attr,   State.Content,        State.EndAttrSCont,   State.EndAttrSCont,  State.Error,        State.Error,              State.Error,              State.Error,             State.Error,              State.Error, /* Token.CData          */
    /* Token.AtomicValue    */ State.StartFragCont,  State.StartFragCont, State.Error,       State.StartContent,    State.Content,    State.PostB64Cont,    State.PostB64Attr,   State.Content,        State.Attribute,      State.Error,         State.Error,        State.RootLevelAttr,      State.Error,              State.PostB64RootAttr,   State.Error,              State.Error, /* Token.AtomicValue    */
    /* Token.Base64         */ State.StartFragB64,   State.StartFragB64,  State.Error,       State.StartContentB64, State.B64Content, State.B64Content,     State.B64Attribute,  State.B64Content,     State.B64Attribute,   State.Error,         State.Error,        State.RootLevelB64Attr,   State.Error,              State.RootLevelB64Attr,  State.Error,              State.Error, /* Token.Base64         */
    /* Token.RawData        */ State.StartFragCont,  State.TopLevel,      State.Error,       State.StartContent,    State.Content,    State.PostB64Cont,    State.PostB64Attr,   State.Content,        State.Attribute,      State.SpecialAttr,   State.Error,        State.RootLevelAttr,      State.RootLevelSpecAttr,  State.PostB64RootAttr,   State.AfterRootLevelAttr, State.Error, /* Token.RawData        */
    /* Token.Whitespace     */ State.TopLevel,       State.TopLevel,      State.Error,       State.StartContent,    State.Content,    State.PostB64Cont,    State.PostB64Attr,   State.AfterRootEle,   State.Attribute,      State.SpecialAttr,   State.Error,        State.RootLevelAttr,      State.RootLevelSpecAttr,  State.PostB64RootAttr,   State.AfterRootLevelAttr, State.Error, /* Token.Whitespace     */
        };

        //
        // Constructor & finalizer
        //
        internal XmlWellFormedWriter(XmlWriter writer, XmlWriterSettings settings) {
            Debug.Assert(writer != null);
            Debug.Assert(settings != null);
            Debug.Assert(MaxNamespacesWalkCount <= 3);

            this.writer = writer;

            rawWriter = writer as XmlRawWriter;
            predefinedNamespaces = writer as IXmlNamespaceResolver;
            if (rawWriter != null) {
                rawWriter.NamespaceResolver = new NamespaceResolverProxy(this);
            }

            checkCharacters = settings.CheckCharacters;
            omitDuplNamespaces = (settings.NamespaceHandling & NamespaceHandling.OmitDuplicates) != 0;
            writeEndDocumentOnClose = settings.WriteEndDocumentOnClose;

            conformanceLevel = settings.ConformanceLevel;
            stateTable = (conformanceLevel == ConformanceLevel.Document) ? StateTableDocument : StateTableAuto;

            currentState = State.Start;

            nsStack = new Namespace[NamespaceStackInitialSize];
            nsStack[0].Set("xmlns", XmlReservedNs.NsXmlNs, NamespaceKind.Special);
            nsStack[1].Set("xml", XmlReservedNs.NsXml, NamespaceKind.Special);
            if (predefinedNamespaces == null) {
                nsStack[2].Set(string.Empty, string.Empty, NamespaceKind.Implied);
            }
            else {
                string defaultNs = predefinedNamespaces.LookupNamespace(string.Empty);
                nsStack[2].Set(string.Empty, (defaultNs == null ? string.Empty : defaultNs), NamespaceKind.Implied);
            }
            nsTop = 2;

            elemScopeStack = new ElementScope[ElementStackInitialSize];
            elemScopeStack[0].Set(string.Empty, string.Empty, string.Empty, nsTop);
            elemScopeStack[0].xmlSpace = XmlSpace.None;
            elemScopeStack[0].xmlLang = null;
            elemTop = 0;

            attrStack = new AttrName[AttributeArrayInitialSize];

            hasher = new SecureStringHasher();
        }

        //
        // XmlWriter implementation
        //
        public override WriteState WriteState {
            get {
                if ((int)currentState <= (int)State.Error) {
                    return state2WriteState[(int)currentState];
                }
                else {
                    Debug.Assert(false, "Expected currentState <= State.Error ");
                    return WriteState.Error;
                }
            }
        }

        public override XmlWriterSettings Settings {
            get {
                XmlWriterSettings settings = writer.Settings;
                settings.ReadOnly = false;
                settings.ConformanceLevel = conformanceLevel;
                if (omitDuplNamespaces) {
                    settings.NamespaceHandling |= NamespaceHandling.OmitDuplicates;
                }
                settings.WriteEndDocumentOnClose = writeEndDocumentOnClose;
                settings.ReadOnly = true;
                return settings;
            }
        }

        public override void WriteStartDocument() {
            WriteStartDocumentImpl(XmlStandalone.Omit);
        }

        public override void WriteStartDocument(bool standalone) {
            WriteStartDocumentImpl(standalone ? XmlStandalone.Yes : XmlStandalone.No);
        }

        public override void WriteEndDocument() {
            try {
                // auto-close all elements
                while (elemTop > 0) {
                    WriteEndElement();
                }
                State prevState = currentState;
                AdvanceState(Token.EndDocument);

                if (prevState != State.AfterRootEle) {
                    throw new ArgumentException(Res.GetString(Res.Xml_NoRoot));
                }
                if (rawWriter == null) {
                    writer.WriteEndDocument();
                }
            }
            catch {
                currentState = State.Error;
                throw;
            }
        }

        public override void WriteDocType(string name, string pubid, string sysid, string subset) {
            try {
                if (name == null || name.Length == 0) {
                    throw new ArgumentException(Res.GetString(Res.Xml_EmptyName));
                }
                XmlConvert.VerifyQName(name, ExceptionType.XmlException);

                if (conformanceLevel == ConformanceLevel.Fragment) {
                    throw new InvalidOperationException(Res.GetString(Res.Xml_DtdNotAllowedInFragment));
                }

                AdvanceState(Token.Dtd);
                if (dtdWritten) {
                    currentState = State.Error;
                    throw new InvalidOperationException(Res.GetString(Res.Xml_DtdAlreadyWritten));
                }

                if (conformanceLevel == ConformanceLevel.Auto) {
                    conformanceLevel = ConformanceLevel.Document;
                    stateTable = StateTableDocument;
                }

                int i;

                // check characters
                if (checkCharacters) {
                    if (pubid != null) {
                        if ((i = xmlCharType.IsPublicId(pubid)) >= 0) {
                            throw new ArgumentException(Res.GetString(Res.Xml_InvalidCharacter, XmlException.BuildCharExceptionArgs(pubid, i)), "pubid");
                        }
                    }
                    if (sysid != null) {
                        if ((i = xmlCharType.IsOnlyCharData(sysid)) >= 0) {
                            throw new ArgumentException(Res.GetString(Res.Xml_InvalidCharacter, XmlException.BuildCharExceptionArgs(sysid, i)), "sysid");
                        }
                    }
                    if (subset != null) {
                        if ((i = xmlCharType.IsOnlyCharData(subset)) >= 0) {
                            throw new ArgumentException(Res.GetString(Res.Xml_InvalidCharacter, XmlException.BuildCharExceptionArgs(subset, i)), "subset");
                        }
                    }
                }

                // write doctype
                writer.WriteDocType(name, pubid, sysid, subset);
                dtdWritten = true;
            }
            catch {
                currentState = State.Error;
                throw;
            }
        }

        public override void WriteStartElement(string prefix, string localName, string ns) {
            try {
                // check local name
                if (localName == null || localName.Length == 0) {
                    throw new ArgumentException(Res.GetString(Res.Xml_EmptyLocalName));
                }
                CheckNCName(localName);

                AdvanceState(Token.StartElement);

                // lookup prefix / namespace  
                if (prefix == null) {
                    if (ns != null) {
                        prefix = LookupPrefix(ns);
                    }
                    if (prefix == null) {
                        prefix = string.Empty;
                    }
                }
                else if (prefix.Length > 0) {
                    CheckNCName(prefix);
                    if (ns == null) {
                        ns = LookupNamespace(prefix);
                    }
                    if (ns == null || (ns != null && ns.Length == 0)) {
                        throw new ArgumentException(Res.GetString(Res.Xml_PrefixForEmptyNs));
                    }
                }
                if (ns == null) {
                    ns = LookupNamespace(prefix);
                    if (ns == null) {
                        Debug.Assert(prefix.Length == 0);
                        ns = string.Empty;
                    }
                }

                if (elemTop == 0 && rawWriter != null) {
                    // notify the underlying raw writer about the root level element
                    rawWriter.OnRootElement(conformanceLevel);
                }

                // write start tag
                writer.WriteStartElement(prefix, localName, ns);

                // push element on stack and add/check namespace
                int top = ++elemTop;
                if (top == elemScopeStack.Length) {
                    ElementScope[] newStack = new ElementScope[top * 2];
                    Array.Copy(elemScopeStack, newStack, top);
                    elemScopeStack = newStack;
                }
                elemScopeStack[top].Set(prefix, localName, ns, nsTop);

                PushNamespaceImplicit(prefix, ns);

                if (attrCount >= MaxAttrDuplWalkCount) {
                    attrHashTable.Clear();
                }
                attrCount = 0;

            }
            catch {
                currentState = State.Error;
                throw;
            }
        }


        public override void WriteEndElement() {
            try {
                AdvanceState(Token.EndElement);

                int top = elemTop;
                if (top == 0) {
                    throw new XmlException(Res.Xml_NoStartTag, string.Empty);
                }

                // write end tag
                if (rawWriter != null) {
                    elemScopeStack[top].WriteEndElement(rawWriter);
                }
                else {
                    writer.WriteEndElement();
                }

                // pop namespaces
                int prevNsTop = elemScopeStack[top].prevNSTop;
                if (useNsHashtable && prevNsTop < nsTop) {
                    PopNamespaces(prevNsTop + 1, nsTop);
                }
                nsTop = prevNsTop;
                elemTop = --top;

                // check "one root element" condition for ConformanceLevel.Document
                if (top == 0) {
                    if (conformanceLevel == ConformanceLevel.Document) {
                        currentState = State.AfterRootEle;
                    }
                    else {
                        currentState = State.TopLevel;
                    }
                }
            }
            catch {
                currentState = State.Error;
                throw;
            }
        }

        public override void WriteFullEndElement() {
            try {
                AdvanceState(Token.EndElement);

                int top = elemTop;
                if (top == 0) {
                    throw new XmlException(Res.Xml_NoStartTag, string.Empty);
                }

                // write end tag
                if (rawWriter != null) {
                    elemScopeStack[top].WriteFullEndElement(rawWriter);
                }
                else {
                    writer.WriteFullEndElement();
                }

                // pop namespaces
                int prevNsTop = elemScopeStack[top].prevNSTop;
                if (useNsHashtable && prevNsTop < nsTop) {
                    PopNamespaces(prevNsTop + 1, nsTop);
                }
                nsTop = prevNsTop;
                elemTop = --top;

                // check "one root element" condition for ConformanceLevel.Document
                if (top == 0) {
                    if (conformanceLevel == ConformanceLevel.Document) {
                        currentState = State.AfterRootEle;
                    }
                    else {
                        currentState = State.TopLevel;
                    }
                }
            }
            catch {
                currentState = State.Error;
                throw;
            }
        }

        public override void WriteStartAttribute(string prefix, string localName, string namespaceName) {
            try {
                // check local name
                if (localName == null || localName.Length == 0) {
                    if (prefix == "xmlns") {
                        localName = "xmlns";
                        prefix = string.Empty;
                    }
                    else {
                        throw new ArgumentException(Res.GetString(Res.Xml_EmptyLocalName));
                    }
                }
                CheckNCName(localName);

                AdvanceState(Token.StartAttribute);

                // lookup prefix / namespace  
                if (prefix == null) {
                    if (namespaceName != null) {
                        // special case prefix=null/localname=xmlns
                        if (!(localName == "xmlns" && namespaceName == XmlReservedNs.NsXmlNs))
                            prefix = LookupPrefix(namespaceName);
                    }
                    if (prefix == null) {
                        prefix = string.Empty;
                    }
                }
                if (namespaceName == null) {
                    if (prefix != null && prefix.Length > 0) {
                        namespaceName = LookupNamespace(prefix);
                    }
                    if (namespaceName == null) {
                        namespaceName = string.Empty;
                    }
                }

                if (prefix.Length == 0) {
                    if (localName[0] == 'x' && localName == "xmlns") {
                        if (namespaceName.Length > 0 && namespaceName != XmlReservedNs.NsXmlNs) {
                            throw new ArgumentException(Res.GetString(Res.Xml_XmlnsPrefix));
                        }
                        curDeclPrefix = String.Empty;
                        SetSpecialAttribute(SpecialAttribute.DefaultXmlns);
                        goto SkipPushAndWrite;
                    }
                    else if (namespaceName.Length > 0) {
                        prefix = LookupPrefix(namespaceName);
                        if (prefix == null || prefix.Length == 0) {
                            prefix = GeneratePrefix();
                        }
                    }
                }
                else {
                    if (prefix[0] == 'x') {
                        if (prefix == "xmlns") {
                            if (namespaceName.Length > 0 && namespaceName != XmlReservedNs.NsXmlNs) {
                                throw new ArgumentException(Res.GetString(Res.Xml_XmlnsPrefix));
                            }
                            curDeclPrefix = localName;
                            SetSpecialAttribute(SpecialAttribute.PrefixedXmlns);
                            goto SkipPushAndWrite;
                        }
                        else if (prefix == "xml") {
                            if (namespaceName.Length > 0 && namespaceName != XmlReservedNs.NsXml) {
                                throw new ArgumentException(Res.GetString(Res.Xml_XmlPrefix));
                            }
                            switch (localName) {
                                case "space":
                                    SetSpecialAttribute(SpecialAttribute.XmlSpace);
                                    goto SkipPushAndWrite;
                                case "lang":
                                    SetSpecialAttribute(SpecialAttribute.XmlLang);
                                    goto SkipPushAndWrite;
                            }
                        }
                    }

                    CheckNCName(prefix);

                    if (namespaceName.Length == 0) {
                        // attributes cannot have default namespace
                        prefix = string.Empty;
                    }
                    else {
                        string definedNs = LookupLocalNamespace(prefix);
                        if (definedNs != null && definedNs != namespaceName) {
                            prefix = GeneratePrefix();
                        }
                    }
                }

                if (prefix.Length != 0) {
                    PushNamespaceImplicit(prefix, namespaceName);
                }

            SkipPushAndWrite:

                // add attribute to the list and check for duplicates
                AddAttribute( prefix, localName, namespaceName );

                if (specAttr == SpecialAttribute.No) {
                    // write attribute name
                    writer.WriteStartAttribute( prefix, localName, namespaceName );
                }
            }
            catch {
                currentState = State.Error;
                throw;
            }
        }

        public override void WriteEndAttribute() {
            try {
                AdvanceState(Token.EndAttribute);

                if (specAttr != SpecialAttribute.No) {
                    string value;

                    switch (specAttr) {
                        case SpecialAttribute.DefaultXmlns:
                            value = attrValueCache.StringValue;
                            if (PushNamespaceExplicit(string.Empty, value)) { // returns true if the namespace declaration should be written out
                                if (rawWriter != null) {
                                    if (rawWriter.SupportsNamespaceDeclarationInChunks) {
                                        rawWriter.WriteStartNamespaceDeclaration(string.Empty);
                                        attrValueCache.Replay(rawWriter);
                                        rawWriter.WriteEndNamespaceDeclaration();
                                    }
                                    else {
                                        rawWriter.WriteNamespaceDeclaration(string.Empty, value);
                                    }
                                }
                                else {
                                    writer.WriteStartAttribute(string.Empty, "xmlns", XmlReservedNs.NsXmlNs);
                                    attrValueCache.Replay(writer);
                                    writer.WriteEndAttribute();
                                }
                            }
                            curDeclPrefix = null;
                            break;
                        case SpecialAttribute.PrefixedXmlns:
                            value = attrValueCache.StringValue;
                            if (value.Length == 0) {
                                throw new ArgumentException(Res.GetString(Res.Xml_PrefixForEmptyNs));
                            }
                            if (value == XmlReservedNs.NsXmlNs || (value == XmlReservedNs.NsXml && curDeclPrefix != "xml")) {
                                throw new ArgumentException(Res.GetString(Res.Xml_CanNotBindToReservedNamespace));
                            }
                            if (PushNamespaceExplicit(curDeclPrefix, value)) { // returns true if the namespace declaration should be written out
                                if (rawWriter != null) {
                                    if (rawWriter.SupportsNamespaceDeclarationInChunks) {
                                        rawWriter.WriteStartNamespaceDeclaration(curDeclPrefix);
                                        attrValueCache.Replay(rawWriter);
                                        rawWriter.WriteEndNamespaceDeclaration();
                                    }
                                    else {
                                        rawWriter.WriteNamespaceDeclaration(curDeclPrefix, value);
                                    }
                                }
                                else {
                                    writer.WriteStartAttribute("xmlns", curDeclPrefix, XmlReservedNs.NsXmlNs);
                                    attrValueCache.Replay(writer);
                                    writer.WriteEndAttribute();
                                }
                            }
                            curDeclPrefix = null;
                            break;
                        case SpecialAttribute.XmlSpace:
                            attrValueCache.Trim();
                            value = attrValueCache.StringValue;

                            if (value == "default") {
                                elemScopeStack[elemTop].xmlSpace = XmlSpace.Default;
                            }
                            else if (value == "preserve") {
                                elemScopeStack[elemTop].xmlSpace = XmlSpace.Preserve;
                            }
                            else {
                                throw new ArgumentException(Res.GetString(Res.Xml_InvalidXmlSpace, value));
                            }
                            writer.WriteStartAttribute("xml", "space", XmlReservedNs.NsXml);
                            attrValueCache.Replay(writer);
                            writer.WriteEndAttribute();
                            break;
                        case SpecialAttribute.XmlLang:
                            value = attrValueCache.StringValue;
                            elemScopeStack[elemTop].xmlLang = value;
                            writer.WriteStartAttribute("xml", "lang", XmlReservedNs.NsXml);
                            attrValueCache.Replay(writer);
                            writer.WriteEndAttribute();
                            break;
                    }
                    specAttr = SpecialAttribute.No;
                    attrValueCache.Clear();
                }
                else {
                    writer.WriteEndAttribute();
                }
            }
            catch {
                currentState = State.Error;
                throw;
            }
        }

        public override void WriteCData(string text) {
            try {
                if (text == null) {
                    text = string.Empty;
                }
                AdvanceState(Token.CData);
                writer.WriteCData(text);
            }
            catch {
                currentState = State.Error;
                throw;
            }
        }

        public override void WriteComment(string text) {
            try {
                if (text == null) {
                    text = string.Empty;
                }
                AdvanceState(Token.Comment);
                writer.WriteComment(text);
            }
            catch {
                currentState = State.Error;
                throw;
            }
        }

        public override void WriteProcessingInstruction(string name, string text) {
            try {
                // check name
                if (name == null || name.Length == 0) {
                    throw new ArgumentException(Res.GetString(Res.Xml_EmptyName));
                }
                CheckNCName(name);

                // check text
                if (text == null) {
                    text = string.Empty;
                }

                // xml declaration is a special case (not a processing instruction, but we allow WriteProcessingInstruction as a convenience)
                if (name.Length == 3 && string.Compare(name, "xml", StringComparison.OrdinalIgnoreCase) == 0) {
                    if (currentState != State.Start) {
                        throw new ArgumentException(Res.GetString(conformanceLevel == ConformanceLevel.Document ? Res.Xml_DupXmlDecl : Res.Xml_CannotWriteXmlDecl));
                    }

                    xmlDeclFollows = true;
                    AdvanceState(Token.PI);

                    if (rawWriter != null) {
                        // Translate PI into an xml declaration
                        rawWriter.WriteXmlDeclaration(text);
                    }
                    else {
                        writer.WriteProcessingInstruction(name, text);
                    }
                }
                else {
                    AdvanceState(Token.PI);
                    writer.WriteProcessingInstruction(name, text);
                }
            }
            catch {
                currentState = State.Error;
                throw;
            }
        }

        public override void WriteEntityRef(string name) {
            try {
                // check name
                if (name == null || name.Length == 0) {
                    throw new ArgumentException(Res.GetString(Res.Xml_EmptyName));
                }
                CheckNCName(name);

                AdvanceState(Token.Text);
                if (SaveAttrValue) {
                    attrValueCache.WriteEntityRef(name);
                }
                else {
                    writer.WriteEntityRef(name);
                }
            }
            catch {
                currentState = State.Error;
                throw;
            }
        }

        public override void WriteCharEntity(char ch) {
            try {
                if (Char.IsSurrogate(ch)) {
                    throw new ArgumentException(Res.GetString(Res.Xml_InvalidSurrogateMissingLowChar));
                }

                AdvanceState(Token.Text);
                if (SaveAttrValue) {
                    attrValueCache.WriteCharEntity(ch);
                }
                else {
                    writer.WriteCharEntity(ch);
                }
            }
            catch {
                currentState = State.Error;
                throw;
            }
        }

        public override void WriteSurrogateCharEntity(char lowChar, char highChar) {
            try {
                if (!Char.IsSurrogatePair(highChar, lowChar)) {
                    throw XmlConvert.CreateInvalidSurrogatePairException(lowChar, highChar);
                }

                AdvanceState(Token.Text);
                if (SaveAttrValue) {
                    attrValueCache.WriteSurrogateCharEntity(lowChar, highChar);
                }
                else {
                    writer.WriteSurrogateCharEntity(lowChar, highChar);
                }
            }
            catch {
                currentState = State.Error;
                throw;
            }
        }

        public override void WriteWhitespace(string ws) {
            try {
                if (ws == null) {
                    ws = string.Empty;
                }
                if (!XmlCharType.Instance.IsOnlyWhitespace(ws)) {
                    throw new ArgumentException(Res.GetString(Res.Xml_NonWhitespace));
                }

                AdvanceState(Token.Whitespace);
                if (SaveAttrValue) {
                    attrValueCache.WriteWhitespace(ws);
                }
                else {
                    writer.WriteWhitespace(ws);
                }
            }
            catch {
                currentState = State.Error;
                throw;
            }
        }

        public override void WriteString(string text) {
            try {
                if (text == null) {
                    return;
                }

                AdvanceState(Token.Text);
                if (SaveAttrValue) {
                    attrValueCache.WriteString(text);
                }
                else {
                    writer.WriteString(text);
                }
            }
            catch {
                currentState = State.Error;
                throw;
            }
        }

        public override void WriteChars(char[] buffer, int index, int count) {
            try {
                if (buffer == null) {
                    throw new ArgumentNullException("buffer");
                }
                if (index < 0) {
                    throw new ArgumentOutOfRangeException("index");
                }
                if (count < 0) {
                    throw new ArgumentOutOfRangeException("count");
                }
                if (count > buffer.Length - index) {
                    throw new ArgumentOutOfRangeException("count");
                }

                AdvanceState(Token.Text);
                if (SaveAttrValue) {
                    attrValueCache.WriteChars(buffer, index, count);
                }
                else {
                    writer.WriteChars(buffer, index, count);
                }
            }
            catch {
                currentState = State.Error;
                throw;
            }
        }

        public override void WriteRaw(char[] buffer, int index, int count) {
            try {
                if (buffer == null) {
                    throw new ArgumentNullException("buffer");
                }
                if (index < 0) {
                    throw new ArgumentOutOfRangeException("index");
                }
                if (count < 0) {
                    throw new ArgumentOutOfRangeException("count");
                }
                if (count > buffer.Length - index) {
                    throw new ArgumentOutOfRangeException("count");
                }

                AdvanceState(Token.RawData);
                if (SaveAttrValue) {
                    attrValueCache.WriteRaw(buffer, index, count);
                }
                else {
                    writer.WriteRaw(buffer, index, count);
                }
            }
            catch {
                currentState = State.Error;
                throw;
            }
        }

        public override void WriteRaw(string data) {
            try {
                if (data == null) {
                    return;
                }

                AdvanceState(Token.RawData);
                if (SaveAttrValue) {
                    attrValueCache.WriteRaw(data);
                }
                else {
                    writer.WriteRaw(data);
                }
            }
            catch {
                currentState = State.Error;
                throw;
            }
        }

        public override void WriteBase64(byte[] buffer, int index, int count) {
            try {
                if (buffer == null) {
                    throw new ArgumentNullException("buffer");
                }
                if (index < 0) {
                    throw new ArgumentOutOfRangeException("index");
                }
                if (count < 0) {
                    throw new ArgumentOutOfRangeException("count");
                }
                if (count > buffer.Length - index) {
                    throw new ArgumentOutOfRangeException("count");
                }

                AdvanceState(Token.Base64);
                writer.WriteBase64(buffer, index, count);
            }
            catch {
                currentState = State.Error;
                throw;
            }
        }

        public override void Close() {
            if (currentState != State.Closed) {
                try {
                    if (writeEndDocumentOnClose) {
                        while (currentState != State.Error && elemTop > 0) {
                            WriteEndElement();
                        }
                    }
                    else {
                        if (currentState != State.Error && elemTop > 0) {
                            //finish the start element tag '>'
                            try {
                                AdvanceState(Token.EndElement);
                            }
                            catch {
                                currentState = State.Error;
                                throw;
                            }
                        }
                    }

                    if (InBase64 && rawWriter != null) {
                        rawWriter.WriteEndBase64();
                    }

                    writer.Flush();
                }
                finally {
                    try {
                        if (rawWriter != null) {
                            rawWriter.Close(WriteState);
                        }
                        else {
                            writer.Close();
                        }
                    }
                    finally {
                        currentState = State.Closed;
                    }
                }
            }
        }

        public override void Flush() {
            try {
                writer.Flush();
            }
            catch {
                currentState = State.Error;
                throw;
            }
        }

        public override string LookupPrefix(string ns) {
            try {
                if (ns == null) {
                    throw new ArgumentNullException("ns");
                }
                for (int i = nsTop; i >= 0; i--) {
                    if (nsStack[i].namespaceUri == ns) {
                        string prefix = nsStack[i].prefix;
                        for (i++; i <= nsTop; i++) {
                            if (nsStack[i].prefix == prefix) {
                                return null;
                            }
                        }
                        return prefix;
                    }
                }
                return (predefinedNamespaces != null) ? predefinedNamespaces.LookupPrefix(ns) : null;
            }
            catch {
                currentState = State.Error;
                throw;
            }
        }

        public override XmlSpace XmlSpace {
            get {
                int i;
                for (i = elemTop; i >= 0 && elemScopeStack[i].xmlSpace == (System.Xml.XmlSpace)(int)-1; i--) ;
                Debug.Assert(i >= 0);
                return elemScopeStack[i].xmlSpace;
            }
        }

        public override string XmlLang {
            get {
                int i;
                for (i = elemTop; i > 0 && elemScopeStack[i].xmlLang == null; i--) ;
                Debug.Assert(i >= 0);
                return elemScopeStack[i].xmlLang;
            }
        }

        public override void WriteQualifiedName(string localName, string ns) {
            try {
                if (localName == null || localName.Length == 0) {
                    throw new ArgumentException(Res.GetString(Res.Xml_EmptyLocalName));
                }
                CheckNCName(localName);

                AdvanceState(Token.Text);
                string prefix = String.Empty;
                if (ns != null && ns.Length != 0) {
                    prefix = LookupPrefix(ns);
                    if (prefix == null) {
                        if (currentState != State.Attribute) {
                            throw new ArgumentException(Res.GetString(Res.Xml_UndefNamespace, ns));
                        }
                        prefix = GeneratePrefix();
                        PushNamespaceImplicit(prefix, ns);
                    }
                }
                // if this is a special attribute, then just convert this to text
                // otherwise delegate to raw-writer
                if (SaveAttrValue || rawWriter == null) {
                    if (prefix.Length != 0) {
                        WriteString(prefix);
                        WriteString(":");
                    }
                    WriteString(localName);
                }
                else {
                    rawWriter.WriteQualifiedName(prefix, localName, ns);
                }
            }
            catch {
                currentState = State.Error;
                throw;
            }
        }

        public override void WriteValue(bool value) {
            try {
                AdvanceState(Token.AtomicValue);
                writer.WriteValue(value);
            }
            catch {
                currentState = State.Error;
                throw;
            }
        }

        public override void WriteValue(DateTime value) {
            try {
                AdvanceState(Token.AtomicValue);
                writer.WriteValue(value);
            }
            catch {
                currentState = State.Error;
                throw;
            }
        }

        public override void WriteValue(DateTimeOffset value) {
            try {
                AdvanceState(Token.AtomicValue);
                writer.WriteValue(value);
            }
            catch {
                currentState = State.Error;
                throw;
            }
        }

        public override void WriteValue(double value) {
            try {
                AdvanceState(Token.AtomicValue);
                writer.WriteValue(value);
            }
            catch {
                currentState = State.Error;
                throw;
            }
        }

        public override void WriteValue(float value) {
            try {
                AdvanceState(Token.AtomicValue);
                writer.WriteValue(value);
            }
            catch {
                currentState = State.Error;
                throw;
            }
        }

        public override void WriteValue(decimal value) {
            try {
                AdvanceState(Token.AtomicValue);
                writer.WriteValue(value);
            }
            catch {
                currentState = State.Error;
                throw;
            }
        }

        public override void WriteValue(int value) {
            try {
                AdvanceState(Token.AtomicValue);
                writer.WriteValue(value);
            }
            catch {
                currentState = State.Error;
                throw;
            }
        }

        public override void WriteValue(long value) {
            try {
                AdvanceState(Token.AtomicValue);
                writer.WriteValue(value);
            }
            catch {
                currentState = State.Error;
                throw;
            }
        }

        public override void WriteValue(string value) {
            try {
                if (value == null) {
                    return;
                }
                if (SaveAttrValue) {
                    AdvanceState(Token.Text);
                    attrValueCache.WriteValue(value);
                }
                else {
                    AdvanceState(Token.AtomicValue);
                    writer.WriteValue(value);
                }
            }
            catch {
                currentState = State.Error;
                throw;
            }
        }

        public override void WriteValue(object value) {
            try {
                if (SaveAttrValue && value is string) {
                    AdvanceState(Token.Text);
                    attrValueCache.WriteValue((string)value);
                }
                else {
                    AdvanceState(Token.AtomicValue);
                    writer.WriteValue(value);
                }
            }
            catch {
                currentState = State.Error;
                throw;
            }
        }

        public override void WriteBinHex(byte[] buffer, int index, int count) {
            if (IsClosedOrErrorState) {
                throw new InvalidOperationException(Res.GetString(Res.Xml_ClosedOrError));
            }
            try {
                AdvanceState(Token.Text);
                base.WriteBinHex(buffer, index, count);
            }
            catch {
                currentState = State.Error;
                throw;
            }
        }

        //
        // Internal methods
        //
#if !SILVERLIGHT
        internal XmlWriter InnerWriter {
            get {
                return this.writer;
            }
        }

        internal XmlRawWriter RawWriter {
            get {
                return rawWriter;
            }
        }
#endif

        //
        // Private methods
        //
        private bool SaveAttrValue {
            get {
                return specAttr != SpecialAttribute.No;
            }
        }

        private bool InBase64 {
            get {
                return (currentState == State.B64Content || currentState == State.B64Attribute || currentState == State.RootLevelB64Attr);
            }
        }

        private void SetSpecialAttribute(SpecialAttribute special) {
            specAttr = special;
            if (State.Attribute == currentState)
                currentState = State.SpecialAttr;
            else if (State.RootLevelAttr == currentState)
                currentState = State.RootLevelSpecAttr;
            else
                Debug.Assert(false, "State.Attribute == currentState || State.RootLevelAttr == currentState");

            if (attrValueCache == null) {
                attrValueCache = new AttributeValueCache();
            }
        }

        private void WriteStartDocumentImpl(XmlStandalone standalone) {
            try {
                AdvanceState(Token.StartDocument);

                if (conformanceLevel == ConformanceLevel.Auto) {
                    conformanceLevel = ConformanceLevel.Document;
                    stateTable = StateTableDocument;
                }
                else if (conformanceLevel == ConformanceLevel.Fragment) {
                    throw new InvalidOperationException(Res.GetString(Res.Xml_CannotStartDocumentOnFragment));
                }

                if (rawWriter != null) {
                    if (!xmlDeclFollows) {
                        rawWriter.WriteXmlDeclaration(standalone);
                    }
                }
                else {
                    // We do not pass the standalone value here - Dev10 Bug #479769
                    writer.WriteStartDocument();
                }
            }
            catch {
                currentState = State.Error;
                throw;
            }
        }

        private void StartFragment() {
            conformanceLevel = ConformanceLevel.Fragment;
            Debug.Assert(stateTable == StateTableAuto);
        }

        // PushNamespaceImplicit is called when a prefix/namespace pair is used in an element name, attribute name or some other qualified name.
        private void PushNamespaceImplicit(string prefix, string ns) {
            NamespaceKind kind;

            // See if the prefix is already defined
            int existingNsIndex = LookupNamespaceIndex(prefix);

            // Prefix is already defined
            if (existingNsIndex != -1) {
                // It is defined in the current scope
                if (existingNsIndex > elemScopeStack[elemTop].prevNSTop) {
                    // The new namespace Uri needs to be the same as the one that is already declared
                    if (nsStack[existingNsIndex].namespaceUri != ns) {
                        throw new XmlException(Res.Xml_RedefinePrefix, new string[] { prefix, nsStack[existingNsIndex].namespaceUri, ns });
                    }
                    // No additional work needed
                    return;
                }
                // The prefix is defined but in a different scope
                else {
                    // existing declaration is special one (xml, xmlns) -> validate that the new one is the same and can be declared 
                    if (nsStack[existingNsIndex].kind == NamespaceKind.Special) {
                        if (prefix == "xml") {
                            if (ns != nsStack[existingNsIndex].namespaceUri) {
                                throw new ArgumentException(Res.GetString(Res.Xml_XmlPrefix));
                            }
                            else {
                                kind = NamespaceKind.Implied;
                            }
                        }
                        else {
                            Debug.Assert(prefix == "xmlns");
                            throw new ArgumentException(Res.GetString(Res.Xml_XmlnsPrefix));
                        }
                    }
                    // regular namespace declaration -> compare the namespace Uris to decide if the prefix is redefined
                    else {
                        kind = (nsStack[existingNsIndex].namespaceUri == ns) ? NamespaceKind.Implied : NamespaceKind.NeedToWrite;
                    }
                }
            }
            // No existing declaration found in the namespace stack
            else {
                // validate special declaration (xml, xmlns)
                if ((ns == XmlReservedNs.NsXml && prefix != "xml") ||
                     (ns == XmlReservedNs.NsXmlNs && prefix != "xmlns")) {
                    throw new ArgumentException(Res.GetString(Res.Xml_NamespaceDeclXmlXmlns, prefix));
                }

                // check if it can be found in the predefinedNamespaces (which are provided by the user)
                if (predefinedNamespaces != null) {
                    string definedNs = predefinedNamespaces.LookupNamespace(prefix);
                    // compare the namespace Uri to decide if the prefix is redefined
                    kind = (definedNs == ns) ? NamespaceKind.Implied : NamespaceKind.NeedToWrite;
                }
                else {
                    // Namespace not declared anywhere yet, we need to write it out
                    kind = NamespaceKind.NeedToWrite;
                }
            }

            AddNamespace(prefix, ns, kind);
        }

        // PushNamespaceExplicit is called when a namespace declaration is written out;
        // It returs true if the namespace declaration should we written out, false if it should be omited (if OmitDuplicateNamespaceDeclarations is true)
        private bool PushNamespaceExplicit(string prefix, string ns) {
            bool writeItOut = true;

            // See if the prefix is already defined
            int existingNsIndex = LookupNamespaceIndex(prefix);

            // Existing declaration in the current scope
            if (existingNsIndex != -1) {
                // It is defined in the current scope
                if (existingNsIndex > elemScopeStack[elemTop].prevNSTop) {
                    // The new namespace Uri needs to be the same as the one that is already declared
                    if (nsStack[existingNsIndex].namespaceUri != ns) {
                        throw new XmlException(Res.Xml_RedefinePrefix, new string[] { prefix, nsStack[existingNsIndex].namespaceUri, ns });
                    }
                    // Check for duplicate declarations
                    NamespaceKind existingNsKind = nsStack[existingNsIndex].kind;
                    if (existingNsKind == NamespaceKind.Written) {
                        throw DupAttrException((prefix.Length == 0) ? string.Empty : "xmlns", (prefix.Length == 0) ? "xmlns" : prefix);
                    }
                    // Check if it can be omitted
                    if (omitDuplNamespaces && existingNsKind != NamespaceKind.NeedToWrite) {
                        writeItOut = false;
                    }
                    nsStack[existingNsIndex].kind = NamespaceKind.Written;
                    // No additional work needed
                    return writeItOut;
                }
                // The prefix is defined but in a different scope
                else {
                    // check if is the same and can be omitted
                    if (nsStack[existingNsIndex].namespaceUri == ns && omitDuplNamespaces) {
                        writeItOut = false;
                    }
                }
            }
            // No existing declaration found in the namespace stack
            else {
                // check if it can be found in the predefinedNamespaces (which are provided by the user)
                if (predefinedNamespaces != null) {
                    string definedNs = predefinedNamespaces.LookupNamespace(prefix);
                    // compare the namespace Uri to decide if the prefix is redefined
                    if (definedNs == ns && omitDuplNamespaces) {
                        writeItOut = false;
                    }
                }
            }

            // validate special declaration (xml, xmlns)
            if ((ns == XmlReservedNs.NsXml && prefix != "xml") ||
                 (ns == XmlReservedNs.NsXmlNs && prefix != "xmlns")) {
                throw new ArgumentException(Res.GetString(Res.Xml_NamespaceDeclXmlXmlns, prefix));
            }
            if (prefix.Length > 0 && prefix[0] == 'x') {
                if (prefix == "xml") {
                    if (ns != XmlReservedNs.NsXml) {
                        throw new ArgumentException(Res.GetString(Res.Xml_XmlPrefix));
                    }
                }
                else if (prefix == "xmlns") {
                    throw new ArgumentException(Res.GetString(Res.Xml_XmlnsPrefix));
                }
            }

            AddNamespace(prefix, ns, NamespaceKind.Written);

            return writeItOut;
        }

        private void AddNamespace(string prefix, string ns, NamespaceKind kind) {
            int top = ++nsTop;
            if (top == nsStack.Length) {
                Namespace[] newStack = new Namespace[top * 2];
                Array.Copy(nsStack, newStack, top);
                nsStack = newStack;
            }
            nsStack[top].Set(prefix, ns, kind);

            if (useNsHashtable) {
                // add last
                AddToNamespaceHashtable(nsTop);
            }
            else if (nsTop == MaxNamespacesWalkCount) {
                // add all
                nsHashtable = new Dictionary<string, int>(hasher);
                for (int i = 0; i <= nsTop; i++) {
                    AddToNamespaceHashtable(i);
                }
                useNsHashtable = true;
            }
        }

        private void AddToNamespaceHashtable(int namespaceIndex) {
            string prefix = nsStack[namespaceIndex].prefix;
            int existingNsIndex;
            if (nsHashtable.TryGetValue(prefix, out existingNsIndex)) {
                nsStack[namespaceIndex].prevNsIndex = existingNsIndex;
            }
            nsHashtable[prefix] = namespaceIndex;
        }

        private int LookupNamespaceIndex(string prefix) {
            int index;
            if (useNsHashtable) {
                if (nsHashtable.TryGetValue(prefix, out index)) {
                    return index;
                }
            }
            else {
                for (int i = nsTop; i >= 0; i--) {
                    if (nsStack[i].prefix == prefix) {
                        return i;
                    }
                }
            }
            return -1;
        }

        private void PopNamespaces(int indexFrom, int indexTo) {
            Debug.Assert(useNsHashtable);
            Debug.Assert(indexFrom <= indexTo);
            for (int i = indexTo; i >= indexFrom; i--) {
                Debug.Assert(nsHashtable.ContainsKey(nsStack[i].prefix));
                if (nsStack[i].prevNsIndex == -1) {
                    nsHashtable.Remove(nsStack[i].prefix);
                }
                else {
                    nsHashtable[nsStack[i].prefix] = nsStack[i].prevNsIndex;
                }
            }
        }

        static private XmlException DupAttrException(string prefix, string localName) {
            StringBuilder sb = new StringBuilder();
            if (prefix.Length > 0) {
                sb.Append(prefix);
                sb.Append(':');
            }
            sb.Append(localName);
            return new XmlException(Res.Xml_DupAttributeName, sb.ToString());
        }

        // Advance the state machine
        private void AdvanceState(Token token) {
            if ((int)currentState >= (int)State.Closed) {
                if (currentState == State.Closed || currentState == State.Error) {
                    throw new InvalidOperationException(Res.GetString(Res.Xml_ClosedOrError));
                }
                else {
                    throw new InvalidOperationException(Res.GetString(Res.Xml_WrongToken, tokenName[(int)token], GetStateName(currentState)));
                }
            }

        Advance:
            State newState = stateTable[((int)token << 4) + (int)currentState];
            //                         [ (int)token * 16 + (int)currentState ];

            if ((int)newState >= (int)State.Error) {
                switch (newState) {
                    case State.Error:
                        ThrowInvalidStateTransition(token, currentState);
                        break;

                    case State.StartContent:
                        StartElementContent();
                        newState = State.Content;
                        break;

                    case State.StartContentEle:
                        StartElementContent();
                        newState = State.Element;
                        break;

                    case State.StartContentB64:
                        StartElementContent();
                        newState = State.B64Content;
                        break;

                    case State.StartDoc:
                        WriteStartDocument();
                        newState = State.Document;
                        break;

                    case State.StartDocEle:
                        WriteStartDocument();
                        newState = State.Element;
                        break;

                    case State.EndAttrSEle:
                        WriteEndAttribute();
                        StartElementContent();
                        newState = State.Element;
                        break;

                    case State.EndAttrEEle:
                        WriteEndAttribute();
                        StartElementContent();
                        newState = State.Content;
                        break;

                    case State.EndAttrSCont:
                        WriteEndAttribute();
                        StartElementContent();
                        newState = State.Content;
                        break;

                    case State.EndAttrSAttr:
                        WriteEndAttribute();
                        newState = State.Attribute;
                        break;

                    case State.PostB64Cont:
                        if (rawWriter != null) {
                            rawWriter.WriteEndBase64();
                        }
                        currentState = State.Content;
                        goto Advance;

                    case State.PostB64Attr:
                        if (rawWriter != null) {
                            rawWriter.WriteEndBase64();
                        }
                        currentState = State.Attribute;
                        goto Advance;

                    case State.PostB64RootAttr:
                        if (rawWriter != null) {
                            rawWriter.WriteEndBase64();
                        }
                        currentState = State.RootLevelAttr;
                        goto Advance;

                    case State.StartFragEle:
                        StartFragment();
                        newState = State.Element;
                        break;

                    case State.StartFragCont:
                        StartFragment();
                        newState = State.Content;
                        break;

                    case State.StartFragB64:
                        StartFragment();
                        newState = State.B64Content;
                        break;

                    case State.StartRootLevelAttr:
                        WriteEndAttribute();
                        newState = State.RootLevelAttr;
                        break;

                    default:
                        Debug.Assert(false, "We should not get to this point.");
                        break;
                }
            }

            currentState = newState;
        }

        private void StartElementContent() {
            // write namespace declarations
            int start = elemScopeStack[elemTop].prevNSTop;
            for (int i = nsTop; i > start; i--) {
                if (nsStack[i].kind == NamespaceKind.NeedToWrite) {
                    nsStack[i].WriteDecl(writer, rawWriter);
                }
            }

            if (rawWriter != null) {
                rawWriter.StartElementContent();
            }
        }

        private static string GetStateName(State state) {
            if (state >= State.Error) {
                Debug.Assert(false, "We should never get to this point. State = " + state);
                return "Error";
            }
            else {
                return stateName[(int)state];
            }
        }

        internal string LookupNamespace(string prefix) {
            for (int i = nsTop; i >= 0; i--) {
                if (nsStack[i].prefix == prefix) {
                    return nsStack[i].namespaceUri;
                }
            }
            return (predefinedNamespaces != null) ? predefinedNamespaces.LookupNamespace(prefix) : null;
        }

        private string LookupLocalNamespace(string prefix) {
            for (int i = nsTop; i > elemScopeStack[elemTop].prevNSTop; i--) {
                if (nsStack[i].prefix == prefix) {
                    return nsStack[i].namespaceUri;
                }
            }
            return null;
        }

        private string GeneratePrefix() {
            string genPrefix = "p" + (nsTop - 2).ToString("d", CultureInfo.InvariantCulture);
            if (LookupNamespace(genPrefix) == null) {
                return genPrefix;
            }
            int i = 0;

            string s;
            do {
                s = string.Concat(genPrefix, i.ToString(CultureInfo.InvariantCulture));
                i++;
            } while (LookupNamespace(s) != null);
            return s;
        }

#if SILVERLIGHT && !SILVERLIGHT_DISABLE_SECURITY && XMLCHARTYPE_USE_RESOURCE
        [System.Security.SecuritySafeCritical]
#endif
        private unsafe void CheckNCName(string ncname) {
            Debug.Assert(ncname != null && ncname.Length > 0);

            int i;
            int endPos = ncname.Length;

            // Check if first character is StartNCName (inc. surrogates)
            if ((xmlCharType.charProperties[ncname[0]] & XmlCharType.fNCStartNameSC) != 0) { // if ( xmlCharType.IsStartNCNameChar( ncname[0] ) ) {
                i = 1;
            }
#if XML10_FIFTH_EDITION
            else if (xmlCharType.IsNCNameSurrogateChar(ncname, 0)) { // surrogate ranges are same for NCName and StartNCName
                i = 2;
            }
#endif
            else {
                throw InvalidCharsException(ncname, 0);
            }

            // Check if following characters are NCName (inc. surrogates)
            while (i < endPos) {
                if ((xmlCharType.charProperties[ncname[i]] & XmlCharType.fNCNameSC) != 0) { // if ( xmlCharType.IsNCNameChar( ncname[i] ) ) {
                    i++;
                }
#if XML10_FIFTH_EDITION
                else if (xmlCharType.IsNCNameSurrogateChar(ncname, i)) {
                    i += 2;
                }
#endif
                else {
                    throw InvalidCharsException(ncname, i);
                }
            }
        }

        private static Exception InvalidCharsException(string name, int badCharIndex) {
            string[] badCharArgs = XmlException.BuildCharExceptionArgs(name, badCharIndex);
            string[] args = new string[3];
            args[0] = name;
            args[1] = badCharArgs[0];
            args[2] = badCharArgs[1];
            return new ArgumentException(Res.GetString(Res.Xml_InvalidNameCharsDetail, args));
        }

        // This method translates speficic state transition errors in more friendly error messages
        private void ThrowInvalidStateTransition(Token token, State currentState) {
            string wrongTokenMessage = Res.GetString(Res.Xml_WrongToken, tokenName[(int)token], GetStateName(currentState));
            switch (currentState) {
                case State.AfterRootEle:
                case State.Start:
                    if (conformanceLevel == ConformanceLevel.Document) {
                        throw new InvalidOperationException(wrongTokenMessage + ' ' + Res.GetString(Res.Xml_ConformanceLevelFragment));
                    }
                    break;
            }
            throw new InvalidOperationException(wrongTokenMessage);
        }

        private bool IsClosedOrErrorState {
            get {
                return (int)currentState >= (int)State.Closed;
            }
        }

        private void AddAttribute(string prefix, string localName, string namespaceName) {
            int top = attrCount++;
            if (top == attrStack.Length) {
                AttrName[] newStack = new AttrName[top * 2];
                Array.Copy(attrStack, newStack, top);
                attrStack = newStack;
            }
            attrStack[top].Set(prefix, localName, namespaceName);

            if (attrCount < MaxAttrDuplWalkCount) {
                // check for duplicates
                for (int i = 0; i < top; i++) {
                    if (attrStack[i].IsDuplicate(prefix, localName, namespaceName)) {
                        throw DupAttrException(prefix, localName);
                    }
                }
            }
            else {
                // reached the threshold -> add all attributes to hash table
                if (attrCount == MaxAttrDuplWalkCount) {
                    if (attrHashTable == null) {
                        attrHashTable = new Dictionary<string, int>(hasher);
                    }
                    Debug.Assert(attrHashTable.Count == 0);
                    for (int i = 0; i < top; i++) {
                        AddToAttrHashTable(i);
                    }
                }

                // add last attribute to hash table and check for duplicates
                AddToAttrHashTable(top);
                int prev = attrStack[top].prev;
                while (prev > 0) {
                    // indexes are stored incremented by 1, 0 means no entry
                    prev--;
                    if (attrStack[prev].IsDuplicate(prefix, localName, namespaceName)) {
                        throw DupAttrException(prefix, localName);
                    }
                    prev = attrStack[prev].prev;
                }
            }
        }

        private void AddToAttrHashTable(int attributeIndex) {
            string localName = attrStack[attributeIndex].localName;
            int count = attrHashTable.Count;
            attrHashTable[localName] = 0; // overwrite on collision
            if (count != attrHashTable.Count) {
                return;
            }
            // chain to previous attribute in stack with the same localName
            int prev = attributeIndex - 1;
            while (prev >= 0) {
                if (attrStack[prev].localName == localName) {
                    break;
                }
                prev--;
            }
            Debug.Assert(prev >= 0 && attrStack[prev].localName == localName);
            attrStack[attributeIndex].prev = prev + 1; // indexes are stored incremented by 1 
        }

    }
}
