//
// System.Xml.XmlNodeReaderImpl.cs - implements the core part of XmlNodeReader
//
// Author:
//	Atsushi Enomoto  (atsushi@ximian.com)
//
// (C) 2004 Novell Inc.
//

//
// 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.
//

//
// This serves the implementation part of XmlNodeReader except for
// ResolveEntity().
//

using System;
#if NET_2_0
using System.Collections.Generic;
#endif
using System.Xml;
using System.Text;
using Mono.Xml;
#if NET_2_0
using System.Xml.Schema;
#endif

namespace System.Xml
{
#if NET_2_0
	internal class XmlNodeReaderImpl : XmlReader, IHasXmlParserContext, IXmlNamespaceResolver
#else
	internal class XmlNodeReaderImpl : XmlReader, IHasXmlParserContext
#endif
	{
		XmlDocument document;
		XmlNode startNode;
		XmlNode current;
		XmlNode ownerLinkedNode;
		ReadState state = ReadState.Initial;
		int depth;
		bool isEndElement;
		bool ignoreStartNode;

		#region Constructor

		internal XmlNodeReaderImpl (XmlNodeReaderImpl entityContainer)
			: this (entityContainer.current)
		{
		}

		public XmlNodeReaderImpl (XmlNode node)
		{
			startNode = node;
			depth = 0;
			document = startNode.NodeType == XmlNodeType.Document ?
				startNode as XmlDocument : startNode.OwnerDocument;

			switch (node.NodeType) {
			case XmlNodeType.Document:
			case XmlNodeType.DocumentFragment:
			case XmlNodeType.EntityReference:
				ignoreStartNode = true;
				break;
			}

		}
		
		#endregion

		#region Properties

		public override int AttributeCount {
			get {
				if (state != ReadState.Interactive)
					return 0;

				if (isEndElement || current == null)
					return 0;
				XmlNode n = ownerLinkedNode;
				return n.Attributes != null ? n.Attributes.Count : 0;
			}
		}

		public override string BaseURI {
			get {
				if (current == null)
					return startNode.BaseURI;
				return current.BaseURI;
			}
		}

#if NET_2_0
		public override bool CanReadBinaryContent {
			get { return true; }
		}

		public override bool CanReadValueChunk {
			get { return true; }
		}
#else
		internal override bool CanReadBinaryContent {
			get { return true; }
		}

		internal override bool CanReadValueChunk {
			get { return true; }
		}
#endif

		public override bool CanResolveEntity {
			get { return false; }
		}

		public override int Depth {
			get {
				return current == null ? 0 :
					current == ownerLinkedNode ? depth :
					current.NodeType == XmlNodeType.Attribute ? depth + 1 :
					depth + 2;
			}
		}

		public override bool EOF {
			get { return state == ReadState.EndOfFile || state == ReadState.Error; }
		}

		public override bool HasAttributes {
			get {
				if (isEndElement || current == null)
					return false;

				// MS BUG: inconsistent return value between XmlTextReader and XmlNodeReader.
				// As for attribute and its descendants, XmlReader returns element's HasAttributes.
				XmlNode n = ownerLinkedNode;

				if (n.Attributes == null ||
					n.Attributes.Count == 0)
					return false;
				else
					return true;
			}
		}

#if !NET_2_1
		public override bool HasValue {
			get {
				if (current == null)
					return false;

				switch (current.NodeType) {
				case XmlNodeType.Element:
				case XmlNodeType.EntityReference:
				case XmlNodeType.Document:
				case XmlNodeType.DocumentFragment:
				case XmlNodeType.Notation:
				case XmlNodeType.EndElement:
				case XmlNodeType.EndEntity:
					return false;
				default:
					return true;
				}
			}
			      
		}
#endif

		public override bool IsDefault {
			get {
				if (current == null)
					return false;

				if (current.NodeType != XmlNodeType.Attribute)
					return false;
				else
				{
					return !((XmlAttribute) current).Specified;
				}
			}
		}

		public override bool IsEmptyElement {
			get {
				if (current == null)
					return false;

				if(current.NodeType == XmlNodeType.Element)
					return ((XmlElement) current).IsEmpty;
				else 
					return false;
			}
		}

#if NET_2_0
#else
		public override string this [int i] {
			get { return GetAttribute (i); }
		}

		public override string this [string name] {
			get { return GetAttribute (name); }
		}

		public override string this [string name, string namespaceURI] {
			get { return GetAttribute (name, namespaceURI); }
		}
#endif

		public override string LocalName {
			get {
				if (current == null)
					return String.Empty;

				switch (current.NodeType) {
				case XmlNodeType.Attribute:
				case XmlNodeType.DocumentType:
				case XmlNodeType.Element:
				case XmlNodeType.EntityReference:
				case XmlNodeType.ProcessingInstruction:
				case XmlNodeType.XmlDeclaration:
					return current.LocalName;
				}

				return String.Empty;
			}
		}

		public override string Name {
			get {
				if (current == null)
					return String.Empty;

				switch (current.NodeType) {
				case XmlNodeType.Attribute:
				case XmlNodeType.DocumentType:
				case XmlNodeType.Element:
				case XmlNodeType.EntityReference:
				case XmlNodeType.ProcessingInstruction:
				case XmlNodeType.XmlDeclaration:
					return current.Name;
				}

				return String.Empty;
			}
		}

		public override string NamespaceURI {
			get {
				if (current == null)
					return String.Empty;

				return current.NamespaceURI;
			}
		}

		public override XmlNameTable NameTable {
			get { return document.NameTable; }
		}

		public override XmlNodeType NodeType {
			get {
				if (current == null)
					return XmlNodeType.None;

				return isEndElement ? XmlNodeType.EndElement : current.NodeType;
			}
		}

		public override string Prefix {
			get { 
				if (current == null)
					return String.Empty;

				return current.Prefix;
			}
		}

#if NET_2_0
#else
		public override char QuoteChar {
			get {
				return '"';
			}
		}
#endif

		public override ReadState ReadState {
			get { return state; }
		}

#if NET_2_0
		public override IXmlSchemaInfo SchemaInfo {
			get { return current != null ? current.SchemaInfo : null; }
		}
#endif

		public override string Value {
			get {
				if (NodeType == XmlNodeType.DocumentType)
					return ((XmlDocumentType) current).InternalSubset;
				else
					return HasValue ? current.Value : String.Empty;
			}
		}

		public override string XmlLang {
			get {
				if (current == null)
					return startNode.XmlLang;

				return current.XmlLang;
			}
		}

		public override XmlSpace XmlSpace {
			get {
				if (current == null)
					return startNode.XmlSpace;

				return current.XmlSpace;
			}
		}
		#endregion

		#region Methods

		public override void Close ()
		{
			current = null;
			state = ReadState.Closed;
		}

		public override string GetAttribute (int attributeIndex)
		{
			if (NodeType == XmlNodeType.XmlDeclaration) {
				XmlDeclaration decl = current as XmlDeclaration;
				if (attributeIndex == 0)
					return decl.Version;
				else if (attributeIndex == 1) {
					if (decl.Encoding != String.Empty)
						return decl.Encoding;
					else if (decl.Standalone != String.Empty)
						return decl.Standalone;
				}
				else if (attributeIndex == 2 &&
						decl.Encoding != String.Empty && decl.Standalone != null)
					return decl.Standalone;
				throw new ArgumentOutOfRangeException ("Index out of range.");
			} else if (NodeType == XmlNodeType.DocumentType) {
				XmlDocumentType doctype = current as XmlDocumentType;
				if (attributeIndex == 0) {
					if (doctype.PublicId != "")
						return doctype.PublicId;
					else if (doctype.SystemId != "")
						return doctype.SystemId;
				} else if (attributeIndex == 1)
					if (doctype.PublicId == "" && doctype.SystemId != "")
						return doctype.SystemId;
				throw new ArgumentOutOfRangeException ("Index out of range.");
			}

			// This is MS.NET bug which returns attributes in spite of EndElement.
			if (isEndElement || current == null)
				return null;

			if (attributeIndex < 0 || attributeIndex > AttributeCount)
				throw new ArgumentOutOfRangeException ("Index out of range.");

			return ownerLinkedNode.Attributes [attributeIndex].Value;
		}

		public override string GetAttribute (string name)
		{
			// This is MS.NET bug which returns attributes in spite of EndElement.
			if (isEndElement || current == null)
				return null;

			if (NodeType == XmlNodeType.XmlDeclaration)
				return GetXmlDeclarationAttribute (name);
			else if (NodeType == XmlNodeType.DocumentType)
				return GetDocumentTypeAttribute (name);

			if (ownerLinkedNode.Attributes == null)
				return null;
			XmlAttribute attr = ownerLinkedNode.Attributes [name];
			if (attr == null)
				return null;
			else
				return attr.Value;
		}

		public override string GetAttribute (string name, string namespaceURI)
		{
			// This is MS.NET bug which returns attributes in spite of EndElement.
			if (isEndElement || current == null)
				return null;

			if (NodeType == XmlNodeType.XmlDeclaration)
				return GetXmlDeclarationAttribute (name);
			else if (NodeType == XmlNodeType.DocumentType)
				return GetDocumentTypeAttribute (name);

			if (ownerLinkedNode.Attributes == null)
				return null;
			XmlAttribute attr = ownerLinkedNode.Attributes [name, namespaceURI];
			if (attr == null)
				return null;	// In fact MS.NET returns null instead of String.Empty.
			else
				return attr.Value;
		}

		private string GetXmlDeclarationAttribute (string name)
		{
			XmlDeclaration decl = current as XmlDeclaration;
			switch (name) {
			case "version":
				return decl.Version;
			case "encoding":
				// This is MS.NET bug that XmlNodeReturns in case of string.empty.
				return decl.Encoding != String.Empty ? decl.Encoding : null;
			case "standalone":
				return decl.Standalone;
			}
			return null;
		}

		private string GetDocumentTypeAttribute (string name)
		{
			XmlDocumentType doctype = current as XmlDocumentType;
			switch (name) {
			case "PUBLIC":
				return doctype.PublicId;
			case "SYSTEM":
				return doctype.SystemId;
			}
			return null;
		}

		XmlParserContext IHasXmlParserContext.ParserContext {
			get {
				return new XmlParserContext (document.NameTable,
					current == null ?
						new XmlNamespaceManager (document.NameTable) :
						current.ConstructNamespaceManager (),
					document.DocumentType != null ? document.DocumentType.DTD : null,
					current == null ? document.BaseURI : current.BaseURI,
					XmlLang, XmlSpace, Encoding.Unicode);
			}
		}

#if NET_2_0
		public IDictionary<string, string> GetNamespacesInScope (XmlNamespaceScope scope)
		{
			IDictionary<string, string> table = new Dictionary<string, string> ();
			XmlNode n = current;
			do {
				if (n.NodeType == XmlNodeType.Document)
					break;
				for (int i = 0; i < current.Attributes.Count; i++) {
					XmlAttribute a = current.Attributes [i];
					if (a.NamespaceURI == XmlNamespaceManager.XmlnsXmlns)
						table.Add (a.Prefix == XmlNamespaceManager.PrefixXmlns ? a.LocalName : String.Empty, a.Value);
				}
				if (scope == XmlNamespaceScope.Local)
					return table;
				n = n.ParentNode;
			} while (n != null);
			if (scope == XmlNamespaceScope.All)
				table.Add (XmlNamespaceManager.PrefixXml, XmlNamespaceManager.XmlnsXml);
			return table;
		}
#endif

		private XmlElement GetCurrentElement ()
		{
			XmlElement el = null;
			switch (current.NodeType) {
			case XmlNodeType.Attribute:
				el = ((XmlAttribute) current).OwnerElement;
				break;
			case XmlNodeType.Element:
				el = (XmlElement) current;
				break;
			case XmlNodeType.Text:
			case XmlNodeType.CDATA:
			case XmlNodeType.EntityReference:
			case XmlNodeType.Comment:
			case XmlNodeType.SignificantWhitespace:
			case XmlNodeType.Whitespace:
			case XmlNodeType.ProcessingInstruction:
				el = current.ParentNode as XmlElement;
				break;
			}
			return el;
		}

		public override string LookupNamespace (string prefix)
		{
			if (current == null)
				return null;

			XmlElement el = GetCurrentElement ();

			for (; el != null; el = el.ParentNode as XmlElement) {
				for (int i = 0; i < el.Attributes.Count; i++) {
					XmlAttribute attr = el.Attributes [i];
					if (attr.NamespaceURI != XmlNamespaceManager.XmlnsXmlns)
						continue;
					if (prefix == "") {
						if (attr.Prefix == "")
							return attr.Value;
					}
					else if (attr.LocalName == prefix)
						return attr.Value;
					continue;
				}
			}

			switch (prefix) {
			case XmlNamespaceManager.PrefixXml:
				return XmlNamespaceManager.XmlnsXml;
			case XmlNamespaceManager.PrefixXmlns:
				return XmlNamespaceManager.XmlnsXmlns;
			}
			return null;
		}

#if NET_2_0
		public string LookupPrefix (string ns)
		{
			return LookupPrefix (ns, false);
		}

		public string LookupPrefix (string ns, bool atomizedNames)
		{
			if (current == null)
				return null;

			XmlElement el = GetCurrentElement ();

			for (; el != null; el = el.ParentNode as XmlElement) {
				for (int i = 0; i < el.Attributes.Count; i++) {
					XmlAttribute attr = el.Attributes [i];
					if (atomizedNames) {
						if (!Object.ReferenceEquals (attr.NamespaceURI, XmlNamespaceManager.XmlnsXmlns))
							continue;
						if (Object.ReferenceEquals (attr.Value, ns))
							// xmlns:blah="..." -> LocalName, xmlns="..." -> String.Empty
							return attr.Prefix != String.Empty ? attr.LocalName : String.Empty;
					} else {
						if (attr.NamespaceURI != XmlNamespaceManager.XmlnsXmlns)
							continue;
						if (attr.Value == ns)
							// xmlns:blah="..." -> LocalName, xmlns="..." -> String.Empty
							return attr.Prefix != String.Empty ? attr.LocalName : String.Empty;
					}
				}
			}
			switch (ns) {
			case XmlNamespaceManager.XmlnsXml:
				return XmlNamespaceManager.PrefixXml;
			case XmlNamespaceManager.XmlnsXmlns:
				return XmlNamespaceManager.PrefixXmlns;
			}
			return null;
		}
#endif

		public override void MoveToAttribute (int attributeIndex)
		{
			if (isEndElement || attributeIndex < 0 || attributeIndex > AttributeCount)
				throw new ArgumentOutOfRangeException ();
			
			state = ReadState.Interactive;
			current = ownerLinkedNode.Attributes [attributeIndex];
		}

		public override bool MoveToAttribute (string name)
		{
			if (isEndElement || current == null)
				return false;
			XmlNode tmpCurrent = current;
			if (current.ParentNode.NodeType == XmlNodeType.Attribute)
				current = current.ParentNode;

			if (ownerLinkedNode.Attributes == null)
				return false;
			XmlAttribute attr = ownerLinkedNode.Attributes [name];
			if (attr == null) {
				current = tmpCurrent;
				return false;
			}
			else {
				current = attr;
				return true;
			}
		}

		public override bool MoveToAttribute (string name, string namespaceURI)
		{
			if (isEndElement || current == null)
				return false;

			if (ownerLinkedNode.Attributes == null)
				return false;
			XmlAttribute attr = ownerLinkedNode.Attributes [name, namespaceURI];
			if (attr == null)
				return false;
			else {
				current = attr;
				return true;
			}
		}

		public override bool MoveToElement ()
		{
			if (current == null)
				return false;
			XmlNode n = ownerLinkedNode;
			if (current != n) {
				current = n;
				return true;
			} else 
				return false;
		}

		public override bool MoveToFirstAttribute ()
		{
			if (current == null)
				return false;

			if (ownerLinkedNode.Attributes == null)
				return false;
			if(ownerLinkedNode.Attributes.Count > 0)
			{
				current = ownerLinkedNode.Attributes [0];
				return true;
			}
			else
				return false;
		}

		public override bool MoveToNextAttribute ()
		{
			if (current == null)
				return false;

			if (current.NodeType != XmlNodeType.Attribute)
				return MoveToFirstAttribute ();
			else
			{
				XmlAttributeCollection ac = ((XmlAttribute) current).OwnerElement.Attributes;
				for (int i=0; i<ac.Count-1; i++)
				{
					XmlAttribute attr = ac [i];
					if (attr == current)
					{
						i++;
						if (i == ac.Count)
							return false;
						current = ac [i];
						return true;
					}
				}
				return false;
			}
		}

		public override bool Read ()
		{
			// FIXME: at some stage inlining might work effectively.
			// if (EOF)
			switch (state) {
			case ReadState.EndOfFile:
			case ReadState.Error:
			case ReadState.Closed:
				return false;
			}

#if NET_2_0
			if (Binary != null)
				Binary.Reset ();
#endif

			bool ret = ReadContent ();
			ownerLinkedNode = current;
			return ret;
		}

		bool ReadContent ()
		{
			if (ReadState == ReadState.Initial) {
				current = startNode;
				state = ReadState.Interactive;
				// when startNode is document or fragment
				if (ignoreStartNode)
					current = startNode.FirstChild;
				if (current == null) {
					state = ReadState.Error;
					return false;
				} else
					return true;
			}

			MoveToElement ();

			// don't step into EntityReference's children. Also
			// avoid re-entering children of already-consumed
			// element (i.e. when it is regarded as EndElement).
			XmlNode firstChild =
				!isEndElement && current.NodeType != XmlNodeType.EntityReference ?
				current.FirstChild : null;
			if (firstChild != null) {
				isEndElement = false;
				current = firstChild;
				depth++;
				return true;
			}

			if (current == startNode) { // Currently it is on the start node.
				if (IsEmptyElement || isEndElement) {
					// The start node is already consumed.
					isEndElement = false;
					current = null;
					state = ReadState.EndOfFile;
					return false;
				} else {
					// The start node is the only element
					// which should be processed. Now it
					// is set as EndElement.
					isEndElement = true;
					return true;
				}
			}
			if (!isEndElement && !IsEmptyElement &&
			    current.NodeType == XmlNodeType.Element) {
				// element, currently not EndElement, and has
				// no child. (such as <foo></foo>, which
				// should become EndElement).
				isEndElement = true;
				return true;
			}

			// If NextSibling is available, move to there.
			XmlNode next = current.NextSibling;
			if (next != null) {
				isEndElement = false;
				current = next;
				return true;
			}

			// Otherwise, parent.
			XmlNode parent = current.ParentNode;
			if (parent == null || parent == startNode && ignoreStartNode) {
				// Parent is not available, or reached to
				// the start node. This reader never sets 
				// startNode as current if it was originally 
				// ignored (e.g. startNode is XmlDocument).
				isEndElement = false;
				current = null;
				state = ReadState.EndOfFile;
				return false;
			} else {
				// Parent was available, so return it as
				// EndElement.
				current = parent;
				depth--;
				isEndElement = true;
				return true;
			}
		}

#if !NET_2_1
		public override bool ReadAttributeValue ()
		{
			if (current.NodeType == XmlNodeType.Attribute) {
				if (current.FirstChild == null)
					return false;
				current = current.FirstChild;
				return true;
			} else if (current.ParentNode.NodeType == XmlNodeType.Attribute) {
				if (current.NextSibling == null)
					return false;
				current = current.NextSibling;
				return true;
			} else
				return false;
		}
#endif

		public override string ReadString ()
		{
			return base.ReadString ();
		}

#if !NET_2_1
		public override void ResolveEntity ()
		{
			throw new NotSupportedException ("Should not happen.");
		}
#endif

		public override void Skip ()
		{
			// Why is this overriden? Such skipping might raise
			// (or ignore) unexpected validation error.
			base.Skip ();
		}
		#endregion
	}
}
