using System;
using System.Reflection;
using System.Xml;
using System.Xml.Schema;
using Commons.Xml.Relaxng;
using Commons.Xml.Relaxng.Rnc;

using BF = System.Reflection.BindingFlags;

namespace Mono.XmlTools
{
	public class Dtd2Rng
	{
		public static int Main (string [] args)
		{
			if (args.Length == 0) {
				Usage ();
				return 1;
			}

			return new Dtd2Rng ().Process (args);
		}

		static void Usage ()
		{
			Console.Error.WriteLine (@"
Usage dtd2rng [options] dtdfile [ns]

options:
	--help : show this message.
	--compact, -c : output compact syntax.
");
		}

		public int Process (string [] args)
		{
			string file = null;
			bool compact = false;
			string ns = String.Empty;
			foreach (string arg in args) {
				if (arg == "--help") {
					Usage ();
					return 1;
				}
				if (arg == "--compact" || arg == "-c")
					compact = true;
				else if (file == null)
					file = arg;
				else if (ns != String.Empty) {
					Usage ();
					Console.Error.WriteLine ("Extra command line argument.");
					return 1;
				}
				else
					ns = arg;
			}

			XmlTextReader xtr;
			if (file.EndsWith (".dtd")) {
				xtr = new XmlTextReader (
					"<!DOCTYPE dummy SYSTEM '" + file + "'>",
					XmlNodeType.Document, null);
			} else {
				xtr = new XmlTextReader (file);
			}
			xtr.Read ();
			if (xtr.NodeType == XmlNodeType.XmlDeclaration)
				xtr.Read ();

			XmlSchema xsd = GetXmlSchema (xtr);

			RelaxngPattern rng = DtdXsd2Rng (xsd, ns);
			if (compact)
				rng.WriteCompact (Console.Out);
			else {
				XmlTextWriter w = new XmlTextWriter (Console.Out);
				w.Formatting = Formatting.Indented;
				rng.Write (w);
				w.Close ();
			}
			return 0;
		}

		XmlSchema GetXmlSchema (XmlTextReader xtr)
		{
			// Hacky reflection part
			object impl = xtr;
			BF flag = BF.NonPublic | BF.Instance;

			// In Mono NET_2_0 XmlTextReader is just a wrapper which 
			// does not contain DTD directly.
			FieldInfo fi = typeof (XmlTextReader).GetField ("source", flag);
			if (fi != null)
				impl = fi.GetValue (xtr);

			PropertyInfo pi = impl.GetType ().GetProperty ("DTD", flag);
			object dtd = pi.GetValue (impl, null);
			MethodInfo mi =
				dtd.GetType ().GetMethod ("CreateXsdSchema", flag);
			object o = mi.Invoke (dtd, null);
			return (XmlSchema) o;
		}

		RelaxngGrammar g;

		RelaxngGrammar DtdXsd2Rng (XmlSchema xsd, string ns)
		{
			g = new RelaxngGrammar ();
			g.DefaultNamespace = ns;
			RelaxngStart start = new RelaxngStart ();
			g.Starts.Add (start);
			RelaxngChoice choice = new RelaxngChoice ();
			start.Pattern = choice;

			// There are only elements.
			foreach (XmlSchemaElement el in xsd.Items) {
				RelaxngDefine def = DefineElement (el);
				g.Defines.Add (def);
				RelaxngRef dref = new RelaxngRef ();
				dref.Name = def.Name;
				choice.Patterns.Add (dref);
			}

			return g;
		}

		RelaxngDefine DefineElement (XmlSchemaElement el)
		{
			RelaxngDefine def = new RelaxngDefine ();
			def.Patterns.Add (CreateElement (el));
			def.Name = el.Name;

			return def;
		}

		RelaxngPattern CreateElement (XmlSchemaElement xse)
		{
			if (xse.RefName != XmlQualifiedName.Empty) {
				RelaxngRef r = new RelaxngRef ();
				r.Name = xse.RefName.Name;
				// namespace means nothing here.
				return r;
			}

			RelaxngElement re = new RelaxngElement ();
			RelaxngName name = new RelaxngName ();
			name.LocalName = xse.Name;
			re.NameClass = name;

			XmlSchemaComplexType ct = xse.SchemaType as XmlSchemaComplexType;

			foreach (XmlSchemaAttribute a in ct.Attributes)
				re.Patterns.Add (CreateAttribute (a));

			RelaxngPattern rpart;
			if (ct.Particle == null)
				rpart = new RelaxngEmpty ();
			else
				rpart = CreatePatternFromParticle (ct.Particle);

			if (ct.IsMixed) {
				if (rpart.PatternType != RelaxngPatternType.Empty) {
					RelaxngMixed mixed = new RelaxngMixed ();
					mixed.Patterns.Add (rpart);
					rpart = mixed;
				} else {
					rpart = new RelaxngText ();
				}
			}

			re.Patterns.Add (rpart);

			return re;
		}

		RelaxngPattern CreateAttribute (XmlSchemaAttribute attr)
		{
			RelaxngAttribute ra = new RelaxngAttribute ();
			RelaxngName name = new RelaxngName ();
			name.LocalName = attr.Name;
			ra.NameClass = name;
			ra.Pattern = attr.SchemaType != null ?
				CreatePatternFromType (attr.SchemaType) :
				CreatePatternFromTypeName (attr.SchemaTypeName);

			RelaxngPattern ret = ra;

			if (attr.Use == XmlSchemaUse.Optional) {
				RelaxngOptional opt = new RelaxngOptional ();
				opt.Patterns.Add (ra);
				ret = opt;
			}
			return ret;
		}

		RelaxngPattern CreatePatternFromParticle (XmlSchemaParticle xsdp)
		{
			RelaxngSingleContentPattern rngp = null;
			if (xsdp.MinOccurs == 0 && xsdp.MaxOccursString == "unbounded")
				rngp = new RelaxngZeroOrMore ();
			else if (xsdp.MinOccurs == 1 && xsdp.MaxOccursString == "unbounded")
				rngp = new RelaxngOneOrMore ();
			else if (xsdp.MinOccurs == 0)
				rngp = new RelaxngOptional ();

			RelaxngPattern child = CreatePatternFromParticleCore (xsdp);
			if (rngp == null)
				return child;
			rngp.Patterns.Add (child);
			return rngp;
		}

		RelaxngPattern CreatePatternFromParticleCore (XmlSchemaParticle xsdp)
		{
			XmlSchemaGroupBase gb = xsdp as XmlSchemaGroupBase;
			if (xsdp is XmlSchemaAny) {
				RelaxngRef r = new RelaxngRef ();
				r.Name = "anyType";
				return r;
			}
			if (gb is XmlSchemaSequence) {
				RelaxngGroup grp = new RelaxngGroup ();
				foreach (XmlSchemaParticle xsdc in gb.Items)
					grp.Patterns.Add (CreatePatternFromParticle (xsdc));
				return grp;
			}
			if (gb is XmlSchemaChoice) {
				RelaxngChoice rc = new RelaxngChoice ();
				foreach (XmlSchemaParticle xsdc in gb.Items)
					rc.Patterns.Add (CreatePatternFromParticle (xsdc));
				return rc;
			}
			return CreateElement ((XmlSchemaElement) xsdp);
		}

		RelaxngPattern CreatePatternFromType (XmlSchemaType type)
		{
			XmlSchemaSimpleType st = type as XmlSchemaSimpleType;
			if (st == null)
				throw new NotSupportedException ("Complex types are not supported as an attribute type.");
			XmlSchemaSimpleTypeRestriction r =
				st.Content as XmlSchemaSimpleTypeRestriction;
			if (r == null)
				throw new NotSupportedException ("Only simple type restriction is supported as an attribute type.");

			RelaxngChoice c = new RelaxngChoice ();
			foreach (XmlSchemaFacet f in r.Facets) {
				XmlSchemaEnumerationFacet en =
					f as XmlSchemaEnumerationFacet;
				if (en == null)
					throw new NotSupportedException ("Only enumeration facet is supported.");
				RelaxngValue v = new RelaxngValue ();
				v.Type = r.BaseTypeName.Name;
				v.DatatypeLibrary = RemapDatatypeLibrary (
					r.BaseTypeName.Namespace);
				v.Value = en.Value;
				c.Patterns.Add (v);
			}
			return c;
		}

		RelaxngPattern CreatePatternFromTypeName (XmlQualifiedName name)
		{
			if (name == XmlQualifiedName.Empty)
				return new RelaxngText ();
			RelaxngData data = new RelaxngData ();
			data.Type = name.Name;
			data.DatatypeLibrary = RemapDatatypeLibrary (
				name.Namespace);
			return data;
		}

		string RemapDatatypeLibrary (string ns)
		{
			return ns == XmlSchema.Namespace ?
				"http://www.w3.org/2001/XMLSchema-datatypes" :
				ns;
		}
	}
}