//
// mcs/class/System.Data/System.Data/XmlDataLoader.cs
//
// Purpose: Loads XmlDocument to DataSet 
//
// class: XmlDataLoader
// assembly: System.Data.dll
// namespace: System.Data
//
// Author:
//     Ville Palo <vi64pa@koti.soon.fi>
//
// (c)copyright 2002 Ville Palo
//
// XmlDataLoader is included within the Mono Class Library.
//

using System;
using System.Data;
using System.Xml;
using System.Xml.XPath;
using System.Collections;
using System.Globalization;

namespace System.Data 
{

	internal class XmlDataLoader
	{
	
		private DataSet DSet;
		Hashtable DiffGrRows = new Hashtable ();

		public XmlDataLoader (DataSet set) 
		{
			DSet = set;
		}

		public XmlReadMode LoadData (XmlReader reader, XmlReadMode mode)
		{
			XmlReadMode Result = XmlReadMode.Auto;

			switch (mode) {

				case XmlReadMode.Fragment:
					break;
				case XmlReadMode.ReadSchema:
				case XmlReadMode.IgnoreSchema:
				case XmlReadMode.InferSchema:
					Result = mode;
					ReadModeSchema (reader, mode);
					break;
				default:
					break;
			}

			return Result;
		}

		#region reading

		// Read information from the reader.
		private void ReadModeSchema (XmlReader reader, XmlReadMode mode)
		{
			bool inferSchema = mode == XmlReadMode.InferSchema ? true : false;
			//check if the current element is schema.
			if (String.Compare (reader.LocalName, "schema", true) == 0) {
				
				if (mode == XmlReadMode.InferSchema || mode == XmlReadMode.IgnoreSchema)
					reader.Skip(); // skip the schema node.
				else
					DSet.ReadXmlSchema(reader);
				
				reader.MoveToContent();
			}
			// load an XmlDocument from the reader.
			XmlDocument doc = BuildXmlDocument(reader);

			// set EnforceConstraint to false - we do not want any validation during 
			// load time.
			bool origEnforceConstraint = DSet.EnforceConstraints;
			DSet.EnforceConstraints = false;

			// The childs are tables.
			XmlNodeList nList = doc.DocumentElement.ChildNodes;

			for (int i = 0; i < nList.Count; i++) {
				XmlNode node = nList[i];
				AddRowToTable(node, null, inferSchema);
			}
			// set the EnforceConstraints to original value;
			DSet.EnforceConstraints = origEnforceConstraint;
		}

		#endregion // reading

		#region Private helper methods
		
		private void ReadColumns (XmlReader reader, DataRow row, DataTable table, string TableName)
		{
			do {
				if (reader.NodeType == XmlNodeType.Element) {
					DataColumn col = table.Columns [reader.LocalName];
					if (col != null) {
						row [col] = StringToObject (col.DataType, reader.Value);
					}
					reader.Read ();
				}
				else {
					reader.Read ();
				}
				
			} while (table.TableName != reader.LocalName 
				|| reader.NodeType != XmlNodeType.EndElement);
		}

		internal static object StringToObject (Type type, string value)
		{
			if (type == null) return value;

			switch (Type.GetTypeCode (type)) {
				case TypeCode.Boolean: return XmlConvert.ToBoolean (value);
				case TypeCode.Byte: return XmlConvert.ToByte (value);
				case TypeCode.Char: return (char)XmlConvert.ToInt32 (value);
				case TypeCode.DateTime: return XmlConvert.ToDateTime (value);
				case TypeCode.Decimal: return XmlConvert.ToDecimal (value);
				case TypeCode.Double: return XmlConvert.ToDouble (value);
				case TypeCode.Int16: return XmlConvert.ToInt16 (value);
				case TypeCode.Int32: return XmlConvert.ToInt32 (value);
				case TypeCode.Int64: return XmlConvert.ToInt64 (value);
				case TypeCode.SByte: return XmlConvert.ToSByte (value);
				case TypeCode.Single: return XmlConvert.ToSingle (value);
				case TypeCode.UInt16: return XmlConvert.ToUInt16 (value);
				case TypeCode.UInt32: return XmlConvert.ToUInt32 (value);
				case TypeCode.UInt64: return XmlConvert.ToUInt64 (value);
			}

			if (type == typeof (TimeSpan)) return XmlConvert.ToTimeSpan (value);
			if (type == typeof (byte[])) return Convert.FromBase64String (value);

			return Convert.ChangeType (value, type);
		}

		private void AddRowToTable(XmlNode tableNode, DataColumn relationColumn, bool inferSchema)
		{
			Hashtable rowValue = new Hashtable();
			DataTable table;
			
			// Check if the table exists in the DataSet. If not create one.
			if (DSet.Tables.Contains(tableNode.LocalName))
				table = DSet.Tables[tableNode.LocalName];
			else if (inferSchema) {
				table = new DataTable(tableNode.LocalName);
				DSet.Tables.Add(table);
			}
			else
				return;
			
			// Get the child nodes of the table. Any child can be one of the following tow:
			// 1. DataTable - if there was a relation with another table..
			// 2. DataColumn - column of the current table.
			XmlNodeList childList = tableNode.ChildNodes;
			for (int i = 0; i < childList.Count; i++) {
				XmlNode childNode = childList[i];
				
				// The child node is a table if:
				// 1. He has attributes it means that it is a table OR
				// 2. He has more then one child nodes. Columns has only one child node
				// which is a Text node type that has the column value.
				if (childNode.ChildNodes.Count > 1 || childNode.Attributes.Count > 0) {
					
					if (inferSchema) {
						// We need to create new column for the relation between the current
						// table and the new table we found (the child table).
						string newRelationColumnName = table.TableName + "_Id";
						if (!table.Columns.Contains(newRelationColumnName)) {
							DataColumn newRelationColumn = new DataColumn(newRelationColumnName, typeof(int));
							newRelationColumn.AutoIncrement = true;
							// we do not want to serialize this column so MappingType is Hidden.
							newRelationColumn.ColumnMapping = MappingType.Hidden;
							table.Columns.Add(newRelationColumn);
						}
						// Add a row to the new table we found.
						AddRowToTable(childNode, table.Columns[newRelationColumnName], inferSchema);
					}
					else
						AddRowToTable(childNode, null, inferSchema);
					
				}
				else { //Child node is a column.
					object val = null;
					if (childNode.FirstChild != null)
						val = childNode.FirstChild.Value;
					else
						val = "";
					if (table.Columns.Contains(childNode.LocalName))
						rowValue.Add(childNode.LocalName, val);
					else if (inferSchema) {
						table.Columns.Add(childNode.LocalName);
						rowValue.Add(childNode.LocalName, val);
					}
				}
			}

			// Column can be attribute of the table element.
			XmlAttributeCollection aCollection = tableNode.Attributes;
			for (int i = 0; i < aCollection.Count; i++) {
				XmlAttribute attr = aCollection[i];
				//the atrribute can be the namespace.
				if (attr.Prefix.Equals("xmlns"))
					table.Namespace = attr.Value;
				else { // the attribute is a column.
					if (!table.Columns.Contains(attr.LocalName))
						table.Columns.Add(attr.LocalName);
					table.Columns[attr.LocalName].Namespace = table.Namespace;

					rowValue.Add(attr.LocalName, attr.Value);
				}
			}

			// If the current table is a child table we need to add a new column for the relation
			// and add a new relation to the DataSet.
			if (relationColumn != null) {
				if (!table.Columns.Contains(relationColumn.ColumnName)) {
					DataColumn dc = new DataColumn(relationColumn.ColumnName, typeof(int));
					// we do not want to serialize this column so MappingType is Hidden.
					dc.ColumnMapping = MappingType.Hidden;
					table.Columns.Add(dc);
					// Convention of relation name is: ParentTableName_ChildTableName
					DataRelation dr = new DataRelation(relationColumn.Table.TableName + "_" + dc.Table.TableName, relationColumn, dc);
					dr.Nested = true;
					DSet.Relations.Add(dr);
				}
				rowValue.Add (relationColumn.ColumnName, relationColumn.GetAutoIncrementValue());
			}

			// Create new row and add all values to the row.
			// then add it to the table.
			DataRow row = table.NewRow ();
					
			IDictionaryEnumerator enumerator = rowValue.GetEnumerator ();
			while (enumerator.MoveNext ()) {
				row [enumerator.Key.ToString ()] = StringToObject (table.Columns[enumerator.Key.ToString ()].DataType, enumerator.Value.ToString ());
			}

			table.Rows.Add (row);
			
		}
		
		// bulid the document from the reader.
		private XmlDocument BuildXmlDocument(XmlReader reader)
		{
			XmlDocument doc = new XmlDocument();
			// Create the root element. This is the DataSet element.
			XmlElement dataSetElement = doc.CreateElement(DSet.DataSetName);
			
			do {
				XmlNode n = doc.ReadNode (reader);
				if(n == null) break;
				// Add the table nodes to the DataSet node.
				dataSetElement.AppendChild (n);
			} while (reader.IsStartElement());
			
			// Add the DataSet element to the document.
			doc.AppendChild(dataSetElement);
			return doc;
		}

		#endregion // Private helper methods

		
	}

}
