//
// System.Xml.XmlAttribute
//
// Authors:
//   Jason Diamond (jason@injektilo.org)
//   Atsushi Enomoto (ginga@kit.hi-ho.ne.jp)
//
// (C) 2002 Jason Diamond  http://injektilo.org/
// (C) 2003 Atsushi Enomoto
//

//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//

using System;
using System.Text;
using System.Xml.XPath;
using Mono.Xml;

namespace System.Xml
{
	public class XmlAttribute : XmlNode
	{
		#region Fields

		private string localName;
		private string namespaceURI;
		private string prefix;
		internal bool isDefault;
		private XmlElement ownerElement;

		#endregion

		#region Constructor

		protected internal XmlAttribute (
			string prefix, 
			string localName, 
			string namespaceURI,
			XmlDocument doc) : this (prefix, localName, namespaceURI, doc, false, true)
		{
		}

		internal XmlAttribute (
			string prefix, 
			string localName, 
			string namespaceURI,
			XmlDocument doc,
			bool atomizedNames, bool checkNamespace) : base (doc)
		{
			if (prefix == null)
				prefix = String.Empty;
			if (namespaceURI == null)
				namespaceURI = String.Empty;

			// Prefix "xml" should be also checked (http://www.w3.org/XML/xml-names-19990114-errata#NE05)
			// but MS.NET ignores such case
			if (checkNamespace) {
				if (prefix == "xmlns" || (prefix == "" && localName == "xmlns"))
					if (namespaceURI != XmlNamespaceManager.XmlnsXmlns)
						throw new ArgumentException ("Invalid attribute namespace for namespace declaration.");
				else if (prefix == "xml" && namespaceURI != XmlNamespaceManager.XmlnsXml)
					throw new ArgumentException ("Invalid attribute namespace for namespace declaration.");
			}

			// There are no means to identify the DOM is namespace-
			// aware or not, so we can only check Name validity.
			if (prefix != "" && !XmlChar.IsName (prefix))
				throw new ArgumentException ("Invalid attribute prefix.");
			else if (!XmlChar.IsName (localName))
				throw new ArgumentException ("Invalid attribute local name.");

			if (atomizedNames) {
				this.prefix = prefix;
				this.localName = localName;
				this.namespaceURI = namespaceURI;
			} else {
				this.prefix = doc.NameTable.Add (prefix);
				this.localName = doc.NameTable.Add (localName);
				this.namespaceURI = doc.NameTable.Add (namespaceURI);
			}
		}

		#endregion

		#region Properties

		public override string BaseURI {
			get { return OwnerElement != null ? OwnerElement.BaseURI : String.Empty; }
		}

		public override string InnerText {
			get {
				return base.InnerText;
			}

			set {
				Value = value;
			}
		}

		public override string InnerXml {
			get {
				// Not sure why this is an override.  Passing through for now.
				return base.InnerXml;
			}

			set {
				RemoveAll ();
				XmlNamespaceManager nsmgr = ConstructNamespaceManager ();
				XmlParserContext ctx = new XmlParserContext (OwnerDocument.NameTable, nsmgr,
					OwnerDocument.DocumentType != null ? OwnerDocument.DocumentType.DTD : null,
					BaseURI, XmlLang, XmlSpace, null);
				XmlTextReader xtr = new XmlTextReader (value, XmlNodeType.Attribute, ctx);
				xtr.XmlResolver = OwnerDocument.Resolver;
				xtr.Read ();
				OwnerDocument.ReadAttributeNodeValue (xtr, this);
			}
		}

		public override string LocalName {
			get {
				return localName;
			}
		}

		public override string Name {
			get { 
				return prefix != String.Empty ? prefix + ":" + localName : localName; 
			}
		}

		public override string NamespaceURI {
			get {
				return namespaceURI;
			}
		}

		public override XmlNodeType NodeType {
			get {
				return XmlNodeType.Attribute;
			}
		}

		internal override XPathNodeType XPathNodeType {
			get {
				return XPathNodeType.Attribute;
			}
		}

		public override XmlDocument OwnerDocument {
			get {
				return base.OwnerDocument;
			}
		}

		public virtual XmlElement OwnerElement {
			get { return ownerElement; }
		}

		public override XmlNode ParentNode {
			get {
				// It always returns null (by specification).
				return null;
			}
		}

		// We gotta do more in the set block here
		// We need to do the proper tests and throw
		// the correct Exceptions
		//
		// Wrong cases are: (1)check readonly, (2)check character validity,
		// (3)check format validity, (4)this is attribute and qualifiedName != "xmlns"
		public override string Prefix {
			set {
				if (IsReadOnly)
					throw new XmlException ("This node is readonly.");
				if (!XmlChar.IsNCName (value))
					throw new ArgumentException ("Specified name is not a valid NCName: " + value);
				if (prefix == "xmlns" && value != "xmlns")
					throw new ArgumentException ("Cannot bind to the reserved namespace.");

				prefix = OwnerDocument.NameTable.Add (value);
			}
			
			get {
				return prefix;
			}
		}

		public virtual bool Specified {
			get {
				return !isDefault;
			}
		}

		private string BuildChildValue (XmlNodeList list)
		{
			string ret = String.Empty;
			for (int i = 0; i < list.Count; i++) {
				if (list [i].NodeType == XmlNodeType.EntityReference)
					ret += BuildChildValue (list [i].ChildNodes);
				else
					ret += list [i].Value;
			}
			return ret;
		}

		public override string Value {
			get { return BuildChildValue (ChildNodes); }

			set {
				if (this.IsReadOnly)
					throw new ArgumentException ("Attempt to modify a read-only node.");
				XmlNode firstChild = FirstChild;
				if (firstChild == null)
					AppendChild (OwnerDocument.CreateTextNode (value));
				else if (FirstChild.NextSibling != null) {
					this.RemoveAll ();
					AppendChild (OwnerDocument.CreateTextNode (value));
				}
				else
					firstChild.Value = value;
				isDefault = false;
			}
		}

		internal override string XmlLang {
			get { return OwnerElement != null ? OwnerElement.XmlLang : String.Empty; }
		}

		internal override XmlSpace XmlSpace {
			get { return OwnerElement != null ? OwnerElement.XmlSpace : XmlSpace.None; }
		}

		#endregion

		#region Methods

		public override XmlNode CloneNode (bool deep)
		{
			XmlNode node = new XmlAttribute (prefix, localName, namespaceURI,
							 OwnerDocument, true, false);
			if (deep) {
				for (int i = 0; i < ChildNodes.Count; i++)
					node.AppendChild (ChildNodes [i].CloneNode (deep));
			}

			if (IsReadOnly)
				node.SetReadOnly ();
			return node;
		}

		internal void SetDefault ()
		{
			isDefault = true;
		}

		// Parent of XmlAttribute must be null
		internal void SetOwnerElement (XmlElement el) {
			ownerElement = el;
		}

		public override void WriteContentTo (XmlWriter w)
		{
			for (int i = 0; i < ChildNodes.Count; i++)
				ChildNodes [i].WriteTo (w);
		}

		public override void WriteTo (XmlWriter w)
		{
			w.WriteStartAttribute (prefix, localName, namespaceURI);
			WriteContentTo (w);
			w.WriteEndAttribute ();
		}

		internal DTDAttributeDefinition GetAttributeDefinition ()
		{
			if (OwnerElement == null)
				return null;

			// If it is default, then directly create new attribute.
			DTDAttListDeclaration attList = OwnerDocument.DocumentType != null ? OwnerDocument.DocumentType.DTD.AttListDecls [OwnerElement.Name] : null;
			return attList != null ? attList [Name] : null;
		}
		#endregion
	}
}
