#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.Text;
using System.Collections;
#endregion

namespace dataMagus.Common.Physical.Objectoriented
{
	/// <summary>
	/// This class represents a physical objectoriented class.
	/// </summary>
    [Serializable]
    public class PhysicalObjectorientedClass : PhysicalElement
    {
        #region Members
		/// <summary>
		/// Holds all properties (including key properties)
		/// </summary>
		/// <remarks>Dealing with this list must also synchronize the key properties if needed.</remarks>
		private List<PhysicalObjectorientedProperty> _properties = new List<PhysicalObjectorientedProperty>();
        
		/// <summary>
		/// Holds all key properties.
		/// </summary>
		private List<PhysicalObjectorientedProperty> _keyProperties = new List<PhysicalObjectorientedProperty>();
        
		/// <summary>
		/// Index counter to create uniqueness of property names when having equal names.
		/// </summary>
		private int _uniquePropertyNameIndex = 0;
        #endregion

        #region Constructors / Destructors
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="schema">Physical objectoriented schema.</param>
        /// <param name="id">Unique id of the object.</param>
        /// <param name="origin">Origin element the class is based on.</param>
        /// <param name="name">Name of the class.</param>
        public PhysicalObjectorientedClass(PhysicalObjectorientedSchema schema, string id, IElement origin, string name)
			: base(schema, id, origin, name)
		{
		}
        #endregion

        #region Properties
		/// <summary>
		/// Count all properties.
		/// </summary>
        [Browsable(false)]
        public int CountProperties
        {
            get { return this._properties.Count; }
        }

        /// <summary>
        /// Count all key properties.
        /// </summary>
        [Browsable(false)]
        public int CountKeyProperties
        {
            get { return this._keyProperties.Count; }
        }
        #endregion

        #region Methodes
		/// <summary>
		/// Begin initialization mode.
		/// </summary>
		/// <remarks>Prevents event handling.</remarks>
        public override void BeginInit()
        {
            base.BeginInit();
            foreach (PhysicalObjectorientedProperty property in this._properties)
            {
                property.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 (PhysicalObjectorientedProperty property in this._properties)
            {
                property.EndInit(forceEvent);
            }
            base.EndInit(forceEvent);
        }

        /// <summary>
        /// Add an new property.
        /// </summary>
        /// <param name="property">Property to add.</param>
        public void AddProperty(PhysicalObjectorientedProperty property)
        {
            if (!this._properties.Contains(property))
            {
                property.Name = this.EnsureUniquePropertyName(property);
                this._properties.Add(property);
                this.FireElementChangedEvent(this, new ElementEventArgs(this, ElementChangedAction.AddedElement, property));
            }
        }

        /// <summary>
        /// Removes an property.
        /// </summary>
        /// <param name="property">Property to remove.</param>
        public void RemoveProperty(PhysicalObjectorientedProperty property)
        {
            this._properties.Remove(property);
            if (this._properties.Contains(property))
            {
                this._keyProperties.Remove(property);
            }
            this.FireElementChangedEvent(this, new ElementEventArgs(this, ElementChangedAction.RemovedElement, property));
        }

		/// <summary>
		/// Ensure that the property has a unique name.
		/// </summary>
		/// <param name="property">Property to check.</param>
		/// <returns>New name for the property.</returns>
        private string EnsureUniquePropertyName(PhysicalObjectorientedProperty property)
        {
            string name = property.Name;
            foreach (PhysicalObjectorientedProperty item in this._properties)
            {
                if (item.Name.ToLower().Equals(property.Name.ToLower()))
                {
                    name += (++this._uniquePropertyNameIndex).ToString();
                    return name;
                }
            }
            return name;
        }

        /// <summary>
        /// Checks if an property has already been added.
        /// </summary>
        /// <param name="property">Property to check.</param>
        /// <returns>true if the property is contained, else false.</returns>
        public bool ContainsProperty(PhysicalObjectorientedProperty property)
        {
            return this._properties.Contains(property);
        }

        /// <summary>
        /// Get all properties.
        /// </summary>
        /// <returns>Enumerator to parse.</returns>
        public IEnumerator GetProperties()
        {
            return this._properties.GetEnumerator();
        }

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

        /// <summary>
        /// Add a key property.
        /// </summary>
        /// <param name="property">Key property to add.</param>
        public void AddKeyProperty(PhysicalObjectorientedProperty property)
        {
            if (!this._keyProperties.Contains(property))
            {
                this._keyProperties.Add(property);

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

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

        /// <summary>
        /// Removes a key property.
        /// </summary>
        /// <param name="property">Key property to remove.</param>
        public void RemoveKeyProperty(PhysicalObjectorientedProperty property)
        {
            this._keyProperties.Remove(property);
            // we fire an undefined event action because a key property is not a new property; it is 
            // only a reference to another property
            this.FireElementChangedEvent(this, new ElementEventArgs(this, ElementChangedAction.Undefined, property));
        }

        /// <summary>
        /// Checks of a key property has already been added.
        /// </summary>
        /// <param name="property">Key property to check.</param>
        /// <returns>true if the key property is contained, else false.</returns>
        public bool ContainsKeyProperty(PhysicalObjectorientedProperty property)
        {
            return this._keyProperties.Contains(property);
        }

        /// <summary>
        /// Get all key properties.
        /// </summary>
        /// <returns>Enumerator to parse.</returns>
        public IEnumerator GetKeyProperties()
        {
            return this._keyProperties.GetEnumerator();
        }

        /// <summary>
        /// Count the self associations of this class.
        /// </summary>
        /// <returns>Number of self associations.</returns>
        public int CountSelfAssociationLinks()
        {
        	int cnt = 0;
        	PhysicalObjectorientedSchema schema = (PhysicalObjectorientedSchema)this._schema;
        	IEnumerator assocEnum = schema.GetAssociations();
        	while (assocEnum.MoveNext())
        	{
        		PhysicalObjectorientedAssociation assoc = (PhysicalObjectorientedAssociation)assocEnum.Current;
        		// count only selfassociations
        		cnt += (this == assoc.Class1 && this == assoc.Class2 ? 1 : 0);
        	}
        	return cnt;
        }
        
        /// <summary>
        /// Count the associations of this class.
        /// </summary>
        /// <param name="includeSelfAssociations">Indicates if the self associations should be concidered.</param>
        /// <returns>Number of self associations.</returns>
        public int CountAssociationLinks(bool includeSelfAssociations)
        {
        	int cnt = 0;
        	PhysicalObjectorientedSchema schema = (PhysicalObjectorientedSchema)this._schema;
        	IEnumerator assocEnum = schema.GetAssociations();
        	while (assocEnum.MoveNext())
        	{
        		PhysicalObjectorientedAssociation assoc = (PhysicalObjectorientedAssociation)assocEnum.Current;
        		// count relationship from or to table (excluding selfrelation)
        		cnt += (assoc.Class1 != assoc.Class2 && (this == assoc.Class1 || this == assoc.Class2) ? 1 : 0);
        	}
        	cnt += (includeSelfAssociations ? this.CountSelfAssociationLinks() : 0);
        	return cnt;
        }

        /// <summary>
        /// Count the associations of this class having with another one.
        /// </summary>
        /// <param name="classItem">Other class.</param>
        /// <returns>Number of found associations between this class and the other one.</returns>
        public int CountAssociationLinksBetween(IElement classItem)
        {
        	int cnt = 0;
        	PhysicalObjectorientedSchema schema = (PhysicalObjectorientedSchema)this._schema;
        	IEnumerator assocEnum = schema.GetAssociations();
        	while (assocEnum.MoveNext())
        	{
        		PhysicalObjectorientedAssociation assoc = (PhysicalObjectorientedAssociation)assocEnum.Current;
        		// count associations between this and given classItem
        		cnt += ((this == assoc.Class1 && classItem == assoc.Class2)
        		        || (classItem == assoc.Class1 && this == assoc.Class2) ? 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("Class");

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

            // write my items
            writer.WriteStartElement("KeyProperties");
            foreach (PhysicalObjectorientedProperty property in this._keyProperties)
            {
                property.WriteXml(writer);
            }
            writer.WriteEndElement();

            writer.WriteStartElement("Properties");
            foreach (PhysicalObjectorientedProperty property in this._properties)
            {
                property.WriteXml(writer);
            }
            writer.WriteEndElement();

            writer.WriteEndElement();
        }
        #endregion
    }
}
