using System;

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

namespace Wayne.Lib.Log
{
    /// <summary>
    /// Class that is used when logging XML data and wants to utilize the Debuglog mask facilitities.
    /// </summary>
    public class XmlLogObject : ILogObject
    {
        #region Private Classes and Enums

        private enum ProcessingInstructionAttributeElementKind
        {
            AttributeName,
            EqualSign,
            Value
        }

        #endregion

        #region Static Fields

        internal static string XmlMaskNode { get { return "MaskNode"; } }
        internal static string XmlMaskAttribute { get { return "MaskAttr"; } }
        internal static string XmlNodeName { get { return "node"; } }
        internal static string XmlAttributeName { get { return "attr"; } }
        internal static string XmlMaskKind { get { return "kind"; } }
        internal static string XmlMaskNextNodeName { get { return "."; } }

        #endregion

        #region Fields

        private string xml;
        private readonly IXmlElementMask[] elementMasks;

        #endregion

        #region Construction

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="xml">The XML document.</param>
        public XmlLogObject(string xml)
        {
            this.xml = xml;
        }

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="xml">The XML document.</param>
        /// <param name="elementMasks">Element masking.</param>
        public XmlLogObject(string xml, params IXmlElementMask[] elementMasks)
        {
            this.xml = xml;
            this.elementMasks = new IXmlElementMask[elementMasks.Length];
            Array.Copy(elementMasks, this.elementMasks, elementMasks.Length);
        }

        #endregion

        #region Properties

        /// <summary>
        /// The XML document.
        /// </summary>
        public string Xml
        {
            get { return xml; }
        }

        bool ILogObject.UsesFormat
        {
            get { return false; }
        }

        string ILogObject.Format
        {
            get { return string.Empty; }
            set { }
        }

        IFormatProvider ILogObject.Provider
        {
            get { return null; }
            set { }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Appends this object's logObjects to a StringBuilder-output.
        /// </summary>
        /// <param name="output">The StringBuilder.</param>
        /// <param name="logWriter">The LogWriter to write the logObject.</param>
        /// <param name="indentLength">The indent to be used if many lines.</param>
        /// <param name="isFirstLine">Is this the first line to log?</param>
        /// <param name="indent">A string holding a generated indent-text (=a number of spaces). Use AppendIndent() to append the indent.</param>
        void ILogObject.AppendToStringBuilder(LogWriter logWriter, StringBuilder output,
            int indentLength, ref bool isFirstLine, ref string indent)
        {
            if (DebugLoggerMask.MaskSensitiveData)
            {
                StringBuilder newXml = new StringBuilder(xml);

                // mask Track 2
                const string trackTag = "<TagDataElement>57";
                int trackStart = xml.IndexOf(trackTag);
                if (trackStart > 0)
                {
                    trackStart += trackTag.Length + 2;
                    int trackEnd = xml.IndexOf("</TagDataElement>", trackStart);

                    string orgTrack = xml.Substring(trackStart, trackEnd - trackStart);
                    string maskedTrack = DebugLoggerMask.MaskData(orgTrack, DebugLogMaskKind.MaskTrack64);

                    newXml.Replace(orgTrack, maskedTrack);
                }

                // mask PAN
                const string panTag = "<TagDataElement>5A";
                int panStart = xml.IndexOf(panTag);
                if (panStart > 0)
                {
                    panStart += panTag.Length + 2;
                    int panEnd = xml.IndexOf("</TagDataElement>", panStart);

                    string orgPan = xml.Substring(panStart, panEnd - panStart);
                    string maskedPan = DebugLoggerMask.MaskData(orgPan, DebugLogMaskKind.MaskPan64);

                    newXml.Replace(orgPan, maskedPan);
                }

                StringReader xmlStringReader = new StringReader(newXml.ToString());
                XmlReader xmlReader = XmlReader.Create(xmlStringReader);

                XmlWriterSettings xmlWriterSettings = new XmlWriterSettings();
                xmlWriterSettings.OmitXmlDeclaration = true;
                xmlWriterSettings.Indent = true;
                StringBuilder cleanXml = new StringBuilder();
#if DEBUG
                cleanXml.Append("## MASKED ## ");
#endif
                XmlWriter xmlWriter = XmlWriter.Create(cleanXml, xmlWriterSettings);

                Dictionary<string, XmlNodeMask> maskedNodes = new Dictionary<string, XmlNodeMask>(StringComparer.CurrentCultureIgnoreCase);
                Dictionary<string, Dictionary<string, XmlAttributeMask>> maskedAttributes = new Dictionary<string, Dictionary<string, XmlAttributeMask>>(StringComparer.CurrentCultureIgnoreCase);
                int emptyNodeDepth = int.MaxValue;
                int maskNodeDepth = int.MaxValue;
                int maskDigitsNodeDepth = int.MaxValue;
                int maskDigitsAppendHashNodeDepth = int.MaxValue;
                int maskHexNodeDepth = int.MaxValue;
                int maskHexAppendHashNodeDepth = int.MaxValue;
                int maskPanNodeDepth = int.MaxValue;
                int maskPanAppendHashNodeDepth = int.MaxValue;
                int maskEmbeddedPanNodeDepth = int.MaxValue;
                int maskEmbeddedPanAppendHashNodeDepth = int.MaxValue;
                DebugLogMaskKind maskNextNode = DebugLogMaskKind.None;
                bool skipWhitespace = true;

                if (elementMasks != null)
                {
                    foreach (IXmlElementMask elementMask in elementMasks)
                    {
                        XmlAttributeMask xmlAttributeMask = elementMask as XmlAttributeMask;
                        if (xmlAttributeMask != null)
                            AddXmlAttributeMask(maskedAttributes, xmlAttributeMask.NodeName, xmlAttributeMask.AttributeName, xmlAttributeMask.MaskKind);
                        else
                        {
                            XmlNodeMask xmlNodeMask = elementMask as XmlNodeMask;
                            if (xmlNodeMask != null)
                                AddXmlNodeMask(maskedNodes, xmlNodeMask.NodeName, xmlNodeMask.MaskKind);
                        }
                    }
                }

                while (xmlReader.Read())
                {
                    if (xmlReader.NodeType == XmlNodeType.ProcessingInstruction)
                    {
                        if (xmlReader.Name.Equals(XmlMaskNode))
                        {
                            Dictionary<string, string> attributes = GetProcessingInstructionAttributes(xmlReader);
                            string nodeName;
                            if (attributes.TryGetValue(XmlNodeName, out nodeName))
                            {
                                DebugLogMaskKind maskKind = InterpretMaskKind(attributes);
                                if (nodeName.Equals(XmlMaskNextNodeName))
                                    maskNextNode = maskKind;
                                else
                                    AddXmlNodeMask(maskedNodes, nodeName, maskKind);
                            }
                        }
                        else if (xmlReader.Name.Equals(XmlMaskAttribute))
                        {
                            Dictionary<string, string> attributes = GetProcessingInstructionAttributes(xmlReader);
                            string nodeName;
                            if (attributes.TryGetValue(XmlNodeName, out nodeName))
                            {
                                string attributeName;
                                if (attributes.TryGetValue(XmlAttributeName, out attributeName))
                                {
                                    DebugLogMaskKind maskKind = InterpretMaskKind(attributes);

                                    AddXmlAttributeMask(maskedAttributes, nodeName, attributeName, maskKind);
                                }
                            }
                        }
                    }

                    CopyNodeContent(xmlReader, xmlWriter, maskedNodes, maskedAttributes,
                        ref emptyNodeDepth, ref maskNodeDepth, ref maskDigitsNodeDepth, ref maskDigitsAppendHashNodeDepth,
                        ref maskHexNodeDepth, ref maskHexAppendHashNodeDepth,
                        ref maskPanNodeDepth, ref maskPanAppendHashNodeDepth,
                        ref maskEmbeddedPanNodeDepth, ref maskEmbeddedPanAppendHashNodeDepth,
                        ref maskNextNode, ref skipWhitespace);
                }
                xmlWriter.Close();
                xmlReader.Close();
                StringLogObject.AppendObjectToStringBuilder(cleanXml, output, logWriter, indentLength, ref isFirstLine, ref indent);
            }
            else
            {
                try
                {
                    StringBuilder sb = new StringBuilder();
                    int endOfString = xml.IndexOf('\0');
                    if (endOfString > -1)
                        xml = xml.Substring(0, endOfString); // Sometimes there come strings with null-characters which the XML reader doesn't like...
                    XmlReader xmlReader = XmlReader.Create(new StringReader(xml));
                    XmlWriterSettings settings = new XmlWriterSettings();
                    settings.Indent = true;
                    XmlWriter xmlWriter = XmlWriter.Create(sb, settings);
                    xmlWriter.WriteNode(xmlReader, false);
                    xmlWriter.Flush();
                    StringLogObject.AppendObjectToStringBuilder(sb.ToString(), output, logWriter, indentLength, ref isFirstLine, ref indent);
                }
                catch (ArgumentException)
                {
                    // Just log the xml as a string.
                    StringLogObject.AppendObjectToStringBuilder(xml, output, logWriter, indentLength, ref isFirstLine, ref indent);
                }
            }
        }

        private static void AddXmlNodeMask(IDictionary<string, XmlNodeMask> maskedNodes, string nodeName, DebugLogMaskKind maskKind)
        {
            XmlNodeMask existingXmlNodeMask;
            if (maskedNodes.TryGetValue(nodeName, out existingXmlNodeMask))
            {
                if (maskKind == DebugLogMaskKind.None)
                    maskedNodes.Remove(nodeName);
                else
                    existingXmlNodeMask.MaskKind = maskKind;
            }
            else if (maskKind != DebugLogMaskKind.None)
                maskedNodes.Add(nodeName, new XmlNodeMask(nodeName, maskKind));
        }

        private static void AddXmlAttributeMask(IDictionary<string, Dictionary<string, XmlAttributeMask>> maskedAttributes, string nodeName, string attributeName, DebugLogMaskKind maskKind)
        {
            bool newDict = false;
            Dictionary<string, XmlAttributeMask> attributeMaskDict;
            if (!maskedAttributes.TryGetValue(nodeName, out attributeMaskDict))
            {
                attributeMaskDict = new Dictionary<string, XmlAttributeMask>(StringComparer.CurrentCultureIgnoreCase);
                newDict = true;
            }

            XmlAttributeMask existingXmlAttributeMask;
            if (attributeMaskDict.TryGetValue(attributeName, out existingXmlAttributeMask))
            {
                if (maskKind == DebugLogMaskKind.None)
                    attributeMaskDict.Remove(attributeName);
                else
                    existingXmlAttributeMask.MaskKind = maskKind;
            }
            else if (maskKind != DebugLogMaskKind.None)
                attributeMaskDict.Add(attributeName, new XmlAttributeMask(nodeName, attributeName, maskKind));

            if (newDict)
                maskedAttributes.Add(nodeName, attributeMaskDict);
        }

        #endregion

        #region GetProcessingInstructionAttributes

        private static Dictionary<string, string> GetProcessingInstructionAttributes(XmlReader xmlReader)
        {
            Dictionary<string, string> attributes = new Dictionary<string, string>();
            string[] elements = xmlReader.Value.Split(' ');
            ProcessingInstructionAttributeElementKind elementKind = ProcessingInstructionAttributeElementKind.AttributeName;
            string currentAttributeName = string.Empty;
            int equalSignPos;
            foreach (string element in elements)
            {
                if (!string.IsNullOrEmpty(element))
                {
                    switch (elementKind)
                    {
                        case ProcessingInstructionAttributeElementKind.AttributeName:
                            equalSignPos = element.IndexOf('=');
                            if (equalSignPos > -1)
                            {
                                if (equalSignPos > 0)
                                {
                                    currentAttributeName = element.Substring(0, equalSignPos);
                                    if (equalSignPos == element.Length - 1)
                                        elementKind = ProcessingInstructionAttributeElementKind.Value;
                                    else
                                    {
                                        string value = element.Substring(equalSignPos + 1);
                                        if (value.StartsWith("\"") && value.EndsWith("\""))
                                            attributes[currentAttributeName] = value.Substring(1, value.Length - 2);
                                        elementKind = ProcessingInstructionAttributeElementKind.AttributeName;
                                    }
                                }
                            }
                            else
                            {
                                currentAttributeName = element;
                                elementKind = ProcessingInstructionAttributeElementKind.EqualSign;
                            }
                            break;
                        case ProcessingInstructionAttributeElementKind.EqualSign:
                            if (element.Equals("="))
                                elementKind = ProcessingInstructionAttributeElementKind.Value;
                            else if (element.StartsWith("="))
                            {
                                string value = element.Substring(1);
                                if (value.StartsWith("\"") && value.EndsWith("\""))
                                    attributes[currentAttributeName] = value.Substring(1, value.Length - 2);
                                elementKind = ProcessingInstructionAttributeElementKind.AttributeName;
                            }
                            break;
                        case ProcessingInstructionAttributeElementKind.Value:
                            if (element.StartsWith("\"") && element.EndsWith("\""))
                                attributes[currentAttributeName] = element.Substring(1, element.Length - 2);
                            elementKind = ProcessingInstructionAttributeElementKind.AttributeName;
                            break;
                    }
                }
            }
            return attributes;
        }

        #endregion

        #region InterpretMaskKind

        private static DebugLogMaskKind InterpretMaskKind(Dictionary<string, string> attributes)
        {
            string maskKindValue;
            if (attributes.TryGetValue(XmlMaskKind, out maskKindValue))
            {
                try
                {
                    return (DebugLogMaskKind)Enum.Parse(typeof(DebugLogMaskKind), maskKindValue, true);
                }
                catch (ArgumentException) { }
            }
            return DebugLogMaskKind.Remove;
        }

        #endregion

        #region CopyNodeContent

        private static void CopyNodeContent(XmlReader xmlReader, XmlWriter xmlWriter,
            Dictionary<string, XmlNodeMask> maskedNodes, Dictionary<string, Dictionary<string, XmlAttributeMask>> maskedAttributes,
            ref int emptyNodeDepth, ref int maskNodeDepth, ref int maskDigitsNodeDepth, ref int maskDigitsAppendHashNodeDepth,
            ref int maskHexNodeDepth, ref int maskHexAppendHashNodeDepth, ref int maskPanNodeDepth, ref int maskPanAppendHashNodeDepth,
            ref int maskEmbeddedPanNodeDepth, ref int maskEmbeddedPanAppendHashNodeDepth, ref DebugLogMaskKind maskNextNode, ref bool skipWhitespace)
        {
            switch (xmlReader.NodeType)
            {
                case XmlNodeType.Element:
                    if (xmlReader.Depth < emptyNodeDepth)
                    {
                        bool skip = false;
                        XmlNodeMask xmlNodeMask;
                        DebugLogMaskKind maskKind;
                        if (maskNextNode != DebugLogMaskKind.None)
                        {
                            maskKind = maskNextNode;
                            maskNextNode = DebugLogMaskKind.None;
                        }
                        else if (maskedNodes.TryGetValue(xmlReader.LocalName, out xmlNodeMask))
                            maskKind = xmlNodeMask.MaskKind;
                        else
                            maskKind = DebugLogMaskKind.None;

                        switch (maskKind)
                        {
                            case DebugLogMaskKind.Remove:
                                xmlReader.Skip();
                                skip = true;
                                break;
                            case DebugLogMaskKind.Empty:
                                emptyNodeDepth = xmlReader.Depth;
                                break;
                            case DebugLogMaskKind.Mask:
                                maskNodeDepth = xmlReader.Depth;
                                break;
                            case DebugLogMaskKind.MaskDigits:
                                maskDigitsNodeDepth = xmlReader.Depth;
                                break;
                            case DebugLogMaskKind.MaskDigitsAppendHash:
                                maskDigitsAppendHashNodeDepth = xmlReader.Depth;
                                break;
                            case DebugLogMaskKind.MaskHex:
                                maskHexNodeDepth = xmlReader.Depth;
                                break;
                            case DebugLogMaskKind.MaskHexAppendHash:
                                maskHexAppendHashNodeDepth = xmlReader.Depth;
                                break;
                            case DebugLogMaskKind.MaskPan:
                                maskPanNodeDepth = xmlReader.Depth;
                                break;
                            case DebugLogMaskKind.MaskPanAppendHash:
                                maskPanAppendHashNodeDepth = xmlReader.Depth;
                                break;
                            case DebugLogMaskKind.MaskEmbeddedPan:
                                maskEmbeddedPanNodeDepth = xmlReader.Depth;
                                break;
                            case DebugLogMaskKind.MaskEmbeddedPanAppendHash:
                                maskEmbeddedPanAppendHashNodeDepth = xmlReader.Depth;
                                break;
                        }

                        if (!skip)
                        {
                            skipWhitespace = false;
                            xmlWriter.WriteStartElement(xmlReader.Prefix, xmlReader.LocalName, xmlReader.NamespaceURI);

                            if ((xmlReader.Depth < emptyNodeDepth) && xmlReader.HasAttributes)
                            {
                                bool manualAttributes = false;
                                Dictionary<string, XmlAttributeMask> attributeMaskDict;
                                if (maskedAttributes.TryGetValue(xmlReader.LocalName, out attributeMaskDict) && (attributeMaskDict.Count > 0))
                                {
                                    manualAttributes = true;
                                    while (xmlReader.MoveToNextAttribute())
                                    {
                                        if (!xmlReader.Prefix.Equals("xmlns")) // This attribute is already written.
                                        {
                                            bool writeAttribute = true;
                                            string value = xmlReader.Value;

                                            XmlAttributeMask xmlAttributeMask;
                                            if (attributeMaskDict.TryGetValue(xmlReader.Name, out xmlAttributeMask))
                                            {
                                                switch (xmlAttributeMask.MaskKind)
                                                {
                                                    case DebugLogMaskKind.Remove: writeAttribute = false; break;
                                                    case DebugLogMaskKind.Empty: value = string.Empty; break;
                                                    default: value = DebugLoggerMask.MaskData(value, xmlAttributeMask.MaskKind); break;
                                                }
                                            }
                                            if (writeAttribute)
                                            {
                                                xmlWriter.WriteStartAttribute(xmlReader.Prefix, xmlReader.LocalName, xmlReader.NamespaceURI);
                                                xmlWriter.WriteValue(value);
                                                xmlWriter.WriteEndAttribute();
                                            }
                                        }
                                    }
                                    xmlReader.MoveToElement(); // Move the reader back to the element node.
                                }
                                if (!manualAttributes)
                                    xmlWriter.WriteAttributes(xmlReader, true);
                            }

                            if (xmlReader.IsEmptyElement)
                            {
                                if ((emptyNodeDepth < int.MaxValue) && (xmlReader.Depth <= emptyNodeDepth))
                                    emptyNodeDepth = int.MaxValue;
                                if ((maskNodeDepth < int.MaxValue) && (xmlReader.Depth <= maskNodeDepth))
                                    maskNodeDepth = int.MaxValue;
                                if ((maskDigitsNodeDepth < int.MaxValue) && (xmlReader.Depth <= maskDigitsNodeDepth))
                                    maskDigitsNodeDepth = int.MaxValue;
                                if ((maskDigitsAppendHashNodeDepth < int.MaxValue) && (xmlReader.Depth <= maskDigitsAppendHashNodeDepth))
                                    maskDigitsAppendHashNodeDepth = int.MaxValue;
                                if ((maskHexNodeDepth < int.MaxValue) && (xmlReader.Depth <= maskHexNodeDepth))
                                    maskHexNodeDepth = int.MaxValue;
                                if ((maskHexAppendHashNodeDepth < int.MaxValue) && (xmlReader.Depth <= maskHexAppendHashNodeDepth))
                                    maskHexAppendHashNodeDepth = int.MaxValue;
                                if ((maskPanNodeDepth < int.MaxValue) && (xmlReader.Depth <= maskPanNodeDepth))
                                    maskPanNodeDepth = int.MaxValue;
                                if ((maskPanAppendHashNodeDepth < int.MaxValue) && (xmlReader.Depth <= maskPanAppendHashNodeDepth))
                                    maskPanAppendHashNodeDepth = int.MaxValue;
                                if ((maskEmbeddedPanNodeDepth < int.MaxValue) && (xmlReader.Depth <= maskEmbeddedPanNodeDepth))
                                    maskEmbeddedPanNodeDepth = int.MaxValue;
                                if ((maskEmbeddedPanAppendHashNodeDepth < int.MaxValue) && (xmlReader.Depth <= maskEmbeddedPanAppendHashNodeDepth))
                                    maskEmbeddedPanAppendHashNodeDepth = int.MaxValue;
                                xmlWriter.WriteEndElement();
                            }
                        }
                    }
                    break;

                case XmlNodeType.Text:
                    if (xmlReader.Depth < emptyNodeDepth)
                    {
                        if (xmlReader.Depth >= maskNodeDepth)
                            xmlWriter.WriteString(DebugLoggerMask.MaskData(xmlReader.Value, DebugLogMaskKind.Mask));
                        else if (xmlReader.Depth >= maskDigitsNodeDepth)
                            xmlWriter.WriteString(DebugLoggerMask.MaskData(xmlReader.Value, DebugLogMaskKind.MaskDigits));
                        else if (xmlReader.Depth >= maskDigitsAppendHashNodeDepth)
                            xmlWriter.WriteString(DebugLoggerMask.MaskData(xmlReader.Value, DebugLogMaskKind.MaskDigitsAppendHash));
                        else if (xmlReader.Depth >= maskHexNodeDepth)
                            xmlWriter.WriteString(DebugLoggerMask.MaskData(xmlReader.Value, DebugLogMaskKind.MaskHex));
                        else if (xmlReader.Depth >= maskHexAppendHashNodeDepth)
                            xmlWriter.WriteString(DebugLoggerMask.MaskData(xmlReader.Value, DebugLogMaskKind.MaskHexAppendHash));
                        else if (xmlReader.Depth >= maskPanNodeDepth)
                            xmlWriter.WriteString(DebugLoggerMask.MaskData(xmlReader.Value, DebugLogMaskKind.MaskPan));
                        else if (xmlReader.Depth >= maskPanAppendHashNodeDepth)
                            xmlWriter.WriteString(DebugLoggerMask.MaskData(xmlReader.Value, DebugLogMaskKind.MaskPanAppendHash));
                        else if (xmlReader.Depth >= maskEmbeddedPanNodeDepth)
                            xmlWriter.WriteString(DebugLoggerMask.MaskData(xmlReader.Value, DebugLogMaskKind.MaskEmbeddedPan));
                        else if (xmlReader.Depth >= maskEmbeddedPanAppendHashNodeDepth)
                            xmlWriter.WriteString(DebugLoggerMask.MaskData(xmlReader.Value, DebugLogMaskKind.MaskEmbeddedPanAppendHash));
                        else
                            xmlWriter.WriteString(xmlReader.Value);
                        skipWhitespace = false;
                    }
                    break;

                case XmlNodeType.Whitespace:
                case XmlNodeType.SignificantWhitespace:
                    if (!skipWhitespace && (xmlReader.Depth < emptyNodeDepth))
                        xmlWriter.WriteWhitespace(xmlReader.Value);
                    break;

                case XmlNodeType.CDATA:
                    if (xmlReader.Depth < emptyNodeDepth)
                    {
                        xmlWriter.WriteCData(xmlReader.Value);
                        skipWhitespace = false;
                    }
                    break;

                case XmlNodeType.EntityReference:
                    if (xmlReader.Depth < emptyNodeDepth)
                    {
                        xmlWriter.WriteEntityRef(xmlReader.Name);
                        skipWhitespace = false;
                    }
                    break;

                case XmlNodeType.XmlDeclaration:
                    //if (xmlReader.Depth < emptyNodeDepth)
                    //    xmlWriter.WriteProcessingInstruction(xmlReader.Name, xmlReader.Value);
                    skipWhitespace = true;
                    break;

                case XmlNodeType.ProcessingInstruction:
                    //if (xmlReader.Depth < emptyNodeDepth)
                    //    xmlWriter.WriteProcessingInstruction(xmlReader.Name, xmlReader.Value);
                    skipWhitespace = true;
                    break;

                case XmlNodeType.DocumentType:
                    if (xmlReader.Depth < emptyNodeDepth)
                    {
                        xmlWriter.WriteDocType(xmlReader.Name, xmlReader.GetAttribute("PUBLIC"), xmlReader.GetAttribute("SYSTEM"), xmlReader.Value);
                        skipWhitespace = false;
                    }
                    break;

                case XmlNodeType.Comment:
                    skipWhitespace = false;
                    if (xmlReader.Depth < emptyNodeDepth)
                    {
                        xmlWriter.WriteComment(xmlReader.Value);
                        skipWhitespace = false;
                    }
                    break;

                case XmlNodeType.EndElement:
                    if ((emptyNodeDepth < int.MaxValue) && (xmlReader.Depth <= emptyNodeDepth))
                        emptyNodeDepth = int.MaxValue;
                    if ((maskNodeDepth < int.MaxValue) && (xmlReader.Depth <= maskNodeDepth))
                        maskNodeDepth = int.MaxValue;
                    if ((maskDigitsNodeDepth < int.MaxValue) && (xmlReader.Depth <= maskDigitsNodeDepth))
                        maskDigitsNodeDepth = int.MaxValue;
                    if ((maskDigitsAppendHashNodeDepth < int.MaxValue) && (xmlReader.Depth <= maskDigitsAppendHashNodeDepth))
                        maskDigitsAppendHashNodeDepth = int.MaxValue;
                    if ((maskHexNodeDepth < int.MaxValue) && (xmlReader.Depth <= maskHexNodeDepth))
                        maskHexNodeDepth = int.MaxValue;
                    if ((maskHexAppendHashNodeDepth < int.MaxValue) && (xmlReader.Depth <= maskHexAppendHashNodeDepth))
                        maskHexAppendHashNodeDepth = int.MaxValue;
                    if ((maskPanNodeDepth < int.MaxValue) && (xmlReader.Depth <= maskPanNodeDepth))
                        maskPanNodeDepth = int.MaxValue;
                    if ((maskPanAppendHashNodeDepth < int.MaxValue) && (xmlReader.Depth <= maskPanAppendHashNodeDepth))
                        maskPanAppendHashNodeDepth = int.MaxValue;
                    if ((maskEmbeddedPanNodeDepth < int.MaxValue) && (xmlReader.Depth <= maskEmbeddedPanNodeDepth))
                        maskEmbeddedPanNodeDepth = int.MaxValue;
                    if ((maskEmbeddedPanAppendHashNodeDepth < int.MaxValue) && (xmlReader.Depth <= maskEmbeddedPanAppendHashNodeDepth))
                        maskEmbeddedPanAppendHashNodeDepth = int.MaxValue;
                    if (xmlReader.Depth < emptyNodeDepth)
                    {
                        xmlWriter.WriteFullEndElement();
                        skipWhitespace = false;
                    }
                    break;
            }
        }

        #endregion
    }
}