#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.Runtime.Serialization;
using System.Xml.Serialization;
using System.Collections;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
#endregion

namespace dataMagus.Common.Physical
{
	/// <summary>
	/// Description of DataType.
	/// </summary>
    [Serializable]
    public class DataType : IXmlSerializable, ICloneable
	{
		#region Constants
		/// <summary>
		/// Base datatype for String.
		/// </summary>
		public static readonly DataType String = new DataType(BaseDataType.String);
		
		/// <summary>
		/// Base datatype for Number.
		/// </summary>
		public static readonly DataType Number = new DataType(BaseDataType.Number);
		
		/// <summary>
		/// Base datatype for Float.
		/// </summary>
		public static readonly DataType Float = new DataType(BaseDataType.Float);
		
		/// <summary>
		/// Base datatype for Boolean.
		/// </summary>
		public static readonly DataType Boolean = new DataType(BaseDataType.Boolean);
		
		/// <summary>
		/// Base datatype for DateTime.
		/// </summary>
		public static readonly DataType DateTime = new DataType(BaseDataType.DateTime);
		
		/// <summary>
		/// Base datatype for Binary.
		/// </summary>
		public static readonly DataType Binary = new DataType(BaseDataType.Binary);
		
		/// <summary>
		/// Base datatype for Unknown.
		/// </summary>
		public static readonly DataType Unknown = new DataType(BaseDataType.Unknown);
        
		/// <summary>
		/// Index counter to create uniqueness of data type names when having equal names.
		/// </summary>
		private int _uniqueDataTypeNameIndex = 0;
		#endregion
		
		#region Enumerations
		/// <summary>
		/// Base types
		/// </summary>
		public enum BaseDataType
		{
			/// <summary>
			/// Type Unknown
			/// </summary>
			Unknown,

			/// <summary>
			/// Type String
			/// </summary>
			String,
			
			/// <summary>
			/// Type Number
			/// </summary>
			Number,
			
			/// <summary>
			/// Type Float
			/// </summary>
			Float,
			
			/// <summary>
			/// Type Boolean
			/// </summary>
			Boolean,
			
			/// <summary>
			/// Type DateTime
			/// </summary>
			DateTime,
			
			/// <summary>
			/// Type Binary
			/// </summary>
			Binary
		}
		#endregion
		
		#region Members
		/// <summary>
		/// Type the datatype is based on.
		/// </summary>
		private BaseDataType _baseType = BaseDataType.Unknown;
		
		/// <summary>
		/// Name of the datatype.
		/// </summary>
		private string _name;
		
		/// <summary>
		/// Indicates if the type has been derived.
		/// </summary>
		private bool _isDerived = true;
		#endregion
		
		#region Constructors / Destructors
		/// <summary>
		/// Default constructor.
		/// </summary>
		/// <remarks>Only used for serialization</remarks>
		public DataType()
		{
		}
		
		/// <summary>
		/// Constructor.
		/// </summary>
		/// <param name="baseType">Type the datatype is based on.</param>
		private DataType(BaseDataType baseType)
			: this(baseType, baseType.ToString())
		{
            this._name = this.EnsureUniqueDataTypeName(this._baseType.ToString()); 
            this._isDerived = false;
		}
		
		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="baseType">Type the datatype is based on.</param>
		/// <param name="name">Name of the type.</param>
		public DataType(BaseDataType baseType, string name)
		{
			this._baseType = baseType;
            if (string.IsNullOrEmpty(name))
            {
                name = baseType.ToString();
            }
			this._name = this.EnsureUniqueDataTypeName(name);
			if (string.IsNullOrEmpty(this._name))
			{
				this._name = this._baseType.ToString();
			}
		}
		#endregion
		
		#region Properties
		/// <summary>
		/// Type the datatype is based on.
		/// </summary>
		public BaseDataType BaseType 
		{
			get { return this._baseType; }
		}
		
		/// <summary>
		/// Name of the datatype.
		/// </summary>
		public string Name 
		{
			get { return this._name; }
		}
		
		/// <summary>
		/// Indicates if the type has been derived.
		/// </summary>
		public bool IsDerived 
		{
			get { return this._isDerived; }
		}
		#endregion
		
		#region Methods
		/// <summary>
		/// Create a new datatype.
		/// </summary>
		/// <param name="baseType">Type the datatype is based on.</param>
		/// <param name="name">Name of the type.</param>
		/// <returns>Created derived datatype.</returns>
        public static DataType CreateDataType(DataType.BaseDataType baseType, string name)
        { 
            // for internal use only
            DataType datatype = new DataType();
            datatype._name = name;
            datatype._baseType = baseType;
            datatype._isDerived = true;
            return datatype;
        }

        /// <summary>
        /// Ensure that the name of the datatype is unique.
        /// </summary>
        /// <param name="name">Name of the type.</param>
        /// <returns>New name.</returns>
        private string EnsureUniqueDataTypeName(string name)
        {
            IEnumerator dataTypeEnumerator = DataContainer.Instance.PhysicalData.GetDataTypes();
            while (dataTypeEnumerator.MoveNext())
            {
                DataType item = (DataType)dataTypeEnumerator.Current;
                // Special case 
                if (null == item)
                {
                    break;
                }
                if (item.Name.ToLower().Equals(name.ToLower()))
                {
                    name += (++this._uniqueDataTypeNameIndex).ToString();
                    return name;
                }
            }
            return name;
        }

        /// <summary>
        /// String representation of the object.
        /// </summary>
        /// <returns>Name of the datatype.</returns>
        public override string ToString()
        {
            if (this.IsDerived)
            {
                return string.Format("{0} : {1}", this.Name, this.BaseType.ToString());
            }
            return this.Name;
        }

        /// <summary>
        /// Gets the xml schema object.
        /// </summary>
        /// <returns>Always null.</returns>
        public System.Xml.Schema.XmlSchema GetSchema()
        {
            return null;
        }

        /// <summary>
        /// Reads back an instance of this class from a reader.
        /// </summary>
        /// <remarks>Reader must stand on a DataType-Element.</remarks>
        /// <param name="reader">Reader object to read from.</param>
        public virtual void ReadXml(System.Xml.XmlReader reader)
        {
        	reader.ReadStartElement();
            string name = reader.GetAttribute("name");
            string baseType = reader.GetAttribute("baseType");
            if (!reader.IsEmptyElement)
            {
            	reader.ReadEndElement();
            }

            this._name = name;
           	this._baseType = (DataType.BaseDataType)Enum.Parse(typeof(DataType.BaseDataType), baseType);
            this._isDerived = true;
            if (name == baseType && Enum.IsDefined(typeof(DataType.BaseDataType), name))
            {
            	this._isDerived = false;
            }
        }
        
        /// <summary>
        /// Check if a datatype is equal to another. Equality is when then
        /// Name, BaseType and IsDerived-Flag are equal.
        /// </summary>
        /// <param name="obj">Object to compare.</param>
        /// <returns>true if the object is equal, otherwise false.</returns>
		public override bool Equals(object obj)
		{
			if (obj is DataType)
			{
				DataType toCompare = (DataType)obj;
				return (this.BaseType == toCompare.BaseType
					&& this.IsDerived == toCompare.IsDerived
					&& this.Name == toCompare.Name);
			}
			else 
			{
				return base.Equals(obj);
			}
		}
		
		/// <summary>
		/// Calculate a unique hashcode for the datatype.
		/// </summary>
		/// <returns>Hashcode.</returns>
		public override int GetHashCode()
		{
			string hashString = string.Format("{0}-{1}-{2}", this.Name, this.BaseType.ToString(), this.IsDerived.ToString());
			return hashString.GetHashCode();
		}

        /// <summary>
        /// Writes a the instace of this class to a writer.
        /// </summary>
        /// <param name="writer">Writer object to write into.</param>
        public virtual void WriteXml(System.Xml.XmlWriter writer)
        {
            writer.WriteStartElement("DataType");
            writer.WriteAttributeString("name", this.Name);
            writer.WriteAttributeString("baseType", this._baseType.ToString());
            writer.WriteEndElement();
        }
		#endregion

        #region ICloneable Members
        /// <summary>
        /// Clones the datatype.
        /// </summary>
        /// <returns>Cloned object.</returns>
        public object Clone()
        {
            BinaryFormatter binaryFormatter = new BinaryFormatter();
            MemoryStream stream = new MemoryStream();
            binaryFormatter.Serialize(stream, this);
            stream.Seek(0, SeekOrigin.Begin);
            DataType cloneElement = (DataType)binaryFormatter.Deserialize(stream);
            return cloneElement;
        }
        #endregion
    }
}
