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

namespace dataMagus.Common.Logical
{
	/// <summary>
	/// This class represents a logical entity.
	/// </summary>
	[Serializable]
    public class LogicalEntity : LogicalElement, IAttributable
	{
		#region Members
		/// <summary>
		/// Holds all attributes (including key attributes)
		/// </summary>
		/// <remarks>Dealing with this list must also synchronize the key attributes if needed.</remarks>
		private List<LogicalAttribute> _attributes = new List<LogicalAttribute>();
		
		/// <summary>
		/// Holds all key attributes.
		/// </summary>
		private List<LogicalAttribute> _keyAttributes = new List<LogicalAttribute>();
		
		/// <summary>
		/// Index counter to create uniqueness of attribute names when having equal names.
		/// </summary>
        private int _uniqueAttributeNameIndex = 0;
		#endregion
		
		#region Constructors / Destructors
		/// <summary>
		/// Constructor.
		/// </summary>
		/// <param name="name">Name of the entity.</param>
		public LogicalEntity(string name) 
			: base(name)
		{
		}

		/// <summary>
		/// Default constructor.
		/// </summary>
		/// <remarks>Only used for serialization.</remarks>
        private LogicalEntity()
            : base(String.Empty)
        {
        }
		#endregion
		
		#region Properties
		/// <summary>
		/// Count all attributes.
		/// </summary>
        [Browsable(false)]
        public int CountAttributes
        {
            get { return this._attributes.Count; }
        }

        /// <summary>
        /// Count all key attributes.
        /// </summary>
        [Browsable(false)]
        public int CountKeyAttributes
        {
            get { return this._keyAttributes.Count; }
        }
		#endregion
		
		#region Methodes
		/// <summary>
		/// Begin initialization mode.
		/// </summary>
		/// <remarks>Prevents event handling.</remarks>
        public override void BeginInit()
        {
            base.BeginInit();
            foreach (LogicalAttribute attribute in this._attributes)
            {
                attribute.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 (LogicalAttribute attribute in this._attributes)
            {
                attribute.EndInit(forceEvent);
            }
            base.EndInit(forceEvent);
        }

        /// <summary>
        /// Add an attribute.
        /// </summary>
        /// <param name="attribute">Attribute to add.</param>
		public void AddAttribute(LogicalAttribute attribute)
		{
            if (!this._attributes.Contains(attribute))
            {
                if (this._initMode)
                {
                    attribute.BeginInit();
                }
                attribute.Name = this.EnsureUniqueAttributeName(attribute);
                this._attributes.Add(attribute);
                this.FireElementChangedEvent(this, new ElementEventArgs(this, ElementChangedAction.AddedElement, attribute));
            }
		}

		/// <summary>
		/// Ensure that the attribute has a unique name.
		/// </summary>
		/// <param name="attribute">Attribute to check.</param>
		/// <returns>New name for the attribute.</returns>
        private string EnsureUniqueAttributeName(LogicalAttribute attribute)
        {
            string name = attribute.Name;
            foreach (LogicalAttribute item in this._attributes)
            {
                if (item.Name.ToLower().Equals(attribute.Name.ToLower()))
                {
                    name += (++this._uniqueAttributeNameIndex).ToString();
                    return name;
                }
            }
            return name;
        }
		
        /// <summary>
        /// Removes an attribute.
        /// </summary>
        /// <param name="attribute">Attribute to remove.</param>
		public void RemoveAttribute(LogicalAttribute attribute)
		{
            // Remove all related physical data for the atttribute
            DataContainer.Instance.PhysicalData.CleanPhysicalData(attribute.Id);

			this._attributes.Remove(attribute);
			if (this._keyAttributes.Contains(attribute))
			{
				this._keyAttributes.Remove(attribute);
			}
			this.FireElementChangedEvent(this, new ElementEventArgs(this, ElementChangedAction.RemovedElement, attribute));
		}

        /// <summary>
        /// Checks if an attribute has already been added.
        /// </summary>
        /// <param name="attribute">Attribute to check.</param>
        /// <returns>true if the attribute is contained, else false.</returns>
        public bool ContainsAttribute(LogicalAttribute attribute)
        {
            return this._attributes.Contains(attribute);
        }

        /// <summary>
        /// Get all attributes.
        /// </summary>
        /// <returns>Enumerator to parse.</returns>
        public IEnumerator GetAttributes()
        {
            return this._attributes.GetEnumerator();
        }

        /// <summary>
        /// Add a key attribute.
        /// </summary>
        /// <param name="attribute">Key attribute to add.</param>
        public void AddKeyAttribute(LogicalAttribute attribute)
        {
            if (!this._keyAttributes.Contains(attribute))
            {
                this._keyAttributes.Add(attribute);
                // we fire an undefined event action because a key attribute is not a new attribute; it is 
                // only a reference to another attribute
                this.FireElementChangedEvent(this, new ElementEventArgs(this, ElementChangedAction.Undefined, attribute));
            }
        }

        /// <summary>
        /// Removes a key attribute.
        /// </summary>
        /// <param name="attribute">Key attribute to remove.</param>
        public void RemoveKeyAttribute(LogicalAttribute attribute)
        {
            // Clean physical data too
            DataContainer.Instance.PhysicalData.CleanPhysicalData(attribute.Id);

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

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

        /// <summary>
        /// Get all attributes.
        /// </summary>
        /// <returns>Enumerator to parse.</returns>
        public IEnumerator GetKeyAttributes()
        {
            return this._keyAttributes.GetEnumerator();
        }
        
        /// <summary>
        /// Count the self relations of this entity.
        /// </summary>
        /// <returns>Number of self relations.</returns>
        public int CountSelfRelationshipLinks()
        {
        	int cnt = 0;
        	IEnumerator relEnum = DataContainer.Instance.LogicalSchema.GetRelationships();
        	while (relEnum.MoveNext())
        	{
        		LogicalRelationship rel = (LogicalRelationship)relEnum.Current;
        		// count only selfrelation
        		cnt += (this == rel.Entity1 && this == rel.Entity2 ? 1 : 0);
        	}
        	return cnt;
        }

        /// <summary>
        /// Get all relationships of this entity.
        /// </summary>
        /// <returns>Enumerator to parse.</returns>
        public IEnumerator GetRelationships()
        {
            List<LogicalRelationship> results = new List<LogicalRelationship>();
            IEnumerator relEnum = DataContainer.Instance.LogicalSchema.GetRelationships();
            while (relEnum.MoveNext())
            {
                LogicalRelationship rel = (LogicalRelationship)relEnum.Current;
                if (rel.Entity1.Equals(this) || rel.Entity2.Equals(this))
                {
                    results.Add(rel);
                }
            }
            return results.GetEnumerator();
        }
        
        /// <summary>
        /// Count the relations of this entity.
        /// </summary>
        /// <param name="includeSelfRelations">Indicates if the self relationships should be concidered.</param>
        /// <returns>Number of self relations.</returns>
        public int CountRelationshipLinks(bool includeSelfRelations)
        {
        	int cnt = 0;
        	IEnumerator relEnum = DataContainer.Instance.LogicalSchema.GetRelationships();
        	while (relEnum.MoveNext())
        	{
        		LogicalRelationship rel = (LogicalRelationship)relEnum.Current;
        		// count relationship from or to entity (excluding selfrelation)
        		cnt += (rel.Entity1 != rel.Entity2 && (this == rel.Entity1 || this == rel.Entity2) ? 1 : 0);
        	}
        	cnt += (includeSelfRelations ? this.CountSelfRelationshipLinks() : 0);
        	return cnt;
        }

        /// <summary>
        /// Count the relations of this entity having with another one.
        /// </summary>
        /// <param name="entity">Other relationship.</param>
        /// <returns>Number of found relations between this entity and the other one.</returns>
        public int CountRelationshipLinksBetween(IElement entity)
        {
        	int cnt = 0;
        	IEnumerator relEnum = DataContainer.Instance.LogicalSchema.GetRelationships();
        	while (relEnum.MoveNext())
        	{
        		LogicalRelationship rel = (LogicalRelationship)relEnum.Current;
        		// count relationship between this and given entity
        		cnt += ((this == rel.Entity1 && entity == rel.Entity2)
        		        || (entity == rel.Entity1 && this == rel.Entity2) ? 1 : 0);
        	}
        	return cnt;
        }
        
        /// <summary>
        /// Reads back an instance of this class from a reader.
        /// </summary>
        /// <remarks>Reader must stand on a Entity-Element.</remarks>
        /// <param name="reader">Reader object to read from.</param>
        public override void ReadXml(System.Xml.XmlReader reader)
        {
            base.BeginInit();

            if (reader.IsStartElement("Entity"))
            {
                if (!reader.IsEmptyElement)
                {
                    base.ReadXml(reader);

                    reader.ReadStartElement();
                    if (reader.IsStartElement("Attributes"))
                    {
                        if (!reader.IsEmptyElement)
                        {
                            while (!reader.EOF)
                            {
                                reader.Read();
                                if (reader.NodeType == System.Xml.XmlNodeType.EndElement && reader.Name == "Attributes")
                                {
                                    break;
                                }
                                if (reader.IsStartElement("Attribute"))
                                {
                                    LogicalAttribute attribute = new LogicalAttribute(this, reader.GetAttribute("name"));
                                    attribute.ReadXml(reader);
                                    this._attributes.Add(attribute);
                                }
                            }
                        }
                    }
                    if (!reader.IsEmptyElement)
                    {
                        reader.ReadEndElement();
                    }
                    else
                    {
                        reader.ReadStartElement();
                    }

                    if (reader.IsStartElement("KeyAttributes"))
                    {
                        if (!reader.IsEmptyElement)
                        {
                            while (!reader.EOF)
                            {
                                reader.Read();
                                if (reader.NodeType == System.Xml.XmlNodeType.EndElement && reader.Name == "KeyAttributes")
                                {
                                    break;
                                }
                                if (reader.IsStartElement("Attribute"))
                                {
                                    string id = reader.GetAttribute("id");
                                    LogicalAttribute attribute = LogicalSchema.ContainsKeyAttribute(id, this._attributes);
                                    if (null != attribute)
                                    {
                                        this._keyAttributes.Add(attribute);
                                    }
                                }
                            }
                        }
                    }
                    if (!reader.IsEmptyElement)
                    {
                        reader.ReadEndElement();
                    }
                    else
                    {
                        reader.ReadStartElement();
                    }
                }
            }
            base.EndInit(false);
        }

        /// <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("Entity");

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

            // write my items
            writer.WriteStartElement("Attributes");
            foreach (LogicalAttribute attribute in this._attributes)
            {
                attribute.WriteXml(writer);
            }
            writer.WriteEndElement();

            writer.WriteStartElement("KeyAttributes");
            foreach (LogicalAttribute attribute in this._keyAttributes)
            {
                attribute.WriteXml(writer);
            }
            writer.WriteEndElement();

            writer.WriteEndElement();
        }
		#endregion
    }
}
