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

namespace dataMagus.Common.Physical.Relational
{
	/// <summary>
	/// This class represents a physical relational table.
	/// </summary>
    [Serializable]
	public class PhysicalRelationalTable : PhysicalElement
	{
		#region Members
		/// <summary>
		/// Holds all columns (including key columns)
		/// </summary>
		/// <remarks>Dealing with this list must also synchronize the key properties if needed.</remarks>
        private List<PhysicalRelationalColumn> _columns = new List<PhysicalRelationalColumn>();
        
		/// <summary>
		/// Holds all primary key columns.
		/// </summary>
        private List<PhysicalRelationalColumn> _primaryKeyColumns = new List<PhysicalRelationalColumn>();
        
		/// <summary>
		/// Holds all indexes.
		/// </summary>
        private List<PhysicalRelationalIndex> _indexes = new List<PhysicalRelationalIndex>();
        
		/// <summary>
		/// Index counter to create uniqueness of column names when having equal names.
		/// </summary>
        private int _uniqueColumnNameIndex = 0;
		#endregion
		
		#region Constructors / Destructors
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="schema">Physical relational schema.</param>
        /// <param name="id">Unique id of the object.</param>
        /// <param name="origin">Origin element the table is based on.</param>
        /// <param name="name">Name of the table.</param>
        public PhysicalRelationalTable(PhysicalRelationalSchema schema, string id, IElement origin, string name)
			: base(schema, id, origin, name)
		{
		}
		#endregion
		
		#region Properties
		/// <summary>
		/// Count all columns.
		/// </summary>
		[Browsable(false)]
        public int CountColumns
        {
            get { return this._columns.Count; }
        }

		/// <summary>
		/// Count all primary key columns.
		/// </summary>
		[Browsable(false)]
        public int CountPrimaryKeyColumns
        {
            get { return this._primaryKeyColumns.Count; }
        }

		/// <summary>
		/// Count all indexes.
		/// </summary>
		[Browsable(false)]
        public int CountIndexes
        {
            get { return this._indexes.Count; }
        }
		#endregion
		
		#region Methodes
		/// <summary>
		/// Begin initialization mode.
		/// </summary>
		/// <remarks>Prevents event handling.</remarks>
        public override void BeginInit()
        {
            base.BeginInit();
            foreach (PhysicalRelationalColumn column in this._columns)
            {
                column.BeginInit();
            }

            foreach (PhysicalRelationalIndex index in this._indexes)
            {
                index.BeginInit();
            }
        }

		/// <summary>
		/// End initialization mode.
		/// </summary>
		/// <remarks>Event handling will continue.</remarks>
		/// <param name="forceEvent">Indicates if an Undefined event has to be fired to inform other listeners.</param>
        public override void EndInit(bool forceEvent)
        {
            foreach (PhysicalRelationalColumn column in this._columns)
            {
                column.EndInit(forceEvent);
            }

            foreach (PhysicalRelationalIndex index in this._indexes)
            {
                index.EndInit(forceEvent);
            }

            base.EndInit(forceEvent);
        }

        /// <summary>
        /// Add a new column.
        /// </summary>
        /// <param name="column">Column to add.</param>
        public void AddColumn(PhysicalRelationalColumn column)
        {
            if (!this._columns.Contains(column))
            {
                column.Name = this.EnsureUniqueColumnName(column);
                column.Parent = this;
                this._columns.Add(column);
                this.FireElementChangedEvent(this, new ElementEventArgs(this, ElementChangedAction.AddedElement, column));
            }
        }

        /// <summary>
        /// Remove a column.
        /// </summary>
        /// <param name="column">Column to remove.</param>
        public void RemoveColumn(PhysicalRelationalColumn column)
        {
            this._columns.Remove(column);
            if (this._primaryKeyColumns.Contains(column))
            {
                this._primaryKeyColumns.Remove(column);
            }
            this.FireElementChangedEvent(this, new ElementEventArgs(this, ElementChangedAction.RemovedElement, column));
        }

        /// <summary>
        /// Checks if an column has already been added.
        /// </summary>
        /// <param name="column">Column to check.</param>
        /// <returns>true if the column is contained, else false.</returns>
        public bool ContainsColumn(PhysicalRelationalColumn column)
        {
            return this._columns.Contains(column);
        }

		/// <summary>
		/// Find a column by id.
		/// </summary>
		/// <param name="id">Id to search for.</param>
		/// <returns>The found column or null if nothing has been found.</returns>
        public PhysicalRelationalColumn FindColumn(string id)
        {
            foreach (PhysicalRelationalColumn column in this._columns)
            {
                if (column.Id.Equals(id))
                {
                    return column;
                }
            }
            return null;
        }

        /// <summary>
        /// Get all relationships.
        /// </summary>
        /// <returns>Enumerator to parse</returns>
        public IEnumerator GetRelationships()
        {
            List<PhysicalRelationalRelationship> results = new List<PhysicalRelationalRelationship>();
        	PhysicalRelationalSchema schema = (PhysicalRelationalSchema)this._schema;
        	IEnumerator relEnum = schema.GetRelationships();
            while (relEnum.MoveNext())
            {
                PhysicalRelationalRelationship rel = (PhysicalRelationalRelationship)relEnum.Current;
                if (rel.Table1.Equals(this) || rel.Table2.Equals(this))
                {
                    results.Add(rel);
                }
            }
            return results.GetEnumerator();
        }

		/// <summary>
		/// Ensure that the column has a unique name.
		/// </summary>
		/// <param name="column">Column to check.</param>
		/// <returns>New name for the column.</returns>
        private string EnsureUniqueColumnName(PhysicalRelationalColumn column)
        {
            string name = column.Name;
            foreach (PhysicalRelationalColumn item in this._columns)
            {
                if (item.Name.ToLower().Equals(column.Name.ToLower()))
                {
                    name += (++this._uniqueColumnNameIndex).ToString();
                    return name;
                }
            }
            return name;
        }

		/// <summary>
		/// Find an index by id.
		/// </summary>
		/// <param name="id">Id to search for.</param>
		/// <returns>The found index or null if nothing has been found.</returns>
        public PhysicalRelationalIndex FindIndex(string id)
        {
            foreach (PhysicalRelationalIndex index in this._indexes)
            {
                if (index.Id.Equals(id))
                {
                    return index;
                }
            }
            return null;
        }

        /// <summary>
        /// Get all columns.
        /// </summary>
        /// <returns>Enumerator to parse</returns>
        public IEnumerator GetColumns()
        {
            return this._columns.GetEnumerator();
        }

        /// <summary>
        /// Add a new primary key column.
        /// </summary>
        /// <param name="column">Primary key column to add.</param>
        public void AddPrimaryKeyColumn(PhysicalRelationalColumn column)
        {
            if (!this._primaryKeyColumns.Contains(column))
            {
                column.Parent = this;
                column.AllowNulls = false;
                this._primaryKeyColumns.Add(column);

                // make sure that the primary key column is also listed as a normal column!
                this.AddColumn(column);

                // we fire an undefined event action because a key column is not a new column; it is 
                // only a reference to another column
                this.FireElementChangedEvent(this, new ElementEventArgs(this, ElementChangedAction.Undefined, column));
            }
        }

        /// <summary>
        /// Remove a primary key column.
        /// </summary>
        /// <param name="column">Primary key column to remove.</param>
        public void RemoveKeyColumn(PhysicalRelationalColumn column)
        {
            this._primaryKeyColumns.Remove(column);
            // we fire an undefined event action because a key column is not a new column; it is 
            // only a reference to another column
            this.FireElementChangedEvent(this, new ElementEventArgs(this, ElementChangedAction.Undefined, column));
        }

        /// <summary>
        /// Checks if an primary key column has already been added.
        /// </summary>
        /// <param name="column">Primary key column to check.</param>
        /// <returns>true if the primary key column is contained, else false.</returns>
        public bool ContainsPrimaryKeyColumn(PhysicalRelationalColumn column)
        {
            return this._primaryKeyColumns.Contains(column);
        }

        /// <summary>
        /// Get all primary key columns.
        /// </summary>
        /// <returns>Enumerator to parse.</returns>
        public IEnumerator GetPrimaryKeyColumns()
        {
            return this._primaryKeyColumns.GetEnumerator();
        }

        /// <summary>
        /// Add a new index.
        /// </summary>
        /// <param name="index">Index to add.</param>
        public void AddIndex(PhysicalRelationalIndex index)
        {
            if (!this._indexes.Contains(index))
            {
                this._indexes.Add(index);
                this.FireElementChangedEvent(this, new ElementEventArgs(this, ElementChangedAction.AddedElement, index));
            }
        }

        /// <summary>
        /// Remove an index.
        /// </summary>
        /// <param name="index">Index to remove.</param>
        public void RemoveIndex(PhysicalRelationalIndex index)
        {
            this._indexes.Remove(index);
            this.FireElementChangedEvent(this, new ElementEventArgs(this, ElementChangedAction.RemovedElement, index));
        }

        /// <summary>
        /// Checks if an index has already been added.
        /// </summary>
        /// <param name="index">Index to check.</param>
        /// <returns>true if the index is contained, else false.</returns>
        public bool ContainsIndex(PhysicalRelationalIndex index)
        {
            return this._indexes.Contains(index);
        }

        /// <summary>
        /// Get all indexes.
        /// </summary>
        /// <returns>Enumerator to parse</returns>
        public IEnumerator GetIndexes()
        {
            return this._indexes.GetEnumerator();
        }

        /// <summary>
        /// Count the self relationships of this table.
        /// </summary>
        /// <returns>Number of self relationships.</returns>
        public int CountSelfRelationshipLinks()
        {
        	int cnt = 0;
        	PhysicalRelationalSchema schema = (PhysicalRelationalSchema)this._schema;
        	IEnumerator relEnum = schema.GetRelationships();
        	while (relEnum.MoveNext())
        	{
        		PhysicalRelationalRelationship rel = (PhysicalRelationalRelationship)relEnum.Current;
        		// count only selfrelation
        		cnt += (this == rel.Table1 && this == rel.Table2 ? 1 : 0);
        	}
        	return cnt;
        }
        
        /// <summary>
        /// Count the relationships of this table.
        /// </summary>
        /// <param name="includeSelfRelations">Indicates if the self relationships should be concidered.</param>
        /// <returns>Number of self relationships.</returns>
        public int CountRelationshipLinks(bool includeSelfRelations)
        {
        	int cnt = 0;
        	PhysicalRelationalSchema schema = (PhysicalRelationalSchema)this._schema;
        	IEnumerator relEnum = schema.GetRelationships();
        	while (relEnum.MoveNext())
        	{
        		PhysicalRelationalRelationship rel = (PhysicalRelationalRelationship)relEnum.Current;
        		// count relationship from or to table (excluding selfrelation)
        		cnt += (rel.Table1 != rel.Table2 && (this == rel.Table1 || this == rel.Table2) ? 1 : 0);
        	}
        	cnt += (includeSelfRelations ? this.CountSelfRelationshipLinks() : 0);
        	return cnt;
        }
        
        /// <summary>
        /// Count the relationships of this table having with another one.
        /// </summary>
        /// <param name="table">Other table.</param>
        /// <returns>Number of found relationships between this table and the other one.</returns>
        public int CountRelationshipLinksBetween(IElement table)
        {
        	int cnt = 0;
        	PhysicalRelationalSchema schema = (PhysicalRelationalSchema)this._schema;
        	IEnumerator relEnum = schema.GetRelationships();
        	while (relEnum.MoveNext())
        	{
        		PhysicalRelationalRelationship rel = (PhysicalRelationalRelationship)relEnum.Current;
        		// count relationship between this and given table
        		cnt += ((this == rel.Table1 && table == rel.Table2)
        		        || (table == rel.Table1 && this == rel.Table2) ? 1 : 0);
        	}
        	return cnt;
        }
        
        /// <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)
        {
            // write first the element name
            writer.WriteStartElement("Table");

            // write base items
            base.WriteXml(writer);

            // write my items
            writer.WriteStartElement("PrimaryKeyColumns");
            foreach (PhysicalRelationalColumn column in this._primaryKeyColumns)
            {
                column.WriteXml(writer);
            }
            writer.WriteEndElement();

            writer.WriteStartElement("Columns");
            foreach (PhysicalRelationalColumn column in this._columns)
            {
                column.WriteXml(writer);
            }
            writer.WriteEndElement();

            writer.WriteStartElement("Indexes");
            foreach (PhysicalRelationalIndex index in this._indexes)
            {
                index.WriteXml(writer);
            }
            writer.WriteEndElement();

            writer.WriteEndElement();
        }
		#endregion
	}
}
