//
// System.Xml.XmlValidatingReader.cs
//
// Author:
//   Tim Coleman (tim@timcoleman.com)
//   Atsushi Enomoto (ginga@kit.hi-ho.ne.jp)
//
// Copyright (C) Tim Coleman, 2002
// (C)2003 Atsushi Enomoto
//

using System.IO;
using System.Security.Policy;
using System.Text;
using System.Xml.Schema;
using Mono.Xml;
using Mono.Xml.Schema;

namespace System.Xml {
	public class XmlValidatingReader : XmlReader, IXmlLineInfo {

		#region Fields

		EntityHandling entityHandling;
		XmlReader sourceReader;
		XmlTextReader xmlTextReader;
		XmlReader validatingReader;
		XmlResolver resolver; // Only used to non-XmlTextReader XmlReader
		bool specifiedResolver;
		ValidationType validationType;
		XmlSchemaCollection schemas;
		DTDValidatingReader dtdReader;
		IHasXmlSchemaInfo schemaInfo;
		StringBuilder storedCharacters;

		#endregion // Fields

		#region Constructors

		public XmlValidatingReader (XmlReader reader)
		{
			sourceReader = reader;
			xmlTextReader = reader as XmlTextReader;
			if (xmlTextReader == null)
				resolver = new XmlUrlResolver ();
			entityHandling = EntityHandling.ExpandEntities;
			validationType = ValidationType.Auto;
			schemas = new XmlSchemaCollection ();
			storedCharacters = new StringBuilder ();
		}

		public XmlValidatingReader (Stream xmlFragment, XmlNodeType fragType, XmlParserContext context)
			: this (new XmlTextReader (xmlFragment, fragType, context))
		{
		}

		public XmlValidatingReader (string xmlFragment, XmlNodeType fragType, XmlParserContext context)
			: this (new XmlTextReader (xmlFragment, fragType, context))
		{
		}

		#endregion // Constructors

		#region Properties

		public override int AttributeCount {
			get { return validatingReader == null ? 0 : validatingReader.AttributeCount; }
		}

		public override string BaseURI {
			get { return validatingReader == null ? sourceReader.BaseURI : validatingReader.BaseURI; }
		}

		// This property for this class always return true.
		public override bool CanResolveEntity {
			get { return true; }
		}

		public override int Depth { 
			get { return validatingReader == null ? 0 : validatingReader.Depth; }
		}

		public Encoding Encoding {
			get {
				if (xmlTextReader != null)
					return xmlTextReader.Encoding;
				else
					throw new NotSupportedException ("Encoding is supported only for XmlTextReader.");
			}
		}

		public EntityHandling EntityHandling {
			get { return entityHandling; }
			set { entityHandling = value; }
		}

		public override bool EOF { 
			get { return validatingReader == null ? false : validatingReader.EOF; }
		}

#if NET_1_2
		[MonoTODO]
		public override Evidence [] Evidences {
			get { throw new NotImplementedException ();
			}
		}
#endif

		public override bool HasValue { 
			get { return validatingReader == null ? false : validatingReader.HasValue; }
		}

		public override bool IsDefault {
			get { return validatingReader == null ? false : validatingReader.IsDefault; }
		}

		public override bool IsEmptyElement { 
			get { return validatingReader == null ? false : validatingReader.IsEmptyElement; }
		}

		public override string this [int i] { 
			get {
				if (validatingReader == null)
					throw new IndexOutOfRangeException ("Reader is not started.");
				return validatingReader [i];
			}
		}

		public override string this [string name] { 
			get { return validatingReader == null ? null : validatingReader [name]; }
		}

		public override string this [string localName, string namespaceName] { 
			get { return validatingReader == null ? null : validatingReader [localName, namespaceName]; }
		}

		int IXmlLineInfo.LineNumber {
			get {
				if (IsDefault)
					return 0;
				IXmlLineInfo info = validatingReader as IXmlLineInfo;
				return info != null ? info.LineNumber : 0;
			}
		}

		int IXmlLineInfo.LinePosition {
			get {
				if (IsDefault)
					return 0;
				IXmlLineInfo info = validatingReader as IXmlLineInfo;
				return info != null ? info.LinePosition : 0;
			}
		}

		public override string LocalName { 
			get {
				if (validatingReader == null)
					return String.Empty;
				else if (Namespaces)
					return validatingReader.LocalName;
				else
					return validatingReader.Name;
			}
		}

		public override string Name {
			get { return validatingReader == null ? String.Empty : validatingReader.Name; }
		}

		public bool Namespaces {
			get {
				if (xmlTextReader != null)
					return xmlTextReader.Namespaces;
				else
					return true;
			}
			set {
				if (ReadState != ReadState.Initial)
					throw new InvalidOperationException ("Namespaces have to be set before reading.");

				if (xmlTextReader != null)
					xmlTextReader.Namespaces = value;
				else
					throw new NotSupportedException ("Property 'Namespaces' is supported only for XmlTextReader.");
			}
		}

		public override string NamespaceURI { 
			get {
				if (validatingReader == null)
					return String.Empty;
				else if (Namespaces)
					return validatingReader.NamespaceURI;
				else
					return String.Empty;
			}
		}

		public override XmlNameTable NameTable { 
			get { return validatingReader == null ? sourceReader.NameTable : validatingReader.NameTable; }
		}

		public override XmlNodeType NodeType { 
			get { return validatingReader == null ? XmlNodeType.None : validatingReader.NodeType; }
		}

		public override string Prefix {
			get { return validatingReader == null ? String.Empty : validatingReader.Prefix; }
		}

		public override char QuoteChar { 
			get { return validatingReader == null ? sourceReader.QuoteChar : validatingReader.QuoteChar; }
		}

		public XmlReader Reader {
			get { return sourceReader; }
		}

		public override ReadState ReadState { 
			get {
				if (validatingReader == null)
					return ReadState.Initial;
				return validatingReader.ReadState; 
			}
		}

		internal XmlResolver Resolver {
			get {
				// This is special rule... MS.NET shares the
				// XmlResolver between XmlTextReader and
				// XmlValidatingReader, so we mimick that
				// silly behavior here.
				if (this.xmlTextReader != null)
					return this.xmlTextReader.Resolver;
				else
					return resolver;
			}
		}

		public XmlSchemaCollection Schemas {
			get { return schemas; }
		}

		public object SchemaType {
			get { return schemaInfo.SchemaType; }
		}

//		[MonoTODO] We decided not to support XDR schema. It is obsolete.
		public ValidationType ValidationType {
			get { return validationType; }
			set {
				if (ReadState != ReadState.Initial)
					throw new InvalidOperationException ("ValidationType cannot be set after the first call to Read method.");
				switch (validationType) {
				case ValidationType.Auto:
				case ValidationType.DTD:
				case ValidationType.None:
				case ValidationType.Schema:
					validationType = value; 
					break;
				case ValidationType.XDR:
					throw new NotSupportedException ();
				}
			}
		}

		public override string Value {
			get { return validatingReader == null ? String.Empty : validatingReader.Value; }
		}

		public override string XmlLang {
			get { return validatingReader == null ? String.Empty : validatingReader.XmlLang; }
		}

		public XmlResolver XmlResolver {
			set {
				specifiedResolver = true;
				if (xmlTextReader != null)
					xmlTextReader.XmlResolver = value;
				else
					resolver = value;

				XsdValidatingReader xsvr = validatingReader as XsdValidatingReader;
				if (xsvr != null)
					xsvr.XmlResolver = value;
				DTDValidatingReader dvr = validatingReader as DTDValidatingReader;
				if (dvr != null)
					dvr.XmlResolver = value;
			}
		}

		public override XmlSpace XmlSpace {
			get { return validatingReader == null ? XmlSpace.None : validatingReader.XmlSpace; }
		}

		#endregion // Properties

		#region Methods

		public override void Close ()
		{
			if (validatingReader == null)
				sourceReader.Close ();
			else
				validatingReader.Close ();
		}

		public override string GetAttribute (int i)
		{
			return this [i];
		}

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

		public override string GetAttribute (string localName, string namespaceName)
		{
			return this [localName, namespaceName];
		}

		internal XmlParserContext GetInternalParserContext ()
		{
			return dtdReader != null ?
				dtdReader.ParserContext : null;
		}

		bool IXmlLineInfo.HasLineInfo ()
		{
			IXmlLineInfo info = validatingReader as IXmlLineInfo;
			return info != null ? info.HasLineInfo () : false;
		}

		public override string LookupNamespace (string prefix)
		{
			if (validatingReader != null)
				return sourceReader.LookupNamespace (prefix);
			else
				return validatingReader.LookupNamespace (prefix);
		}

		public override void MoveToAttribute (int i)
		{
			if (validatingReader == null)
				throw new IndexOutOfRangeException ("Reader is not started.");
			else
				validatingReader.MoveToAttribute (i);
		}

		public override bool MoveToAttribute (string name)
		{
			if (validatingReader == null)
				return false;
			return validatingReader.MoveToAttribute (name);
		}

		public override bool MoveToAttribute (string localName, string namespaceName)
		{
			if (validatingReader == null)
				return false;
			return validatingReader.MoveToAttribute (localName, namespaceName);
		}

		public override bool MoveToElement ()
		{
			if (validatingReader == null)
				return false;
			return validatingReader.MoveToElement ();
		}

		public override bool MoveToFirstAttribute ()
		{
			if (validatingReader == null)
				return false;
			return validatingReader.MoveToFirstAttribute ();
		}

		public override bool MoveToNextAttribute ()
		{
			if (validatingReader == null)
				return false;
			return validatingReader.MoveToNextAttribute ();
		}

//		[MonoTODO] We decided not to support XDR schema. It is obsolete.
		public override bool Read ()
		{
			if (ReadState == ReadState.Initial) {
				switch (ValidationType) {
				case ValidationType.Auto:
				case ValidationType.None:
					goto case ValidationType.Schema; // might be specified by xsi:schemaLocation.
				case ValidationType.DTD:
					validatingReader = dtdReader = new DTDValidatingReader (sourceReader, this);
					dtdReader.XmlResolver = Resolver;
					break;
				case ValidationType.Schema:
					dtdReader = new DTDValidatingReader (sourceReader, this);
					XsdValidatingReader xsvr = new XsdValidatingReader (dtdReader, this);
					foreach (XmlSchema schema in Schemas)
						xsvr.Schemas.Add (schema);
					validatingReader = xsvr;
					xsvr.XmlResolver = Resolver;
					dtdReader.XmlResolver = Resolver;
					break;
				case ValidationType.XDR:
					throw new NotSupportedException ();
				}
				schemaInfo = validatingReader as IHasXmlSchemaInfo;
			}
			return validatingReader.Read ();
		}

		public override bool ReadAttributeValue ()
		{
			if (validatingReader == null)
				return false;
			return validatingReader.ReadAttributeValue ();
		}

#if NET_1_0
		// LAMESPEC: MS.NET 1.0 has critical bug here.
		// After calling these methods, validation does not work anymore!
		public override string ReadInnerXml ()
		{
			if (validatingReader == null)
				return "";
			return validatingReader.ReadInnerXml ();
		}

		public override string ReadOuterXml ()
		{
			if (validatingReader == null)
				return "";
			return validatingReader.ReadOuterXml ();
		}
#endif

#if NET_1_0
		public override string ReadString ()
		{
			return base.ReadStringInternal ();
		}
#else
		public override string ReadString ()
		{
			return base.ReadString ();
		}
#endif

		public object ReadTypedValue ()
		{
			if (dtdReader == null)
				return null;
			XmlSchemaDatatype dt = schemaInfo.SchemaType as XmlSchemaDatatype;
			if (dt == null)
				return null;
			switch (NodeType) {
			case XmlNodeType.Element:
				if (IsEmptyElement)
					return null;

				storedCharacters.Length = 0;
				bool loop = true;
				do {
					Read ();
					switch (NodeType) {
					case XmlNodeType.SignificantWhitespace:
					case XmlNodeType.Text:
					case XmlNodeType.CDATA:
						storedCharacters.Append (Value);
						break;
					case XmlNodeType.Comment:
						break;
					default:
						loop = false;
						break;
					}
				} while (loop && !EOF);
				return dt.ParseValue (storedCharacters.ToString (), NameTable, dtdReader.ParserContext.NamespaceManager);
			case XmlNodeType.Attribute:
				return dt.ParseValue (Value, NameTable, dtdReader.ParserContext.NamespaceManager);
			}
			return null;
		}

		public override void ResolveEntity ()
		{
			validatingReader.ResolveEntity ();
		}

		// It should be "protected" as usual "event model"
		// methods are, but validation event is not exposed,
		// so it is no other way to make it "internal".
		internal void OnValidationEvent (object o, ValidationEventArgs e)
		{
			if (ValidationEventHandler != null)
				ValidationEventHandler (o, e);
			else if (ValidationType != ValidationType.None && e.Severity == XmlSeverityType.Error)
				throw e.Exception;
		}
		#endregion // Methods

		#region Events and Delegates

		public event ValidationEventHandler ValidationEventHandler;

		#endregion // Events and Delegates
	}
}
