#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.ComponentModel;
using System.Runtime.Serialization;
using System.Collections.Generic;
using dataMagus.Common.Logical;
using System.Collections;
#endregion

namespace dataMagus.Common.Physical.Relational
{
	/// <summary>
	/// This class represents a physical relational column.
	/// </summary>
    [Serializable]
	public class PhysicalRelationalColumn : PhysicalElement
	{
		#region Constants
        /// <summary>
        /// Name of the DataType property
        /// </summary>
		private const string DataTypePropertyName = "DataType";

		/// <summary>
        /// Name of the AllowNulls property
        /// </summary>
        private const string AllowNullsPropertyName = "AllowNulls";
		#endregion
		
		#region Members
		/// <summary>
		/// Parent element the column belongs to.
		/// </summary>
        private PhysicalRelationalTable _parent;

        /// <summary>
        /// Datatype of the column.
        /// </summary>
        private DataType _dataType = DataType.Unknown;

        /// <summary>
        /// Indicator if the column allows null values.
        /// </summary>
        private bool _allowNulls = true;
		#endregion
		
		#region Constructors / Destructors
        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="schema">Physical relational schema.</param>
        /// <param name="parent">Parent element the column belongs to.</param>
        /// <param name="id">Unique id of the object.</param>
        /// <param name="origin">Origin element the column is based on.</param>
        /// <param name="name">Name of the column.</param>
        /// <param name="dataType">Datatype of the column.</param>
		/// <param name="allowNulls">Indicator if the column allows null values.</param>
        public PhysicalRelationalColumn(PhysicalRelationalSchema schema, PhysicalRelationalTable parent, string id, IElement origin, string name, DataType dataType, bool allowNulls)
			: base(schema, id, origin, name)
		{
            this._parent = parent;
			this._dataType = dataType;

            // Restore physical changes if available
            object dataTypeToRestore = DataContainer.Instance.PhysicalData.GetRestoreDataItem(base._schema, this.Id, PhysicalRelationalColumn.DataTypePropertyName);
            if (null != dataTypeToRestore)
            {
                this._dataType = (DataType)dataTypeToRestore;
            }
		}

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="schema">Physical relational schema.</param>
        /// <param name="parent">Parent element the column belongs to.</param>
        /// <param name="id">Unique id of the object.</param>
        /// <param name="origin">Origin element the column is based on.</param>
        /// <param name="name">Name of the column.</param>
        /// <param name="dataType">Datatype of the column.</param>
        public PhysicalRelationalColumn(PhysicalRelationalSchema schema, PhysicalRelationalTable parent, string id, IElement origin, string name, DataType dataType)
            : this(schema, parent, id, origin, name, DataType.Unknown, true)
        { 
        }

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="schema">Physical relational schema.</param>
        /// <param name="parent">Parent element the column belongs to.</param>
        /// <param name="id">Unique id of the object.</param>
        /// <param name="origin">Origin element the column is based on.</param>
        /// <param name="name">Name of the column.</param>
        public PhysicalRelationalColumn(PhysicalRelationalSchema schema, PhysicalRelationalTable parent, string id, IElement origin, string name)
            : this(schema, parent, id, origin, name, DataType.Unknown, true)
		{
		}

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="schema">Physical relational schema.</param>
        /// <param name="parent">Parent element the column belongs to.</param>
        /// <param name="id">Unique id of the object.</param>
        /// <param name="origin">Origin element the column is based on.</param>
        /// <param name="name">Name of the column.</param>
		/// <param name="allowNulls">Indicator if the column allows null values.</param>
        public PhysicalRelationalColumn(PhysicalRelationalSchema schema, PhysicalRelationalTable parent, string id, IElement origin, string name, bool allowNulls)
            : this(schema, parent, id, origin, name, DataType.Unknown, allowNulls)
        {
        }
		#endregion
		
		#region Properties
        /// <summary>
        /// Datatype of the column.
        /// </summary>
        [ReadOnly(true)]
		public DataType DataType 
		{
			get { return this._dataType; }
			set 
			{ 
				if (this._dataType != value)
				{
                    this.EnsureForeignKeyDataTypes(this, value);
					this.EnsureUpdateOriginalRestoreDataItem(base._schema, this, PhysicalRelationalColumn.DataTypePropertyName, value);
					this._dataType = value;
					this.FireElementChangedEvent(this, new ElementEventArgs(this, ElementChangedAction.ChangedProperty));
				}
			}
		}

		/// <summary>
		/// Parent element the column belongs to.
		/// </summary>
		[Browsable(false)]
        public PhysicalRelationalTable Parent
        {
            get { return this._parent; }
            set { this._parent = value; }
        }

        /// <summary>
        /// Indicator if the column allows null values.
        /// </summary>
        [Browsable(false)]
        public bool AllowNulls
        {
            get { return this._allowNulls; }
            set 
            {
                if (this._allowNulls != value && !this.IsPartOfPrimaryKey)
                {
                    this.EnsureUpdateOriginalRestoreDataItem(base._schema, this, PhysicalRelationalColumn.AllowNullsPropertyName, value);
                    this._allowNulls = value;
                    this.FireElementChangedEvent(this, new ElementEventArgs(this, ElementChangedAction.ChangedProperty));
                }
            }
        }

        /// <summary>
        /// Indicator if the column belongs to the primary key constraint on the table.
        /// </summary>
        public bool IsPartOfPrimaryKey
        {
            get
            {
                if (null == this._parent)
                {
                    return false;
                }
                return this._parent.ContainsPrimaryKeyColumn(this);
            }
        }

        /// <summary>
        /// Indicator if the column belongs to the foreign key constraint on the table.
        /// </summary>
        public bool IsPartOfForeignKey
        {
            get
            {
                PhysicalRelationalSchema schema = this._schema as PhysicalRelationalSchema;
                if (null == schema)
                {
                    return false;
                }
                IEnumerator relEnumerator = schema.GetRelationships();
                while (relEnumerator.MoveNext())
                {
                    PhysicalRelationalRelationship relationship = (PhysicalRelationalRelationship)relEnumerator.Current;
   
                    IEnumerator enumerator = relationship.GetColumnMappings();
                    while (enumerator.MoveNext())
                    {
                        KeyValuePair<PhysicalRelationalColumn, PhysicalRelationalColumn> mapping = (KeyValuePair<PhysicalRelationalColumn, PhysicalRelationalColumn>)enumerator.Current;
                        if (relationship.Table2.Equals(this.Parent))
                        {
                            if (null != mapping.Value && mapping.Value.Equals(this))
                            {
                                return true;
                            }
                        }
                    }
                }
                return false;
            }
        }
		#endregion
		
		#region Methodes
		/// <summary>
		/// Ensures that the datatype will be adjusted when chaning it on the source column.
		/// </summary>
		/// <param name="primaryKeyColumn">Source column.</param>
		/// <param name="value">Datatype to be checked.</param>
        private void EnsureForeignKeyDataTypes(PhysicalRelationalColumn primaryKeyColumn, DataType value)
        {
            if (!this.IsClone)
            {
                // update all foreign key columns if available
                PhysicalRelationalSchema schema = this._schema as PhysicalRelationalSchema;
                if (null != schema)
                {
                    IEnumerator relEnumerator = schema.GetRelationships();
                    while (relEnumerator.MoveNext())
                    {
                        PhysicalRelationalRelationship relationship = (PhysicalRelationalRelationship)relEnumerator.Current;

                        IEnumerator enumerator = relationship.GetColumnMappings();
                        while (enumerator.MoveNext())
                        {
                            KeyValuePair<PhysicalRelationalColumn, PhysicalRelationalColumn> mapping = (KeyValuePair<PhysicalRelationalColumn, PhysicalRelationalColumn>)enumerator.Current;
                            if (relationship.Table1.Equals(this.Parent))
                            {
                                if (null != mapping.Value && null != mapping.Key && mapping.Key == this)
                                {
                                    mapping.Value.DataType = value;
                             
                                }
                            }
                        }
                    }
                }
            }
        }

		/// <summary>
		/// Gets the parent element.
		/// </summary>
		/// <returns>Parent element.</returns>
        public override IElement GetParent()
        {
        	return this._parent;
        }

        /// <summary>
        /// Writes a the instace of this class to a writer.
        /// </summary>
        /// <param name="writer">Writer object to write into.</param>
        public override void WriteXml(System.Xml.XmlWriter writer)
        {
            writer.WriteStartElement("Column");
            base.WriteXml(writer);
            writer.WriteAttributeString("allowNulls", this._allowNulls.ToString());
            writer.WriteAttributeString("isPartOfPrimaryKey", this.IsPartOfPrimaryKey.ToString());
            writer.WriteAttributeString("isPartOfForeignKey", this.IsPartOfForeignKey.ToString());

            writer.WriteStartElement("DataType");
            writer.WriteAttributeString("baseType", this._dataType.BaseType.ToString());
            writer.WriteAttributeString("name", this._dataType.Name);
            writer.WriteEndElement();

            writer.WriteEndElement();
        }
		#endregion
	}
}
