//
// XmlSchemaWriter.cs - DataSet.WriteXmlSchema() support
//
// Author:
//
//	Atsushi Enomoto  <atsushi@ximian.com>
//
// Original WriteXml/WriteXmlSchema authors are:
//	Ville Palo, Alan Tam, Lluis Sanchez and Eran Domb.
//

//
// Copyright (C) 2004-05 Novell, Inc (http://www.novell.com)
//
// 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.Collections;
using System.Collections.Specialized;
using System.Globalization;
using System.Data;
using System.Xml;

namespace System.Data
{
	internal class XmlSchemaWriter
	{
		const string xmlnsxs = System.Xml.Schema.XmlSchema.Namespace;

		public static void WriteXmlSchema (DataSet dataset,
			XmlWriter writer)
		{
			WriteXmlSchema (dataset, writer, dataset.Tables,
				dataset.Relations);
		}

		public static void WriteXmlSchema (DataSet dataset,
			XmlWriter writer, DataTableCollection tables,
			DataRelationCollection relations)
		{
			new XmlSchemaWriter (dataset, writer,
				tables, relations).WriteSchema ();
		}

		internal static void WriteXmlSchema (XmlWriter writer, DataTable[] tables,
						     DataRelation[] relations,
						     string mainDataTable,
						     string dataSetName,
						     CultureInfo locale)
		{
			new XmlSchemaWriter (writer, tables, relations, mainDataTable, dataSetName, locale).WriteSchema ();
		}

		public XmlSchemaWriter (DataSet dataset,
			XmlWriter writer, DataTableCollection tables,
			DataRelationCollection relations)
		{
			dataSetName = dataset.DataSetName;
			dataSetNamespace = dataset.Namespace;
#if NET_2_0
			dataSetLocale = dataset.LocaleSpecified ? dataset.Locale : null;
#else
			dataSetLocale = dataset.Locale;
#endif
			dataSetProperties = dataset.ExtendedProperties;
			w = writer;
			if (tables != null) {
				this.tables = new DataTable[tables.Count];
				for(int i=0;i<tables.Count;i++) this.tables[i] = tables[i];
			}
			if (relations != null) {
				this.relations = new DataRelation[relations.Count];
				for(int i=0;i<relations.Count;i++) this.relations[i] = relations[i];
			}
		}

		public XmlSchemaWriter (XmlWriter writer,
			DataTable[] tables,
			DataRelation[] relations,
			string mainDataTable,
			string dataSetName,
			CultureInfo locale)
		{
			w = writer;
			this.tables = tables;
			this.relations = relations;
			this.mainDataTable = mainDataTable;
			this.dataSetName = dataSetName;
			this.dataSetLocale = locale;
			this.dataSetProperties = new PropertyCollection();
			if (tables[0].DataSet != null) {
				dataSetNamespace = tables[0].DataSet.Namespace;
#if !NET_2_0
				dataSetLocale = tables[0].DataSet.Locale;
#endif
			} else {
				dataSetNamespace = tables[0].Namespace;
#if !NET_2_0
				dataSetLocale = tables[0].Locale;
#endif
			}
		}

		XmlWriter w;
		DataTable[] tables;
		DataRelation[] relations;
		string mainDataTable;
		string dataSetName;
		string dataSetNamespace;
		PropertyCollection dataSetProperties;
		CultureInfo dataSetLocale;

		ArrayList globalTypeTables = new ArrayList ();
		Hashtable additionalNamespaces = new Hashtable ();

		ArrayList annotation = new ArrayList ();

		public string ConstraintPrefix {
			get { return dataSetNamespace != String.Empty ? XmlConstants.TnsPrefix + ':' : String.Empty; }
		}

		// the whole DataSet

		public void WriteSchema ()
		{
			ListDictionary names = new ListDictionary ();
			ListDictionary includes = new ListDictionary ();

			// Add namespaces used in DataSet components (tables, columns, ...)
			foreach (DataTable dt in tables) {
				foreach (DataColumn col in dt.Columns)
					CheckNamespace (col.Prefix, col.Namespace, names, includes);
				CheckNamespace (dt.Prefix, dt.Namespace, names, includes);
			}

			w.WriteStartElement ("xs", "schema", xmlnsxs);
			w.WriteAttributeString ("id", XmlHelper.Encode (dataSetName));

			if (dataSetNamespace != String.Empty) {
				w.WriteAttributeString ("targetNamespace",
					dataSetNamespace);
				w.WriteAttributeString (
					"xmlns",
					XmlConstants.TnsPrefix,
					XmlConstants.XmlnsNS,
					dataSetNamespace);
			}
			w.WriteAttributeString ("xmlns", dataSetNamespace);

			w.WriteAttributeString ("xmlns", "xs",
				XmlConstants.XmlnsNS, xmlnsxs);
			w.WriteAttributeString ("xmlns",
				XmlConstants.MsdataPrefix,
				XmlConstants.XmlnsNS,
				XmlConstants.MsdataNamespace);

			if (CheckExtendedPropertyExists (tables, relations))
				w.WriteAttributeString ("xmlns",
					XmlConstants.MspropPrefix,
					XmlConstants.XmlnsNS,
					XmlConstants.MspropNamespace);

			if (dataSetNamespace != String.Empty) {
				w.WriteAttributeString ("attributeFormDefault", "qualified");
				w.WriteAttributeString ("elementFormDefault", "qualified");
			}

			foreach (string prefix in names.Keys)
				w.WriteAttributeString ("xmlns", prefix,
					XmlConstants.XmlnsNS,
					names [prefix] as string);

			if (includes.Count > 0)
				w.WriteComment ("ATTENTION: This schema contains references to other imported schemas");
			foreach (string ns in includes.Keys) {
				w.WriteStartElement ("xs", "import", xmlnsxs);
				w.WriteAttributeString ("namespace", ns);
				w.WriteAttributeString ("schemaLocation", includes [ns] as string);
				w.WriteEndElement ();
			}
			
			foreach (DataTable table in tables) {
				bool isTopLevel = true;
				foreach (DataRelation rel in table.ParentRelations) {
					if (rel.Nested) {
						isTopLevel = false;
						break;
					}
				}
				// LAMESPEC: You have a nested relationship but only one table, 
				// write table element first
				if (!isTopLevel && tables.Length < 2)
						WriteTableElement (table);
			}
	
			WriteDataSetElement ();

			w.WriteEndElement (); // xs:schema

			w.Flush ();
		}

		// FIXME: actually there are some cases that this method(ology)
		// does not apply.
		private void WriteDataSetElement ()
		{
			w.WriteStartElement ("xs", "element", xmlnsxs);
			w.WriteAttributeString ("name", XmlHelper.Encode (dataSetName));
			w.WriteAttributeString (XmlConstants.MsdataPrefix,
				"IsDataSet", XmlConstants.MsdataNamespace,
				"true");

#if NET_2_0
			bool useCurrentLocale = (dataSetLocale == null);

			if (!useCurrentLocale)
#endif
			{
				w.WriteAttributeString (
					XmlConstants.MsdataPrefix,
					"Locale",
					XmlConstants.MsdataNamespace,
					dataSetLocale.Name);
			}

#if NET_2_0
			if(mainDataTable != null && mainDataTable != "")
				w.WriteAttributeString (
					XmlConstants.MsdataPrefix,
					"MainDataTable",
					XmlConstants.MsdataNamespace,
					mainDataTable);

			if (useCurrentLocale) {
				w.WriteAttributeString (
					XmlConstants.MsdataPrefix,
					"UseCurrentLocale",
					XmlConstants.MsdataNamespace,
					"true");
			}
#endif

			AddExtendedPropertyAttributes (dataSetProperties);

			w.WriteStartElement ("xs", "complexType", xmlnsxs);
			w.WriteStartElement ("xs", "choice", xmlnsxs);
			w.WriteAttributeString ("minOccurs", "0");
			w.WriteAttributeString ("maxOccurs", "unbounded");

			foreach (DataTable table in tables) {
				bool isTopLevel = true;
				foreach (DataRelation rel in table.ParentRelations) {
					if (rel.Nested) {
						isTopLevel = false;
						break;
					}
				}

				// If there is a relation but only one table, could be that
				// we have to add a ref attribute
				bool addref = false;
				if (!isTopLevel && tables.Length < 2) {
					isTopLevel = true;
					addref = true;
				}
				
				if (isTopLevel) {
					if (dataSetNamespace != table.Namespace ||
					    addref) {
						// <xs:element ref="X:y" />
						w.WriteStartElement ("xs",
							"element",
							xmlnsxs);
						w.WriteStartAttribute ("ref", String.Empty);
						w.WriteQualifiedName (XmlHelper.Encode (table.TableName), table.Namespace);
						w.WriteEndAttribute ();
						w.WriteEndElement ();
					}
					else
						WriteTableElement (table);
				}
			}

			w.WriteEndElement (); // choice
			w.WriteEndElement (); // complexType

			WriteConstraints (); // DataSet constraints

			w.WriteEndElement (); // element

			if (annotation.Count > 0) {
				w.WriteStartElement ("xs", "annotation", xmlnsxs);
				w.WriteStartElement ("xs", "appinfo", xmlnsxs);

				foreach (object o in annotation) {
					if (!(o is DataRelation))
						continue;
					WriteDataRelationAnnotation ((DataRelation)o);	
				}
				w.WriteEndElement ();
				w.WriteEndElement ();
			}
		}

		private void WriteDataRelationAnnotation (DataRelation rel) 
		{
			String colnames = String.Empty;
			w.WriteStartElement (XmlConstants.MsdataPrefix, "Relationship",
				 XmlConstants.MsdataNamespace);

			w.WriteAttributeString ("name", XmlHelper.Encode (rel.RelationName));

			w.WriteAttributeString (
					XmlConstants.MsdataPrefix,
					"parent",
					XmlConstants.MsdataNamespace,
					XmlHelper.Encode (rel.ParentTable.TableName));

			w.WriteAttributeString (
					XmlConstants.MsdataPrefix,
					"child",
					XmlConstants.MsdataNamespace,
					XmlHelper.Encode (rel.ChildTable.TableName));

			colnames = String.Empty;
			foreach (DataColumn col in rel.ParentColumns)
				colnames += XmlHelper.Encode (col.ColumnName) + " ";
			w.WriteAttributeString (
					XmlConstants.MsdataPrefix,
					"parentkey",
					XmlConstants.MsdataNamespace,
					colnames.TrimEnd ());

			colnames = String.Empty;
			foreach (DataColumn col in rel.ChildColumns)
				colnames += XmlHelper.Encode (col.ColumnName) + " ";
			w.WriteAttributeString (
					XmlConstants.MsdataPrefix,
					"childkey",
					XmlConstants.MsdataNamespace,
					colnames.TrimEnd ());

			w.WriteEndElement ();
		}

		private void WriteConstraints ()
		{
			ArrayList names = new ArrayList ();

			// add all unique constraints.
			foreach (DataTable table in tables) {
				foreach (Constraint c in table.Constraints) {
					UniqueConstraint u =
						c as UniqueConstraint;
					if (u != null) {
						AddUniqueConstraints (u, names);
						continue;
					}

					ForeignKeyConstraint fk = c as ForeignKeyConstraint;
					bool haveConstraint = false;
					if (relations != null) {
						foreach (DataRelation r in relations)
							if (r.RelationName == fk.ConstraintName)
								haveConstraint = true;
					}
					if (tables.Length > 1 && fk != null && !haveConstraint) {
						DataRelation rel = new DataRelation (fk.ConstraintName,
										fk.RelatedColumns, fk.Columns);
						AddForeignKeys (rel, names, true);
						continue;
					}
				}
			}

			// Add all foriegn key constraints.
			if (relations != null)
				foreach (DataRelation rel in relations) {
					if (rel.ParentKeyConstraint == null || rel.ChildKeyConstraint == null) {
						annotation.Add (rel);
						continue;
					}
					AddForeignKeys (rel, names,false);
				}
		}

		// Add unique constaraints to the schema.
		// return hashtable with the names of all XmlSchemaUnique elements we created.
		private void AddUniqueConstraints (UniqueConstraint uniq,
			ArrayList names)
		{
			// if column of the constraint is hidden do not write the constraint.
			foreach (DataColumn column in uniq.Columns)
				if (column.ColumnMapping == MappingType.Hidden)
					return; // do nothing

			w.WriteStartElement ("xs", "unique", xmlnsxs);
			// if constaraint name do not exist in the hashtable we can use it.
			string name;
			if (!names.Contains (uniq.ConstraintName)) {
				name = XmlHelper.Encode (uniq.ConstraintName);
				w.WriteAttributeString ("name", name);
			}
			// otherwise generate new constraint name for the
			// XmlSchemaUnique element.
			else {
				name = XmlHelper.Encode (uniq.Table.TableName) + "_" + XmlHelper.Encode (uniq.ConstraintName);
				w.WriteAttributeString ("name", name);
				w.WriteAttributeString (
					XmlConstants.MsdataPrefix,
					XmlConstants.ConstraintName,
					XmlConstants.MsdataNamespace,
					XmlHelper.Encode (uniq.ConstraintName));
			}
			names.Add (name);

			if (uniq.IsPrimaryKey) {
				w.WriteAttributeString (
					XmlConstants.MsdataPrefix,
					XmlConstants.PrimaryKey,
					XmlConstants.MsdataNamespace,
					"true");
			}

			AddExtendedPropertyAttributes (uniq.ExtendedProperties);

			w.WriteStartElement ("xs", "selector",
				xmlnsxs);
			w.WriteAttributeString ("xpath", ".//" +
				ConstraintPrefix + XmlHelper.Encode (uniq.Table.TableName));
			w.WriteEndElement (); // selector
			foreach (DataColumn c in uniq.Columns) {
				w.WriteStartElement ("xs", "field",
					xmlnsxs);
				w.WriteStartAttribute ("xpath", String.Empty);
				if (c.ColumnMapping == MappingType.Attribute)
					w.WriteString ("@");
				w.WriteString (ConstraintPrefix);
				w.WriteString (XmlHelper.Encode (c.ColumnName));
				w.WriteEndAttribute (); // xpath
				w.WriteEndElement (); // field
			}

			w.WriteEndElement (); // unique
		}

		// Add the foriegn keys to the schema.
		private void AddForeignKeys (DataRelation rel, ArrayList names, bool isConstraintOnly)
		{
			// Do nothing if it contains hidden relation
			foreach (DataColumn col in rel.ParentColumns)
				if (col.ColumnMapping == MappingType.Hidden)
					return;
			foreach (DataColumn col in rel.ChildColumns)
				if (col.ColumnMapping == MappingType.Hidden)
					return;

			w.WriteStartElement ("xs", "keyref", xmlnsxs);
			w.WriteAttributeString ("name", XmlHelper.Encode (rel.RelationName));

			//ForeignKeyConstraint fkConst = rel.ChildKeyConstraint;
			UniqueConstraint uqConst = null; 

			if (isConstraintOnly) {
				foreach (Constraint c in rel.ParentTable.Constraints) {
					uqConst = c as UniqueConstraint;
					if (uqConst == null)
						continue;
					if (uqConst.Columns == rel.ParentColumns)
						break;
				}
			} 
			else
				uqConst = rel.ParentKeyConstraint;

			string concatName = XmlHelper.Encode (rel.ParentTable.TableName) + "_" + XmlHelper.Encode (uqConst.ConstraintName);
			// first try to find the concatenated name. If we didn't find it - use constraint name.
			if (names.Contains (concatName)) {
				w.WriteStartAttribute ("refer", String.Empty);
				w.WriteQualifiedName (concatName, dataSetNamespace);
				w.WriteEndAttribute ();
			}
			else {
				w.WriteStartAttribute ("refer", String.Empty);
				w.WriteQualifiedName (XmlHelper.Encode (uqConst.ConstraintName), dataSetNamespace);
				w.WriteEndAttribute ();
			}

			if (isConstraintOnly)
				w.WriteAttributeString ( XmlConstants.MsdataPrefix,
					XmlConstants.ConstraintOnly,
					XmlConstants.MsdataNamespace,
					"true");
			else if (rel.Nested)
				w.WriteAttributeString (
					XmlConstants.MsdataPrefix,
					XmlConstants.IsNested,
					XmlConstants.MsdataNamespace,
					"true");

			AddExtendedPropertyAttributes (uqConst.ExtendedProperties);

			w.WriteStartElement ("xs", "selector", xmlnsxs);
			w.WriteAttributeString ("xpath", ".//" + 
				ConstraintPrefix + XmlHelper.Encode (rel.ChildTable.TableName));
			w.WriteEndElement ();

			foreach (DataColumn c in rel.ChildColumns) {
				w.WriteStartElement ("xs", "field",
					xmlnsxs);
				w.WriteStartAttribute ("xpath", String.Empty);
				if (c.ColumnMapping == MappingType.Attribute)
					w.WriteString ("@");
				w.WriteString (ConstraintPrefix);
				w.WriteString (XmlHelper.Encode (c.ColumnName));
				w.WriteEndAttribute ();
				w.WriteEndElement (); // field
			}

			w.WriteEndElement (); // keyref
		}

		// ExtendedProperties

		private bool CheckExtendedPropertyExists (
			DataTable[] tables,
			DataRelation[] relations)
		{
			if (dataSetProperties.Count > 0)
				return true;
			foreach (DataTable dt in tables) {
				if (dt.ExtendedProperties.Count > 0)
					return true;
				foreach (DataColumn col in dt.Columns)
					if (col.ExtendedProperties.Count > 0)
						return true;
				foreach (Constraint c in dt.Constraints)
					if (c.ExtendedProperties.Count > 0)
						return true;
			}
			if (relations == null)
				return false;
			foreach (DataRelation rel in relations)
				if (rel.ExtendedProperties.Count > 0)
					return true;
			return false;
		}

		private void AddExtendedPropertyAttributes (PropertyCollection props)
		{
			// add extended properties to xs:element
			foreach (DictionaryEntry de in props) {
				w.WriteStartAttribute (
					XmlConstants.MspropPrefix,
					XmlConvert.EncodeName (de.Key.ToString ()),
					XmlConstants.MspropNamespace);
				if (de.Value != null)
					w.WriteString (
						DataSet.WriteObjectXml (de.Value));
				w.WriteEndAttribute ();
			}
		}

		// Table

		private void WriteTableElement (DataTable table)
		{
			w.WriteStartElement ("xs", "element", xmlnsxs);
			w.WriteAttributeString ("name", XmlHelper.Encode (table.TableName));

			AddExtendedPropertyAttributes (table.ExtendedProperties);

			WriteTableType (table);

			w.WriteEndElement ();
		}

		private void WriteTableType (DataTable table)
		{
			ArrayList elements;
			ArrayList atts;
			DataColumn simple;

			DataSet.SplitColumns (table, out atts, out elements, out simple);

			w.WriteStartElement ("xs", "complexType", xmlnsxs);

			if (simple != null) {
				w.WriteStartElement ("xs", "simpleContent", xmlnsxs);
				// add column name attribute
				w.WriteAttributeString (
					XmlConstants.MsdataPrefix,
					XmlConstants.ColumnName,
					XmlConstants.MsdataNamespace,
					XmlHelper.Encode (simple.ColumnName));

				// add ordinal attribute
				w.WriteAttributeString (
					XmlConstants.MsdataPrefix,
					XmlConstants.Ordinal,
					XmlConstants.MsdataNamespace,
					XmlConvert.ToString (simple.Ordinal));

				// add extension
				w.WriteStartElement ("xs", "extension",
					xmlnsxs);
				w.WriteStartAttribute ("base", String.Empty);
				WriteQName (MapType (simple.DataType));
				w.WriteEndAttribute ();

				WriteTableAttributes (atts);

				w.WriteEndElement ();
			} else {
				WriteTableAttributes (atts);
				bool isNested = false;
				foreach (DataRelation rel in table.ParentRelations) {
					if (rel.Nested) {
						isNested = true;
						break;
					}
				}
				
				// When we have a nested relationship and only one table,
				// could be that we have a reference attribute
				if (elements.Count > 0 || (isNested && tables.Length < 2)) {
					w.WriteStartElement ("xs", "sequence",
						xmlnsxs);

					foreach (DataColumn col in elements)
						WriteTableTypeParticles (col);

					foreach (DataRelation rel in table.ChildRelations)
						if (rel.Nested)
							WriteChildRelations (rel);
					w.WriteEndElement ();
				}
			}

			w.WriteFullEndElement (); // complexType
		}

		private void WriteTableTypeParticles (DataColumn col)
		{
			w.WriteStartElement ("xs", "element", xmlnsxs);
			w.WriteAttributeString ("name", XmlHelper.Encode (col.ColumnName));

			if (col.ColumnName != col.Caption && col.Caption != String.Empty)
				w.WriteAttributeString (
					XmlConstants.MsdataPrefix,
					XmlConstants.Caption, 
					XmlConstants.MsdataNamespace,
					col.Caption);

			if (col.AutoIncrement == true)
				w.WriteAttributeString (
					XmlConstants.MsdataPrefix,
					XmlConstants.AutoIncrement,
					XmlConstants.MsdataNamespace,
					"true");

			if (col.AutoIncrementSeed != 0) {
				w.WriteAttributeString (

					XmlConstants.MsdataPrefix,
					XmlConstants.AutoIncrementSeed,
					XmlConstants.MsdataNamespace,
					XmlConvert.ToString (col.AutoIncrementSeed));
			}

			if (col.AutoIncrementStep != 1) {
				w.WriteAttributeString (

					XmlConstants.MsdataPrefix,
					XmlConstants.AutoIncrementStep,
					XmlConstants.MsdataNamespace,
					XmlConvert.ToString (col.AutoIncrementStep));
			}

			if (!DataColumn.GetDefaultValueForType (col.DataType).Equals (col.DefaultValue))
				w.WriteAttributeString ("default",
					DataSet.WriteObjectXml (col.DefaultValue));

			if (col.ReadOnly)
				w.WriteAttributeString (
					XmlConstants.MsdataPrefix,
					XmlConstants.ReadOnly,
					XmlConstants.MsdataNamespace,
					"true");

			XmlQualifiedName typeQName = null;
			if (col.MaxLength < 0) {
				w.WriteStartAttribute ("type", String.Empty);
				typeQName = MapType (col.DataType);
				WriteQName (typeQName);
				w.WriteEndAttribute ();
			}

			if (typeQName == XmlConstants.QnString
				&& col.DataType != typeof (string)
				&& col.DataType != typeof (char)) {
				w.WriteStartAttribute (
					XmlConstants.MsdataPrefix,
					XmlConstants.DataType,
					XmlConstants.MsdataNamespace);
				string runtimeName = col.DataType.AssemblyQualifiedName;
				w.WriteString (runtimeName);
				w.WriteEndAttribute ();
			}

			if (col.AllowDBNull)
				w.WriteAttributeString ("minOccurs", "0");

			//writer.WriteAttributeString (XmlConstants.MsdataPrefix, 
			//                            XmlConstants.Ordinal, 
			//                            XmlConstants.MsdataNamespace, 
			//                            col.Ordinal.ToString ());

			// Write SimpleType if column have MaxLength
			if (col.MaxLength > -1)
				WriteSimpleType (col);

			AddExtendedPropertyAttributes (col.ExtendedProperties);

			w.WriteEndElement (); // sequence
		}

		private void WriteChildRelations (DataRelation rel)
		{
			// If there is a parent/child relation and only one table,
			// it would just be a ref element.
			if (rel.ChildTable.Namespace != dataSetNamespace ||
			    tables.Length < 2) {
				w.WriteStartElement ("xs", "element", xmlnsxs);
				w.WriteStartAttribute ("ref", String.Empty);
				w.WriteQualifiedName (
					XmlHelper.Encode (rel.ChildTable.TableName),
					rel.ChildTable.Namespace);
				w.WriteEndAttribute ();
			} else {
				w.WriteStartElement ("xs", "element", xmlnsxs);
				w.WriteStartAttribute ("name", String.Empty);
				w.WriteQualifiedName (
					XmlHelper.Encode (rel.ChildTable.TableName),
					rel.ChildTable.Namespace);
				w.WriteEndAttribute ();
				w.WriteAttributeString ("minOccurs", "0");
				w.WriteAttributeString ("maxOccurs", "unbounded");

				globalTypeTables.Add (rel.ChildTable);
			}
			
			// Iff there is a genuine ChildTable and not just a ref, call WriteTableType
			if (tables.Length > 1)
				WriteTableType (rel.ChildTable);
			w.WriteEndElement ();
		}

		private void WriteTableAttributes (ArrayList atts)
		{
			//Then a list of attributes
			foreach (DataColumn col in atts) {
				w.WriteStartElement ("xs", "attribute", xmlnsxs);

				string name = XmlHelper.Encode (col.ColumnName);
				if (col.Namespace != String.Empty) {
					w.WriteAttributeString ("form", "qualified");
					string prefix = col.Prefix == String.Empty ? "app" + additionalNamespaces.Count : col.Prefix;
					name = prefix + ":" + name;
					// FIXME: Handle prefix mapping correctly.
					additionalNamespaces [prefix] = col.Namespace;
				}
				w.WriteAttributeString ("name", name);

				AddExtendedPropertyAttributes (
					col.ExtendedProperties);

				if (col.ReadOnly)
					w.WriteAttributeString (
						XmlConstants.MsdataPrefix,
						XmlConstants.ReadOnly,
						XmlConstants.MsdataNamespace,
						"true");

				if (col.MaxLength < 0) {
					// otherwise simpleType is written later
					w.WriteStartAttribute ("type", String.Empty);
					WriteQName (MapType (col.DataType));
					w.WriteEndAttribute ();
				}

				if (!col.AllowDBNull)
					w.WriteAttributeString ("use", "required");
				if (col.DefaultValue != DataColumn.GetDefaultValueForType (col.DataType))
					w.WriteAttributeString ("default",
						DataSet.WriteObjectXml (col.DefaultValue));

				if (col.MaxLength > -1)
					WriteSimpleType (col);

				w.WriteEndElement (); // attribute
			}
		}

		private void WriteSimpleType (DataColumn col)
		{
			w.WriteStartElement ("xs", "simpleType", xmlnsxs);
			w.WriteStartElement ("xs", "restriction", xmlnsxs);
			w.WriteStartAttribute ("base", String.Empty);
			WriteQName (MapType (col.DataType));
			w.WriteEndAttribute ();

			w.WriteStartElement ("xs", "maxLength", xmlnsxs);
			w.WriteAttributeString ("value",
				XmlConvert.ToString (col.MaxLength));
			w.WriteEndElement (); // maxLength
			w.WriteEndElement (); // restriction
			w.WriteEndElement (); // simpleType
		}

		private void WriteQName (XmlQualifiedName name)
		{
			w.WriteQualifiedName (name.Name, name.Namespace);
		}

		private void CheckNamespace (string prefix, string ns, ListDictionary names, ListDictionary includes)
		{
			if (ns == String.Empty)
				return;
			if (dataSetNamespace != ns) {
				if ((string)names [prefix] != ns) {
					for (int i = 1; i < int.MaxValue; i++) {
						string p = "app" + i;
						if (names [p] == null) {
							names.Add (p, ns);
							HandleExternalNamespace (p, ns, includes);
							break;
						}
					}
				}
			}
		}

		private void HandleExternalNamespace (string prefix, string ns, ListDictionary includes)
		{
			if (includes.Contains (ns))
				return; // nothing to do
			includes.Add (ns, "_" + prefix + ".xsd");
		}

		private /*static*/ XmlQualifiedName MapType (Type type)
		{
			switch (Type.GetTypeCode (type)) {
				case TypeCode.String: return XmlConstants.QnString;
				case TypeCode.Int16: return XmlConstants.QnShort;
				case TypeCode.Int32: return XmlConstants.QnInt;
				case TypeCode.Int64: return XmlConstants.QnLong;
				case TypeCode.Boolean: return XmlConstants.QnBoolean;
				case TypeCode.Byte: return XmlConstants.QnUnsignedByte;
				//case TypeCode.Char: return XmlConstants.QnChar;
				case TypeCode.DateTime: return XmlConstants.QnDateTime;
				case TypeCode.Decimal: return XmlConstants.QnDecimal;
				case TypeCode.Double: return XmlConstants.QnDouble;
				case TypeCode.SByte: return XmlConstants.QnSbyte;
				case TypeCode.Single: return XmlConstants.QnFloat;
				case TypeCode.UInt16: return XmlConstants.QnUsignedShort;
				case TypeCode.UInt32: return XmlConstants.QnUnsignedInt;
				case TypeCode.UInt64: return XmlConstants.QnUnsignedLong;
			}
			
			if (typeof (TimeSpan) == type)
				return XmlConstants.QnDuration;
			else if (typeof (System.Uri) == type)
				return XmlConstants.QnUri;
			else if (typeof (byte[]) == type)
				return XmlConstants.QnBase64Binary;
			else if (typeof (XmlQualifiedName) == type)
				return XmlConstants.QnXmlQualifiedName;
			else
				return XmlConstants.QnString;
		}
	}
}
