using System;
using System.Xml;
using NUnit.Framework;

namespace MonoTests
{
	public class XmlComparer
	{
		[Flags]
		public enum Flags {
			IgnoreNone=0,
			IgnoreAttribOrder=1,
		}
		Flags flags;
		bool ignoreWS = true;

		string lastCompare = "";
		string _actual = "";
		string _expected = "";

		public XmlComparer (Flags flags, bool ignoreWS) 
		{
			this.flags = flags;
			this.ignoreWS = ignoreWS;
		}

		public XmlComparer (Flags flags) : this (flags, true)
		{
		}

		public XmlComparer () : this (Flags.IgnoreAttribOrder)
		{
		}

		public bool AreEqualAttribs (XmlAttributeCollection expected, XmlAttributeCollection actual)
		{
			if (expected.Count != actual.Count)
				return false;
			for (int i=0; i<expected.Count; i++) {
				if ((flags & Flags.IgnoreAttribOrder) != 0) {
					string ln = expected[i].LocalName;
					string ns = expected[i].NamespaceURI;
					string val = expected[i].Value;
					_expected = ns+":"+ln+"="+val;
					XmlAttribute atr2 = actual[ln, ns];
					_actual = atr2 == null ? "<<missing>>" : ns + ":" + ln + "=" + atr2.Value;
					if (atr2 == null || atr2.Value.Trim().ToLower() != val.Trim().ToLower())
						return false;
				} else {
					if (expected [i].LocalName != actual [i].LocalName)
						return false;
					if (expected [i].NamespaceURI != actual [i].NamespaceURI)
						return false;
					if (expected [i].Value.Trim().ToLower() != actual [i].Value.Trim().ToLower())
						return false;
				}
			}
			return true;
		}

		public bool AreEqualNodeList (XmlNodeList expected, XmlNodeList actual)
		{
			if (expected.Count != actual.Count)
				return false;
			for (int i=0; i<expected.Count; i++) {
				if (!AreEqual (expected[i], actual[i]))
					return false;
			}
			return true;
		}

		public bool AreEqual (XmlNode expected, XmlNode actual)
		{
			lastCompare = expected.OuterXml + "\n" + actual.OuterXml;
			_actual = actual.OuterXml;
			_expected = expected.OuterXml;
			// skip XmlDeclaration
			if ((expected.NodeType == XmlNodeType.XmlDeclaration) &&
				(actual.NodeType == XmlNodeType.XmlDeclaration))
				return true;
			if (expected.NodeType != actual.NodeType)
				return false;
			if (expected.LocalName != actual.LocalName)
				return false;
			if (expected.NamespaceURI != actual.NamespaceURI)
				return false;
			if (expected.Attributes != null && actual.Attributes != null) {
				if (!AreEqualAttribs (expected.Attributes, actual.Attributes))
					return false;

				_actual = actual.OuterXml;
				_expected = expected.OuterXml;
			}
			else //one of nodes has no attrs
				if (expected.Attributes != null || actual.Attributes != null)
					return false;//and another has some
			if (!expected.HasChildNodes && !actual.HasChildNodes) {
				string val1 = expected.Value;
				string val2 = actual.Value;
				if (ignoreWS) //ignore white spaces
				{ 
					if (val1 != null)
						val1 = val1.Trim().Replace("\r\n", "\n");
					if (val2 != null)
						val2 = val2.Trim().Replace("\r\n", "\n");
				}
				return val1 == val2;
			}
			else {//one of nodes has some children
				if (!expected.HasChildNodes || !actual.HasChildNodes)
					return false;//and another has none
				return AreEqualNodeList (expected.ChildNodes, actual.ChildNodes);
			}
		}

		public string LastCompare 
		{
			get {return lastCompare;}
		}

		public string Actual
		{
			get { return _actual; }
		}

		public string Expected
		{
			get { return _expected; }
		}

		public static void AssertAreEqual (string expected, string actual) {
			AssertAreEqual (expected, actual, String.Empty);
		}

		public static void AssertAreEqual (string expected, string actual, string msg) {

			try {
				XmlDocument or = new XmlDocument ();
				or.LoadXml (expected);
				XmlDocument dr = new XmlDocument ();
				dr.LoadXml (actual);
				XmlComparer comparer = new XmlComparer ();
				if (!comparer.AreEqual (or, dr))
					Assert.AreEqual (comparer.Expected, comparer.Actual, msg);
			}
			catch (AssertionException) {
				throw;
			}
			catch (Exception e) {
				//swallow e when there is XML error and fallback
				//to the text comparison
				Assert.AreEqual (expected, actual, msg);
			}
		}
	}
}