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

using System;
using System.Xml.Schema;
using System.Xml.XPath;
using System.Collections;
using System.Diagnostics;
using System.Globalization;

namespace System.Xml {
    // Represents an element.
    public class XmlElement : XmlLinkedNode {
        XmlName name;
        XmlAttributeCollection attributes;
        XmlLinkedNode lastChild; // == this for empty elements otherwise it is the last child

        internal XmlElement( XmlName name, bool empty, XmlDocument doc ): base( doc ) {
            Debug.Assert(name!=null);
            this.parentNode = null;
            if ( !doc.IsLoading ) {
                XmlDocument.CheckName( name.Prefix );
                XmlDocument.CheckName( name.LocalName );
            }
            if (name.LocalName.Length == 0) 
                throw new ArgumentException(Res.GetString(Res.Xdom_Empty_LocalName));
            this.name = name;
            if (empty) {
                this.lastChild = this; 
            }
        }

        protected internal XmlElement( string prefix, string localName, string namespaceURI, XmlDocument doc ) 
        : this( doc.AddXmlName( prefix, localName, namespaceURI, null ), true, doc ) {
        }

        internal XmlName XmlName {
            get { return name; }
            set { name = value; }
        }

        // Creates a duplicate of this node.
        public override XmlNode CloneNode(bool deep) {
            Debug.Assert( OwnerDocument != null );
            XmlDocument doc = OwnerDocument;
            bool OrigLoadingStatus = doc.IsLoading;
            doc.IsLoading = true;
            XmlElement element = doc.CreateElement( Prefix, LocalName, NamespaceURI );
            doc.IsLoading = OrigLoadingStatus;
            if ( element.IsEmpty != this.IsEmpty )
                element.IsEmpty = this.IsEmpty;

            if (HasAttributes) {
                foreach( XmlAttribute attr in Attributes ) {
                    XmlAttribute newAttr = (XmlAttribute)(attr.CloneNode(true));
                    if (attr is XmlUnspecifiedAttribute && attr.Specified == false)
                        ( ( XmlUnspecifiedAttribute )newAttr).SetSpecified(false);
                    element.Attributes.InternalAppendAttribute( newAttr );
                }
            }
            if (deep)
                element.CopyChildren( doc, this, deep );

            return element;
        }

        // Gets the name of the node.
        public override string Name { 
            get { return name.Name;}
        }

        // Gets the name of the current node without the namespace prefix.
        public override string LocalName { 
            get { return name.LocalName;}
        }

        // Gets the namespace URI of this node.
        public override string NamespaceURI { 
            get { return name.NamespaceURI;} 
        }

        // Gets or sets the namespace prefix of this node.
        public override string Prefix { 
            get { return name.Prefix;}
            set { name = name.OwnerDocument.AddXmlName( value, LocalName, NamespaceURI, SchemaInfo ); }
        }

        // Gets the type of the current node.
        public override XmlNodeType NodeType {
            get { return XmlNodeType.Element;}
        }

        public override XmlNode ParentNode {
            get {
                return this.parentNode;
            }
        }

        // Gets the XmlDocument that contains this node.
        public override XmlDocument OwnerDocument { 
            get { 
                return name.OwnerDocument;
            }
        }

        internal override bool IsContainer {
            get { return true;}
        }

        //the function is provided only at Load time to speed up Load process
        internal override XmlNode AppendChildForLoad(XmlNode newChild, XmlDocument doc) {
            XmlNodeChangedEventArgs args = doc.GetInsertEventArgsForLoad( newChild, this );

            if (args != null)
                doc.BeforeEvent( args );

            XmlLinkedNode newNode = (XmlLinkedNode)newChild;

            if (lastChild == null 
                || lastChild == this) { // if LastNode == null 
                newNode.next = newNode;
                lastChild = newNode; // LastNode = newNode;
                newNode.SetParentForLoad(this);
            }
            else {
                XmlLinkedNode refNode = lastChild; // refNode = LastNode;
                newNode.next = refNode.next;
                refNode.next = newNode;
                lastChild = newNode; // LastNode = newNode;
                if (refNode.IsText
                    && newNode.IsText) {
                    NestTextNodes(refNode, newNode);
                }
                else {
                    newNode.SetParentForLoad(this);
                }
            }

            if (args != null)
                doc.AfterEvent( args );

            return newNode;
        }

        // Gets or sets whether the element does not have any children.
        public bool IsEmpty {
            get { 
                return lastChild == this;
            }

            set {
                if (value) {
                    if (lastChild != this) {
                        RemoveAllChildren();
                        lastChild = this;
                    }
                }
                else {
                    if (lastChild == this) {
                        lastChild = null;
                    }
                }

            }
        }

        internal override XmlLinkedNode LastNode {
            get { 
                return lastChild == this ? null : lastChild; 
            }

            set { 
                lastChild = value;
            }
        }

        internal override bool IsValidChildType( XmlNodeType type ) {
            switch (type) {
                case XmlNodeType.Element:
                case XmlNodeType.Text:
                case XmlNodeType.EntityReference:
                case XmlNodeType.Comment:
                case XmlNodeType.Whitespace:
                case XmlNodeType.SignificantWhitespace:
                case XmlNodeType.ProcessingInstruction:
                case XmlNodeType.CDATA:
                    return true;

                default:
                    return false;
            }
        }


        // Gets a XmlAttributeCollection containing the list of attributes for this node.
        public override XmlAttributeCollection Attributes { 
            get { 
                if (attributes == null) {
                    lock ( OwnerDocument.objLock ) {
                        if ( attributes == null ) {
                            attributes = new XmlAttributeCollection(this);
                        }
                    }
                }

                return attributes; 
            }
        }

        // Gets a value indicating whether the current node
        // has any attributes.
        public virtual bool HasAttributes {
            get {
                if ( this.attributes == null )
                    return false;
                else
                    return this.attributes.Count > 0;
            }
        }

        // Returns the value for the attribute with the specified name.
        public virtual string GetAttribute(string name) {
            XmlAttribute attr = GetAttributeNode(name);
            if (attr != null)
                return attr.Value;
            return String.Empty;
        }

        // Sets the value of the attribute
        // with the specified name.
        public virtual void SetAttribute(string name, string value) {
            XmlAttribute attr = GetAttributeNode(name);
            if (attr == null) {
                attr = OwnerDocument.CreateAttribute(name);
                attr.Value = value;
                Attributes.InternalAppendAttribute( attr );
            }
            else {
                attr.Value = value;
            }
        }

        // Removes an attribute by name.
        public virtual void RemoveAttribute(string name) {
            if (HasAttributes)
                Attributes.RemoveNamedItem(name);
        }

        // Returns the XmlAttribute with the specified name.
        public virtual XmlAttribute GetAttributeNode(string name) {
            if (HasAttributes)
                return Attributes[name];
            return null;
        }

        // Adds the specified XmlAttribute.
        public virtual XmlAttribute SetAttributeNode(XmlAttribute newAttr) {
            if ( newAttr.OwnerElement != null )
                throw new InvalidOperationException( Res.GetString(Res.Xdom_Attr_InUse) );
            return(XmlAttribute) Attributes.SetNamedItem(newAttr);
        }

        // Removes the specified XmlAttribute.
        public virtual XmlAttribute RemoveAttributeNode(XmlAttribute oldAttr) {
            if (HasAttributes)
                return(XmlAttribute) Attributes.Remove(oldAttr);
            return null;
        }

        // Returns a XmlNodeList containing
        // a list of all descendant elements that match the specified name.
        public virtual XmlNodeList GetElementsByTagName(string name) {
            return new XmlElementList( this, name );
        }

        //
        // DOM Level 2
        //

        // Returns the value for the attribute with the specified LocalName and NamespaceURI.
        public virtual string GetAttribute(string localName, string namespaceURI) {
            XmlAttribute attr = GetAttributeNode( localName, namespaceURI );
            if (attr != null)
                return attr.Value;
            return String.Empty;
        }

        // Sets the value of the attribute with the specified name
        // and namespace.
        public virtual string SetAttribute(string localName, string namespaceURI, string value) {
            XmlAttribute attr = GetAttributeNode( localName, namespaceURI );
            if (attr == null) {
                attr = OwnerDocument.CreateAttribute( string.Empty, localName, namespaceURI );
                attr.Value = value;
                Attributes.InternalAppendAttribute( attr );
            }
            else {
                attr.Value = value;
            }

            return value;
        }

        // Removes an attribute specified by LocalName and NamespaceURI.
        public virtual void RemoveAttribute(string localName, string namespaceURI) {
            //Debug.Assert(namespaceURI != null);
            RemoveAttributeNode( localName, namespaceURI );
        }

        // Returns the XmlAttribute with the specified LocalName and NamespaceURI.
        public virtual XmlAttribute GetAttributeNode(string localName, string namespaceURI) {
            //Debug.Assert(namespaceURI != null);
            if (HasAttributes)
                return Attributes[ localName, namespaceURI ];
            return null;
        }

        // Adds the specified XmlAttribute.
        public virtual XmlAttribute SetAttributeNode(string localName, string namespaceURI) {
            XmlAttribute attr = GetAttributeNode( localName, namespaceURI );
            if (attr == null) {
                attr = OwnerDocument.CreateAttribute( string.Empty, localName, namespaceURI );
                Attributes.InternalAppendAttribute( attr );
            }
            return attr;
        }

        // Removes the XmlAttribute specified by LocalName and NamespaceURI.
        public virtual XmlAttribute RemoveAttributeNode(string localName, string namespaceURI) {
            //Debug.Assert(namespaceURI != null);
            if (HasAttributes) {
                XmlAttribute attr = GetAttributeNode( localName, namespaceURI );
                Attributes.Remove( attr );
                return attr;
            }
            return null;
        }

        // Returns a XmlNodeList containing 
        // a list of all descendant elements that match the specified name.
        public virtual XmlNodeList GetElementsByTagName(string localName, string namespaceURI) {
            //Debug.Assert(namespaceURI != null);
            return new XmlElementList( this, localName, namespaceURI );
        }

        // Determines whether the current node has the specified attribute.
        public virtual bool HasAttribute(string name) {
            return GetAttributeNode(name) != null;
        }

        // Determines whether the current node has the specified
        // attribute from the specified namespace.
        public virtual bool HasAttribute(string localName, string namespaceURI) {
            return GetAttributeNode(localName, namespaceURI) != null;
        }

        // Saves the current node to the specified XmlWriter.
        public override void WriteTo(XmlWriter w) {

            if (GetType() == typeof(XmlElement)) {
                // Use the non-recursive version (for XmlElement only)
                WriteElementTo(w, this);
            }
            else {
                // Use the (potentially) recursive version
                WriteStartElement(w);

                if (IsEmpty) {
                    w.WriteEndElement();
                }
                else {
                    WriteContentTo(w);
                    w.WriteFullEndElement();
                }
            }
        }

        // This method is copied from System.Xml.Linq.ElementWriter.WriteElement but adapted to DOM
        private static void WriteElementTo(XmlWriter writer, XmlElement e) {
            XmlNode root = e;
            XmlNode n = e;
            while (true) {
                e = n as XmlElement;
                // Only use the inlined write logic for XmlElement, not for derived classes
                if (e != null && e.GetType() == typeof(XmlElement)) {
                    // Write the element
                    e.WriteStartElement(writer);
                    // Write the element's content
                    if (e.IsEmpty) {
                        // No content; use a short end element <a />
                        writer.WriteEndElement();
                    }
                    else if (e.lastChild == null) {
                        // No actual content; use a full end element <a></a>
                        writer.WriteFullEndElement();
                    }
                    else {
                        // There are child node(s); move to first child
                        n = e.FirstChild;
                        Debug.Assert(n != null);
                        continue;
                    }
                }
                else {
                    // Use virtual dispatch (might recurse)
                    n.WriteTo(writer);
                }
                // Go back to the parent after writing the last child
                while (n != root && n == n.ParentNode.LastChild) {
                    n = n.ParentNode;
                    Debug.Assert(n != null);
                    writer.WriteFullEndElement();
                }
                if (n == root)
                    break;
                n = n.NextSibling;
                Debug.Assert(n != null);
            }
        }

        // Writes the start of the element (and its attributes) to the specified writer
        private void WriteStartElement(XmlWriter w) {
            w.WriteStartElement(Prefix, LocalName, NamespaceURI);

            if (HasAttributes) {
                XmlAttributeCollection attrs = Attributes;
                for (int i = 0; i < attrs.Count; i += 1) {
                    XmlAttribute attr = attrs[i];
                    attr.WriteTo(w);
                }
            }
        }

        // Saves all the children of the node to the specified XmlWriter.
        public override void WriteContentTo(XmlWriter w) {
            for (XmlNode node = FirstChild; node != null; node = node.NextSibling) {
                node.WriteTo(w);
            }
        }

        // Removes the attribute node with the specified index from the attribute collection.
        public virtual XmlNode RemoveAttributeAt(int i) {
            if (HasAttributes)
                return attributes.RemoveAt( i );
            return null;
        }

        // Removes all attributes from the element.
        public virtual void RemoveAllAttributes() {
            if (HasAttributes) {
                attributes.RemoveAll();
            }
        }

        // Removes all the children and/or attributes
        // of the current node.
        public override void RemoveAll() {
            //remove all the children
            base.RemoveAll(); 
            //remove all the attributes
            RemoveAllAttributes();
        }
        
        internal void RemoveAllChildren() {
            base.RemoveAll();
        }

        public override IXmlSchemaInfo SchemaInfo {
            get {
                return name;
            }
        }

        // Gets or sets the markup representing just
        // the children of this node.
        public override string InnerXml {
            get {
                return base.InnerXml;
            }
            set {
                RemoveAllChildren();
                XmlLoader loader = new XmlLoader();
                loader.LoadInnerXmlElement( this, value );
            }
        }

        // Gets or sets the concatenated values of the
        // node and all its children.
        public override string InnerText { 
            get {
                return base.InnerText;
            }
            set {
                XmlLinkedNode linkedNode = LastNode;
                if (linkedNode != null && //there is one child
                    linkedNode.NodeType == XmlNodeType.Text && //which is text node
                    linkedNode.next == linkedNode ) // and it is the only child 
                {
                    //this branch is for perf reason, event fired when TextNode.Value is changed.
                    linkedNode.Value = value;
                } 
                else {
                    RemoveAllChildren();
                    AppendChild( OwnerDocument.CreateTextNode( value ) );
                }
            }
        }

        public override XmlNode NextSibling { 
            get { 
                if (this.parentNode != null
                    && this.parentNode.LastNode != this)
                    return next;
                return null; 
            } 
        }

        internal override void SetParent(XmlNode node) {
            this.parentNode = node;
        }

        internal override XPathNodeType XPNodeType { get { return XPathNodeType.Element; } }

        internal override string XPLocalName { get { return LocalName; } }

        internal override string GetXPAttribute( string localName, string ns ) {
            if ( ns == OwnerDocument.strReservedXmlns )
                return null;
            XmlAttribute attr = GetAttributeNode( localName, ns );
            if ( attr != null )
                return attr.Value;
            return string.Empty;
        }
    }
}
