#region Copyright and GPL
/*************************************************************************
 * dataMagus - The open source database learning software
 * Copyright (C) 2007 by all authors
 * Authors: Monika Schwitter (monsy@gmx.ch)
 *          Jürg Zgraggen (j.zgraggen@ejaz.net)
 *          HSLU (Hochschule Luzern - Technik+Architektur)
 * 
 *     This program is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 * 
 *     This program is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
 * 
 *     You should have received a copy of the GNU General Public License
 *     along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *************************************************************************/
#endregion
#region Using
using System;
using System.IO;
using System.Text;
using System.Xml;
using System.Diagnostics;
using System.Xml.Schema;
using System.Xml.Serialization;
using System.Runtime.Serialization;
using System.Collections.Generic;
using System.Collections;
using System.Reflection;
using dataMagus.Common.PlugIn.Generator;
#endregion

namespace dataMagus.Common.Physical
{
	/// <summary>
	/// This class is used to store all data from the physical layers in memory.
	/// With the xml serialization, the data can be stored persitantly.
	/// </summary>
    public sealed class PhysicalItemContainer : IXmlSerializable
	{
		#region Enumerations
		/// <summary>
		/// Action type for changing datatypes. Will be used to inform
		/// the different generators.
		/// </summary>
        public enum DataTypeAction
        { 
        	/// <summary>
        	/// New datatype has been added.
        	/// </summary>
            Added,
            
            /// <summary>
            /// Datatype has been removed.
            /// </summary>
            Removed,
            
            /// <summary>
            /// Datatype has been modified.
            /// </summary>
            Modified
        }
		#endregion
		
		#region Members
		/// <summary>
		/// Holds the additional or changed physical data to be restored when the physical
		/// schema will be created.
		/// </summary>
        private Dictionary<string, Dictionary<string, Dictionary<string, object>>> _restoreDataItems = new Dictionary<string, Dictionary<string, Dictionary<string, object>>>();
        
        /// <summary>
        /// Holds all interaction data the user made for creating the physical schema.
        /// </summary>
        private Dictionary<string, Dictionary<string, object>> _interactionDataItems = new Dictionary<string, Dictionary<string, object>>();
		
        /// <summary>
        /// Holds a list of all datatypes in the physical layer.
        /// </summary>
        private List<DataType> _dataTypes = new List<DataType>();
		#endregion
		
		#region Constructors / Destructors
		/// <summary>
		/// Default constructor.
		/// </summary>
		public PhysicalItemContainer()
		{
			// Add the system default types
			this._dataTypes.Add(DataType.Unknown);
			this._dataTypes.Add(DataType.String);
			this._dataTypes.Add(DataType.Number);
			this._dataTypes.Add(DataType.Float);
			this._dataTypes.Add(DataType.Boolean);
			this._dataTypes.Add(DataType.DateTime);
			this._dataTypes.Add(DataType.Binary);
		}
		#endregion
		
		#region Properties
		/// <summary>
		/// Count all datatypes.
		/// </summary>
		public int CountDataTypes 
		{
			get { return this._dataTypes.Count; }
		}
		#endregion
		
		#region Methodes
		/// <summary>
		/// Update the value of a data item in the restore collection.
		/// </summary>
		/// <param name="physicalSchema">Physical schema the item belongs to.</param>
		/// <param name="physicalId">Physical id of the element the item belongs to.</param>
		/// <param name="propertyName">Name of the property on the element to update.</param>
		/// <param name="value">Value to be updated.</param>
        public void UpdateRestoreDataItem(IPhysicalSchema physicalSchema, string physicalId, string propertyName, object value)
		{
            if (!Helper.IsXmlSerializable(value))
            {
                throw new Exception("Parameter value must be a value type or must implement IXmlSerializable.");
            }
            if (null == physicalSchema)
            {
                return;
            }

            if (!this._restoreDataItems.ContainsKey(physicalSchema.GetType().FullName))
            {
                this._restoreDataItems.Add(physicalSchema.GetType().FullName, new Dictionary<string, Dictionary<string, object>>());
            }

            Dictionary<string, Dictionary<string, object>> schemaRestoreItems = this._restoreDataItems[physicalSchema.GetType().FullName];
            if (!schemaRestoreItems.ContainsKey(physicalId))
            {
                schemaRestoreItems.Add(physicalId, new Dictionary<string, object>());
            }

            Dictionary<string, object> restoreItems = schemaRestoreItems[physicalId];
            if (!restoreItems.ContainsKey(propertyName))
            { 
                restoreItems.Add(propertyName, null);
            }
            restoreItems[propertyName] = value;
		}

        /// <summary>
        /// Get a data item from the restore collection.
        /// </summary>
		/// <param name="physicalSchema">Physical schema the item belongs to.</param>
		/// <param name="physicalId">Physical id of the element the item belongs to.</param>
		/// <param name="propertyName">Name of the property on the element to get.</param>
        /// <returns>Value of the item if found or null if nothing has been found.</returns>
        public object GetRestoreDataItem(IPhysicalSchema physicalSchema, string physicalId, string propertyName)
        {
            if (null == physicalSchema)
            {
                return null;
            }
            if (!this._restoreDataItems.ContainsKey(physicalSchema.GetType().FullName))
            {
                return null;
            }
            if (!this._restoreDataItems[physicalSchema.GetType().FullName].ContainsKey(physicalId))
            {
                return null;
            }
            if (!this._restoreDataItems[physicalSchema.GetType().FullName][physicalId].ContainsKey(propertyName))
            {
                return null;
            }
            return this._restoreDataItems[physicalSchema.GetType().FullName][physicalId][propertyName];
        }

        /// <summary>
        /// Remove a data item from the restore collection.
        /// </summary>
		/// <param name="physicalSchema">Physical schema the item belongs to.</param>
		/// <param name="physicalId">Physical id of the element the item belongs to.</param>
        public void RemoveRestoreDataItem(IPhysicalSchema physicalSchema, string physicalId)
        {
            if (this._restoreDataItems.ContainsKey(physicalSchema.GetType().FullName))
            {
                if (this._restoreDataItems[physicalSchema.GetType().FullName].ContainsKey(physicalId))
                {
                    this._restoreDataItems[physicalSchema.GetType().FullName].Remove(physicalId);
                }
            }
        }

        /// <summary>
        /// Remove all data items from the restore collection by a logical id.
        /// </summary>
        /// <remarks>
        /// Every physical id in the schema is based on a logical one and
        /// contains it. So all physical items to a logical element id can be found
        /// and removed. This can happen, when the logical element will be removed on
        /// the logical schema for which some physical layers still have data saved.
        /// </remarks>
		/// <param name="physicalSchema">Physical schema the item belongs to.</param>
        /// <param name="logicalId">Id of the logical element.</param>
        private void RemoveRestoreDataItems(IPhysicalSchema physicalSchema, string logicalId)
        {
            if (null == physicalSchema)
            {
                return;
            }
            if (this._restoreDataItems.ContainsKey(physicalSchema.GetType().FullName))
            {
                List<string> physicalIdsToDelete = new List<string>();
                Dictionary<string, Dictionary<string, object>> restoreItems = this._restoreDataItems[physicalSchema.GetType().FullName];
                IEnumerator restoreItemsEnumerator = restoreItems.GetEnumerator();
                while (restoreItemsEnumerator.MoveNext())
                {
                    KeyValuePair<string, Dictionary<string, object>> restoreItem = (KeyValuePair<string, Dictionary<string, object>>)restoreItemsEnumerator.Current;
                    // if the physical id is LIKE the logical id, then we delete the entry
                    if (restoreItem.Key.IndexOf(logicalId) >= 0)
                    {
                        physicalIdsToDelete.Add(restoreItem.Key);
                    }
                }
                foreach (string physicalId in physicalIdsToDelete)
                {
                    this.RemoveRestoreDataItem(physicalSchema, physicalId);
                }
            }
        }

        /// <summary>
        /// Remove all data items from the restore collection.
        /// </summary>
		/// <param name="physicalSchema">Physical schema the item belongs to.</param>
        private void RemoveRestoreDataItems(IPhysicalSchema physicalSchema)
        {
            if (null == physicalSchema)
            {
                return;
            }
            if (this._restoreDataItems.ContainsKey(physicalSchema.GetType().FullName))
            {
	            Dictionary<string, Dictionary<string, object>> restoreItems = this._restoreDataItems[physicalSchema.GetType().FullName];
	            if (null != restoreItems)
	            {
	                restoreItems.Clear();
	            }
            }
        }
        
        /// <summary>
		/// Update the value of a item in the user interaction collection.
        /// </summary>
		/// <param name="physicalSchema">Physical schema the item belongs to.</param>
        /// <param name="logicalId">Id of the logical element.</param>
        /// <param name="value">Value to be updated.</param>
        public void UpdateInteractionDataItem(IPhysicalSchema physicalSchema, string logicalId, object value)
        {
            if (!Helper.IsXmlSerializable(value))
            {
                throw new Exception("Parameter value must be a value type or must implement IXmlSerializable.");
            }
            if (null == physicalSchema)
            {
                return;
            }
            if (!this._interactionDataItems.ContainsKey(physicalSchema.GetType().FullName))
            {
                this._interactionDataItems.Add(physicalSchema.GetType().FullName, new Dictionary<string, object>());
            }

            Dictionary<string, object> schemaInteractions = this._interactionDataItems[physicalSchema.GetType().FullName];
            if (!schemaInteractions.ContainsKey(logicalId))
            {
                schemaInteractions.Add(logicalId, null);
            }

            schemaInteractions[logicalId] = value;
        }

        /// <summary>
        /// Get a data item from the user interaction collection.
        /// </summary>
		/// <param name="physicalSchema">Physical schema the item belongs to.</param>
        /// <param name="logicalId">Id of the logical element.</param>
        /// <returns>Value of the item if found or null if nothing has been found.</returns>
        public object GetInteractionDataItem(IPhysicalSchema physicalSchema, string logicalId)
        {
            if (null == physicalSchema)
            {
                return null;
            }
            if (!this._interactionDataItems.ContainsKey(physicalSchema.GetType().FullName))
            {
                return null;
            }
            if (!this._interactionDataItems[physicalSchema.GetType().FullName].ContainsKey(logicalId))
            {
                return null;
            }
            return this._interactionDataItems[physicalSchema.GetType().FullName][logicalId];
        }

        /// <summary>
        /// Remove all data items from the user interaction collection.
        /// </summary>
		/// <param name="physicalSchema">Physical schema the item belongs to.</param>
        /// <param name="logicalId">Id of the logical element.</param>
        public void RemoveInteractionDataItem(IPhysicalSchema physicalSchema, string logicalId)
        {
            if (null == physicalSchema)
            {
                return;
            }
            if (this._interactionDataItems.ContainsKey(physicalSchema.GetType().FullName))
            {
                Dictionary<string, object> schemaInteractions = this._interactionDataItems[physicalSchema.GetType().FullName];
                if (null != schemaInteractions)
                {
                    schemaInteractions.Remove(logicalId);
                }
            }
        }

        /// <summary>
        /// Remove all data items from the user interaction collection.
        /// </summary>
		/// <param name="physicalSchema">Physical schema the item belongs to.</param>
        private void RemoveInteractionDataItems(IPhysicalSchema physicalSchema)
        {
            if (null == physicalSchema)
            {
                return;
            }
            if (this._interactionDataItems.ContainsKey(physicalSchema.GetType().FullName))
            {
	            Dictionary<string, object> schemaInteractions = this._interactionDataItems[physicalSchema.GetType().FullName];
	            if (null != schemaInteractions)
	            {
	                schemaInteractions.Clear();
	            }
            }
        }

        /// <summary>
        /// Add a new datatype to the schemas.
        /// </summary>
        /// <param name="dataType">Datatype to be added.</param>
        public void AddDataType(DataType dataType)
        { 
            if (!this._dataTypes.Contains(dataType))
            {
                if (dataType.IsDerived)
                {
                    // inform generator
                    List<IGenerator> generatorsToInform = GeneratorManager.Instance.FindInterfaceType(typeof(IGenerator));
                    foreach (IGenerator generator in generatorsToInform)
                    {
                        DataType clonedDataType = (DataType)dataType.Clone();
                        generator.UpdateDataType(clonedDataType, null, PhysicalItemContainer.DataTypeAction.Added);
                    }
                }
                this._dataTypes.Add(dataType);
            }
        }

        /// <summary>
        /// Modify a datatype on the schemas.
        /// </summary>
        /// <param name="newDataType">Modified datatype.</param>
        /// <param name="oldDataType">Original datatype.</param>
        public void ModifyDataType(DataType newDataType, DataType oldDataType)
        {
            if (newDataType.IsDerived)
            {
                DataType dataTypeToUpdate = null;
                foreach (DataType item in this._dataTypes)
                {
                    if (null != item.Name && null != oldDataType.Name &&
                        item.Name.ToLower().Equals(oldDataType.Name.ToLower()))
                    {
                        dataTypeToUpdate = item;
                        break;
                    }
                }
                if (null != dataTypeToUpdate)
                {
                    this.ExchangeDataType(newDataType, dataTypeToUpdate);

                    // inform generators
                    List<IGenerator> generatorsToInform = GeneratorManager.Instance.FindInterfaceType(typeof(IGenerator));
                    foreach (IGenerator generator in generatorsToInform)
                    {
                        DataType clonedNewDataType = (DataType)newDataType.Clone();
                        DataType clonedOldDataType = (DataType)oldDataType.Clone();
                        generator.UpdateDataType(newDataType, clonedOldDataType, PhysicalItemContainer.DataTypeAction.Modified);
                    }

                }
            }
        }

        /// <summary>
        /// Exchange a data type on the schemas.
        /// </summary>
        /// <param name="newDataType">Modified datatype.</param>
        /// <param name="oldDataType">Original datatype.</param>
        private void ExchangeDataType(DataType newDataType, DataType oldDataType)
        {
            List<IPhysicalSchema> schemas = PhysicalSchemaManager.Instance.FindInterfaceType(typeof(IPhysicalSchema));
            foreach (IPhysicalSchema schema in schemas)
            {
                schema.ExchangeDataType(newDataType, oldDataType);
            }
        }

        /// <summary>
        /// Remove a data type from the schemas.
        /// </summary>
        /// <param name="dataType">Datatype to remove.</param>
        public void RemoveDataType(DataType dataType)
        { 
            if (dataType.IsDerived)
            {
                DataType dataTypeToDelete = null;
                foreach (DataType item in this._dataTypes)
                {
                    if (null != item.Name && null != dataType.Name &&
                        item.Name.ToLower().Equals(dataType.Name.ToLower()))
                    {
                        dataTypeToDelete = item;
                        break;
                    }
                }

                this.ExchangeDataType(DataType.Unknown, dataTypeToDelete);

                // inform generators
                List<IGenerator> generatorsToInform = GeneratorManager.Instance.FindInterfaceType(typeof(IGenerator));
                foreach (IGenerator generator in generatorsToInform)
                {
                    DataType clonedOldDataType = (DataType)dataType.Clone();
                    generator.UpdateDataType(null, clonedOldDataType, PhysicalItemContainer.DataTypeAction.Removed);
                }

                if (null != dataTypeToDelete)
                {
                    this._dataTypes.Remove(dataTypeToDelete);
                }
            }
        }

        /// <summary>
        /// Get all datatypes.
        /// </summary>
        /// <returns>Enumerator to parse.</returns>
        public IEnumerator GetDataTypes()
        {
            return this._dataTypes.GetEnumerator();
        }

        /// <summary>
        /// Checks if a datatype has already been added.
        /// </summary>
        /// <param name="dataType">Datatype to check.</param>
        /// <returns>true if the property is contained, else false.</returns>
        public bool ContainsDataType(DataType dataType)
        {
            return this._dataTypes.Contains(dataType);
        }

        /// <summary>
        /// Find a datatype by name.
        /// </summary>
        /// <param name="name">Name of the datatype.</param>
        /// <returns>Datatype if found or null if nothing has been found.</returns>
        public DataType GetDataType(string name)
        {
            if (String.IsNullOrEmpty(name))
            {
                return null;
            }
            foreach (DataType dataType in this._dataTypes)
            {
                if (null != dataType.Name && dataType.Name.ToLower().Equals(dataType.Name.ToLower()))
                {
                    return dataType;
                }
            }
            return null;
        }

        /// <summary>
        /// Clean all user interaction data items.
        /// </summary>
        /// <param name="logicalId">Id of the logical element.</param>
        private void CleanInteractionData(string logicalId)
        {
            IEnumerator physicalSchemasEnumerator = PhysicalSchemaManager.Instance.GetLoadedSchemas();
            while (physicalSchemasEnumerator.MoveNext())
            {
                IPhysicalSchema physicalSchema = (IPhysicalSchema)physicalSchemasEnumerator.Current;
                this.RemoveInteractionDataItem(physicalSchema, logicalId);
            }
        }

        /// <summary>
        /// Clean all restore data items.
        /// </summary>
        /// <param name="logicalId">Id of the logical element.</param>
        private void CleanRestoreData(string logicalId)
        {
            IEnumerator physicalSchemasEnumerator = PhysicalSchemaManager.Instance.GetLoadedSchemas();
            while (physicalSchemasEnumerator.MoveNext())
            {
                IPhysicalSchema physicalSchema = (IPhysicalSchema)physicalSchemasEnumerator.Current;
                this.RemoveRestoreDataItems(physicalSchema, logicalId);
            }
        }

        /// <summary>
        /// Clean all physical data for a logical item.
        /// </summary>
        /// <param name="logicalId">Id of the logical element.</param>
        public void CleanPhysicalData(string logicalId)
        {
            this.CleanInteractionData(logicalId);
            this.CleanRestoreData(logicalId);
        }

        /// <summary>
        /// Reset all data for a physical data.
        /// </summary>
		/// <param name="physicalSchema">Physical schema the item belongs to.</param>
        /// <param name="resetData">Indicates if the restore data items should be resetted.</param>
        /// <param name="resetInteraction">Indicates if the user interaction data items should be resetted.</param>
        public void Reset(IPhysicalSchema physicalSchema, bool resetData, bool resetInteraction)
        { 
        	if (resetData)
        	{
            	this.RemoveRestoreDataItems(physicalSchema);
        	}
        	
        	if (resetInteraction)
        	{
            	this.RemoveInteractionDataItems(physicalSchema);
        	}
        }
		#endregion

        #region IXmlSerializable Members
        /// <summary>
        /// Gets the xml schema object.
        /// </summary>
        /// <returns>Always null.</returns>
        public XmlSchema GetSchema()
        {
            return null;
        }

        /// <summary>
        /// Reads back an instance of this class from a reader.
        /// </summary>
        /// <remarks>Reader must stand on a PhysicalData-Element.</remarks>
        /// <param name="reader">Reader object to read from.</param>
        public void ReadXml(XmlReader reader)
        {
            if (reader.IsStartElement("PhysicalData"))
            {
                if (!reader.IsEmptyElement)
                {
                    reader.ReadStartElement();
                    if (reader.IsStartElement("DataTypes"))
                    {
                        if (!reader.IsEmptyElement)
                        {
                            while (!reader.EOF)
                            {
                                reader.Read();
                                if (reader.NodeType == XmlNodeType.EndElement && reader.Name == "DataTypes")
                                {
                                    break;
                                }
                                if (reader.IsStartElement("DataType"))
                                { 
                                    DataType.BaseDataType baseType = (DataType.BaseDataType)Enum.Parse(typeof(DataType.BaseDataType), reader.GetAttribute("baseType"));
                                    string name = reader.GetAttribute("name");
                                    // we have to use the factory methode because we don't want to check the name
                                    DataType dataType = DataType.CreateDataType(baseType, name);
                                    this._dataTypes.Add(dataType);
                                }
                            }
                        }
                    }
                    if (!reader.IsEmptyElement)
                    {
                        reader.ReadEndElement();
                    }
                    else
                    {
                        reader.ReadStartElement();
                    }
                    
                    if (reader.IsStartElement("RestoreDataItems"))
                    {
                        if (!reader.IsEmptyElement)
                        {
                            while (!reader.EOF)
                            {
                                reader.Read();
                                if (reader.NodeType == XmlNodeType.EndElement && reader.Name == "RestoreDataItems")
                                {
                                    break;
                                }
                                if (reader.IsStartElement() && !reader.IsEmptyElement)
                                {
                                    List<IPhysicalSchema> pschemaResults = PhysicalSchemaManager.Instance.FindType(reader.Name);
                                    if (1 >= pschemaResults.Count)
                                    {
                                        IPhysicalSchema pschema = pschemaResults[0];

                                        if (!reader.IsEmptyElement)
                                        {
                                            bool readNext = true;
                                            while (!reader.EOF)
                                            {
                                                reader.Read();
                                                if (reader.NodeType == XmlNodeType.EndElement && reader.Name == pschema.GetType().FullName)
                                                {
                                                    break;
                                                }
                                                if (reader.IsStartElement("Item"))
                                                {
                                                    string itemId = reader.GetAttribute("id");
                                                    if (!reader.IsEmptyElement)
                                                    {
                                                        while (!reader.EOF)
                                                        {
                                                            // ReadElementString() and ReadSubtree() already read the next node,
                                                            // so we have to check that. Otherwise we would read one node too much
                                                            if (readNext)
                                                            {
                                                                reader.Read();
                                                            }
                                                            readNext = true;
                                                            if (reader.NodeType == XmlNodeType.EndElement && reader.Name == "Item")
                                                            {
                                                                break;
                                                            }
                                                            if (reader.IsStartElement("Property") && !reader.IsEmptyElement)
                                                            {
                                                                string propName = reader.GetAttribute("name");
                                                                string propTypeName = reader.GetAttribute("type");
                                                                string propAssemblyName = reader.GetAttribute("assembly");	// might be null
                                                                
                                                                // try to find the type
                                                                Type type = Type.GetType(propTypeName);
                                                                	
                                                                // if type not avilable try to find by defined assambly
                                                                if (null == type && !String.IsNullOrEmpty(propAssemblyName))
                                                                {
                                                                	Assembly propAssembly = Assembly.LoadWithPartialName(propAssemblyName);
                                                                	if (null != propAssembly)
                                                                	{
                                                                		type = propAssembly.GetType(propTypeName);
                                                                	}
                                                                }
                                                                
                                                                if (null != type)
                                                                {
                                                                    Type interfaceType = type.GetInterface(typeof(IXmlSerializable).FullName);
                                                                    if (null != interfaceType)
                                                                    {
                                                                        ConstructorInfo ci = type.GetConstructor(Type.EmptyTypes);
                                                                        if (null != ci)
                                                                        {
                                                                            object propInstance = ci.Invoke(new object[] { });
                                                                            ((IXmlSerializable)propInstance).ReadXml(reader.ReadSubtree());
                                                                            this.UpdateRestoreDataItem(pschema, itemId, propName, propInstance);
                                                                        }
                                                                    }
                                                                    else
                                                                    {
                                                                        readNext = false;
                                                                        string propValue = reader.ReadElementString();
                                                                        object propInstance = null;
                                                                        
                                                                        Type convertInterface = type.GetInterface(typeof(IConvertible).ToString());
			                                                            // process enum first, 'cause enum is IConvertible but would fail the task
                                                                        if (type.IsEnum)
                                                                        {
                                                                            propInstance = Enum.Parse(type, propValue);
                                                                        }
                                                                        else if (null != convertInterface)
                                                                        {
                                                                            propInstance = Convert.ChangeType(propValue, type);
                                                                        }
                                                                        else 
                                                                        {
                                                                        	// don't know how to convert -> overread
                                                                        	Trace.TraceWarning(string.Format("Could not convert property {0} with value {1}. It will be overread. Please implement IConvertible or IXmlSerializable or use an Enumeration.", propInstance, propValue));
                                                                        }
                                                                        this.UpdateRestoreDataItem(pschema, itemId, propName, propInstance);
                                                                    }
                                                                }
                                                                else
                                                                {
                                                                	// could not find the type -> ignore
                                                                	Trace.TraceWarning(string.Format("Could not find the type {0} by Type.GetType(). Property will be overread.", propTypeName));
                                                                }
                                                            }
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                    if (!reader.IsEmptyElement)
                    {
                        reader.ReadEndElement();
                    }
                    else
                    {
                        reader.ReadStartElement();
                    }

                    if (reader.IsStartElement("InteractionDataItems"))
                    {
                        if (!reader.IsEmptyElement)
                        {
                            while (!reader.EOF)
                            {
                                reader.Read();
                                if (reader.NodeType == XmlNodeType.EndElement && reader.Name == "InteractionDataItems")
                                {
                                    break;
                                }
                                if (reader.IsStartElement() && !reader.IsEmptyElement)
                                {
                                    List<IPhysicalSchema> pschemaResults = PhysicalSchemaManager.Instance.FindType(reader.Name);
                                    if (1 >= pschemaResults.Count)
                                    { 
                                        IPhysicalSchema pschema = pschemaResults[0];

                                        if (!reader.IsEmptyElement)
                                        {
                                            bool readNext = true;
                                            while (!reader.EOF)
                                            {
                                                // ReadElementString() and ReadSubtree() already read the next node,
                                                // so we have to check that. Otherwise we would read one node too much
                                                if (readNext)
                                                {
                                                    reader.Read();
                                                }
                                                readNext = true;
                                                if (reader.NodeType == XmlNodeType.EndElement && reader.Name == pschema.GetType().FullName)
                                                {
                                                    break;
                                                }
                                                if (reader.IsStartElement("Item"))
                                                {
                                                    string itemId = reader.GetAttribute("id");
                                                    if (!reader.IsEmptyElement)
                                                    {
                                                        string propTypeName = reader.GetAttribute("type");
                                                        string propAssemblyName = reader.GetAttribute("assembly");	// might be null
                                                        
                                                        // try to find the type
                                                        Type type = Type.GetType(propTypeName);
                                                        	
                                                        // if type not avilable try to find by defined assambly
                                                        if (null == type && !String.IsNullOrEmpty(propAssemblyName))
                                                        {
                                                        	Assembly propAssembly = Assembly.LoadWithPartialName(propAssemblyName);
                                                        	if (null != propAssembly)
                                                        	{
                                                        		type = propAssembly.GetType(propTypeName);
                                                        	}
                                                        }
                                                        
                                                        if (null != type)
                                                        {
	                                                        Type interfaceType = type.GetInterface(typeof(IXmlSerializable).FullName);
	                                                        if (null != interfaceType)
	                                                        {
	                                                            ConstructorInfo ci = type.GetConstructor(Type.EmptyTypes);
	                                                            if (null != ci)
	                                                            {
	                                                                object propInstance = ci.Invoke(new object[] { });
	                                                                ((IXmlSerializable)propInstance).ReadXml(reader.ReadSubtree());
	                                                                this.UpdateInteractionDataItem(pschema, itemId, propInstance);
	                                                            }
	                                                        }
	                                                        else
	                                                        {
	                                                            readNext = false;
	                                                            string propValue = reader.ReadElementString();
	                                                            object propInstance = null;
	                                                            
	                                                            Type convertInterface = type.GetInterface(typeof(IConvertible).ToString());
	                                                            // process enum first, 'cause enum is IConvertible but would fail the task
	                                                            if (type.IsEnum)
	                                                            {
	                                                                propInstance = Enum.Parse(type, propValue);
	                                                            }
	                                                            else if (null != convertInterface)                                                                        
	                                                            {
	                                                                propInstance = Convert.ChangeType(propValue, type);
	                                                            }
	                                                            else
	                                                            {
		                                                        	// don't know how to convert -> overread
		                                                        	Trace.TraceWarning(string.Format("Could not convert property {0} with value {1}. Please implement IConvertible or IXmlSerializable or use an Enumeration.", propInstance, propValue));
	                                                            }
	                                                            this.UpdateInteractionDataItem(pschema, itemId, propInstance);
	                                                        }
                                                        }
                                                        else
                                                        {
                                                        	// could not find the type -> ignore
                                                        	Trace.TraceWarning(string.Format("Could not find the type {0} by Type.GetType(). Property will be overread.", propTypeName));
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                    if (!reader.IsEmptyElement)
                    {
                        reader.ReadEndElement();
                    }
                    else
                    {
                        reader.ReadStartElement();
                    }

                    // save generator data
                    if (reader.IsStartElement("GeneratorDataItems"))
                    {
                        if (!reader.IsEmptyElement)
                        {
                            while (!reader.EOF)
                            {
                                reader.Read();
                                if (reader.NodeType == XmlNodeType.EndElement && reader.Name == "GeneratorDataItems")
                                {
                                    break;
                                }
                                if (reader.IsStartElement() && !reader.IsEmptyElement)
                                {
                                    List<IGenerator> generatorResults = GeneratorManager.Instance.FindType(reader.Name);
                                    if (1 >= generatorResults.Count)
                                    {
                                        IGenerator generator = generatorResults[0];

                                        if (!reader.IsEmptyElement)
                                        {
                                            while (!reader.EOF)
                                            {
                                                reader.Read();
                                                if (reader.NodeType == XmlNodeType.EndElement && reader.Name == generator.GetType().FullName)
                                                {
                                                    break;
                                                }
                                                if (null != generator.ExtendedData)
                                                {
                                                    generator.ExtendedData.ReadXml(reader.ReadSubtree());
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Writes a the instace of this class to a writer.
        /// </summary>
        /// <param name="writer">Writer object to write into.</param>
        public void WriteXml(XmlWriter writer)
        {
            writer.WriteStartElement("PhysicalData");

            writer.WriteStartElement("DataTypes");
            foreach (DataType dataType in this._dataTypes)
            {
                // we only save custom type
                if (dataType.IsDerived)
                {
                    writer.WriteStartElement("DataType");
                    writer.WriteAttributeString("baseType", dataType.BaseType.ToString());
                    writer.WriteAttributeString("name", dataType.Name);
                    writer.WriteEndElement();
                }
            }
            writer.WriteEndElement();

            writer.WriteStartElement("RestoreDataItems");
            IEnumerator physicalSchemaEnumerator = this._restoreDataItems.GetEnumerator();
            while (physicalSchemaEnumerator.MoveNext())
            {
                KeyValuePair<string, Dictionary<string, Dictionary<string, object>>> physicalSchema = (KeyValuePair<string, Dictionary<string, Dictionary<string, object>>>)physicalSchemaEnumerator.Current;
                writer.WriteStartElement(physicalSchema.Key);
                IEnumerator itemsEnumerator = physicalSchema.Value.GetEnumerator();
                while (itemsEnumerator.MoveNext())
                {
                    KeyValuePair<string, Dictionary<string, object>> items = (KeyValuePair<string, Dictionary<string, object>>)itemsEnumerator.Current;
                    writer.WriteStartElement("Item");
                    writer.WriteAttributeString("id", items.Key);   
                    IEnumerator itemEnumerator = items.Value.GetEnumerator();
                    while (itemEnumerator.MoveNext())
                    { 
                        KeyValuePair<string, object> item = (KeyValuePair<string, object>)itemEnumerator.Current;
                        if (null != item.Value)
                        {
                            writer.WriteStartElement("Property");
                            writer.WriteAttributeString("name", item.Key);
                            writer.WriteAttributeString("type", item.Value.GetType().FullName);
                            writer.WriteAttributeString("assembly", item.Value.GetType().Assembly.GetName().Name);
                            if (item.Value is IXmlSerializable)
                            {
                                ((IXmlSerializable)item.Value).WriteXml(writer);
                            }
                            else if (item.Value is IConvertible)
                            {
                            	string convertedValue = (string)Convert.ChangeType(item.Value, typeof(string));
                            	if (!String.IsNullOrEmpty(convertedValue))
                            	{
                            		writer.WriteString(convertedValue);
                            	}
                            }
                            else
                            {
                                writer.WriteString(item.Value.ToString());
                            }
                            writer.WriteEndElement();
                        }
                    }
                    writer.WriteEndElement();
                }
                writer.WriteEndElement();
            }
            writer.WriteEndElement();
            
            writer.WriteStartElement("InteractionDataItems");
            physicalSchemaEnumerator = this._interactionDataItems.GetEnumerator();
            while (physicalSchemaEnumerator.MoveNext())
            {
                KeyValuePair<string, Dictionary<string, object>> physicalSchema = (KeyValuePair<string, Dictionary<string, object>>)physicalSchemaEnumerator.Current;
                writer.WriteStartElement(physicalSchema.Key);
                IEnumerator itemsEnumerator = physicalSchema.Value.GetEnumerator();
                while (itemsEnumerator.MoveNext())
                {
                    KeyValuePair<string, object> item = (KeyValuePair<string, object>)itemsEnumerator.Current;
                    if (null != item.Value)
                    {
                        writer.WriteStartElement("Item");
                        writer.WriteAttributeString("id", item.Key);
                        writer.WriteAttributeString("type", item.Value.GetType().FullName);
                        writer.WriteAttributeString("assembly", item.Value.GetType().Assembly.GetName().Name);

                        if (item.Value is IXmlSerializable)
                        {
                            ((IXmlSerializable)item.Value).WriteXml(writer);
                        }
                        else if (item.Value is IConvertible)
                        {
                        	string convertedValue = (string)Convert.ChangeType(item.Value, typeof(string));
                        	if (!String.IsNullOrEmpty(convertedValue))
                        	{
                        		writer.WriteString(convertedValue);
                        	}
                        }
                        else
                        {
                            writer.WriteString(item.Value.ToString());
                        }
                        writer.WriteEndElement();
                    }
                }
                writer.WriteEndElement();
            }
            writer.WriteEndElement();

            // save generator data
            writer.WriteStartElement("GeneratorDataItems");
            IEnumerator generatorsEnumerator = GeneratorManager.Instance.GetLoadedPlugIns();
            while (generatorsEnumerator.MoveNext())
            {
                IGenerator generator = (IGenerator)generatorsEnumerator.Current;
                writer.WriteStartElement(generator.GetType().FullName);
                if (null != generator.ExtendedData)
                {
                	PhysicalItemContainer.EnsureWriteSecureXml(generator.ExtendedData, writer);
                }
                writer.WriteEndElement();
            }
            writer.WriteEndElement();

            writer.WriteEndElement();
        }
        
        /// <summary>
        /// Ensures to write into the stream securely. Occuring exceptions from the serializable
        /// object will be ignored. The stream will not be affected.
        /// </summary>
        /// <param name="toBeWritten">Object to be written into the writer.</param>
        /// <param name="writer">Writer to write in when the object to serialize writes without exceptions.</param>
        private static void EnsureWriteSecureXml(IXmlSerializable toBeWritten, XmlWriter writer)
        {
        	if (null == toBeWritten || null == writer)
        	{
        		return;
        	}
        	
            try
            {
            	// write
            	MemoryStream stream = new MemoryStream();
            	XmlWriter secureWriter = XmlWriter.Create(stream);
                toBeWritten.WriteXml(secureWriter);
            	secureWriter.Flush();
            	
            	// rewind and read
            	stream.Seek(0, SeekOrigin.Begin);
            	XmlReader secureReader = XmlReader.Create(stream);
            	// search first data element -> overread xml-declaration an other things
            	while (secureReader.Read())
            	{
            		if (secureReader.NodeType == XmlNodeType.Element)
            		{
            			break;
            		}
            	}
            	
            	// write now if reader still has content
            	if (!secureReader.EOF)
            	{
            		writer.WriteNode(secureReader, false);
            	}
            	
            	// close reader and stream
            	secureReader.Close();
            	secureWriter.Close();
            	stream.Close();
            }
            catch (Exception ex)
            { 
            	// ignore bursts not to risk a creepy format
            	Trace.TraceError(string.Format("Exception: {0}\r\nStackTrace: {1}", ex.Message, ex.StackTrace));
            }
        }
        #endregion
    }
}
