#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;
#endregion

namespace dataMagus.Common.Logical
{
	/// <summary>
	/// This class extends a logical relationship for n:m implementation.
	/// </summary>
	[Serializable]
	public class ManyToManyLogicalRelationship : LogicalRelationship, IAttributable
	{
		#region Constants
		/// <summary>
		/// Default exact value.
		/// </summary>
		private const int UndefinedExactValue = 0;
		#endregion
		
		#region Enumerations
		/// <summary>
		/// Possible cardinalities.
		/// </summary>
		public enum Cardinality
		{
			/// <summary>
			/// Zero, one or more.
			/// </summary>
			ZeroOneOrMore,
			
			/// <summary>
			/// One or more.
			/// </summary>
			OneOrMore,
			
			/// <summary>
			/// Exaclty.
			/// </summary>
			Exactly
		}
		#endregion
		
		#region Members
		/// <summary>
		/// Cardinality to the first entity.
		/// </summary>
		private Cardinality _cardinality1 = Cardinality.ZeroOneOrMore;
		
		/// <summary>
		/// Cardinality to the second entity.
		/// </summary>
		private Cardinality _cardinality2 = Cardinality.ZeroOneOrMore;
		
		/// <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>
		/// Exact value for the first entity when having Cardinality.Exactly.
		/// </summary>
		private int _exactValue1 = ManyToManyLogicalRelationship.UndefinedExactValue;
		
		/// <summary>
		/// Exact value for the second entity when having Cardinality.Exactly.
		/// </summary>
		private int _exactValue2 = ManyToManyLogicalRelationship.UndefinedExactValue;
        
		/// <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 relationship.</param>
		/// <param name="entity1">Logical first entity.</param>
		/// <param name="entity2">Logical second entity.</param>
		/// <param name="rolename1">Rolename of the first entity.</param>
		/// <param name="rolename2">Rolename of the second entity.</param>
		/// <param name="cardinality1">Cardinality to the first entity.</param>
		/// <param name="cardinality2">Cardinality to the second entity.</param>
		public ManyToManyLogicalRelationship(string name, LogicalEntity entity1, LogicalEntity entity2,
			string rolename1, string rolename2, Cardinality cardinality1, Cardinality cardinality2)
			: base(name, entity1, entity2, rolename1, rolename2)
		{
            this._cardinality1 = cardinality1;
            this._cardinality2 = cardinality2;
		}

		/// <summary>
		/// Default constructor
		/// </summary>
		/// <remarks>Only used for the serialization.</remarks>
        private ManyToManyLogicalRelationship()
        { 
        }
		#endregion
		
		#region Properties
		/// <summary>
		/// Cardinality to the first entity.
		/// </summary>
		public Cardinality Cardinality1 
		{
			get { return this._cardinality1; }
			set 
			{
                if (this._cardinality1 != value)
                {
                    if (Cardinality.Exactly != value)
                    {
                        this.ExactValue1 = ManyToManyLogicalRelationship.UndefinedExactValue;
                    }
                    this._cardinality1 = value;
                    this.FireElementChangedEvent(this, new ElementEventArgs(this, ElementChangedAction.ChangedProperty));
                }
			}
		}
		
		/// <summary>
		/// Cardinality to the second entity.
		/// </summary>
		public Cardinality Cardinality2 
		{
			get { return this._cardinality2; }
			set 
			{
                if (this._cardinality2 != value)
                {
                    if (Cardinality.Exactly != value)
                    {
                        this.ExactValue2 = ManyToManyLogicalRelationship.UndefinedExactValue;
                    }
                    this._cardinality2 = value;
                    this.FireElementChangedEvent(this, new ElementEventArgs(this, ElementChangedAction.ChangedProperty));
                }
			}
		}
		
		/// <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; }
		}

		/// <summary>
		/// Exact value for the first entity when having Cardinality.Exact.
		/// </summary>
		[Browsable(false)]
		public int ExactValue1
		{
			get { return this._exactValue1; }
			set
            {
                if (this._exactValue1 != value)
                {
                    this._exactValue1 = value;
                    this.FireElementChangedEvent(this, new ElementEventArgs(this, ElementChangedAction.ChangedProperty));
                }
            }
		}
		
		/// <summary>
		/// Exact value for the second entity when having Cardinality.Exact.
		/// </summary>
		[Browsable(false)]
		public int ExactValue2
		{
			get { return this._exactValue2; }
			set 
            {
                if (this._exactValue2 != value)
                {
                    this._exactValue2 = value;
                    this.FireElementChangedEvent(this, new ElementEventArgs(this, ElementChangedAction.ChangedProperty));
                }
            }
		}
		#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 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)
        {
            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>
		/// Creates an n:m relationship object from a reader.
		/// </summary>
		/// <remarks>Reader must stand directly before the object.</remarks>
		/// <param name="reader">Reader containing the object.</param>
		/// <returns>The created instance.</returns>
        public static ManyToManyLogicalRelationship CreateInstance(System.Xml.XmlReader reader)
        {
            ManyToManyLogicalRelationship instance = new ManyToManyLogicalRelationship();
            instance.ReadXml(reader);
            return instance;
        }

        /// <summary>
        /// Reads back an instance of this class from a reader.
        /// </summary>
        /// <remarks>Reader must stand on a ManyToManyRelationship-Element.</remarks>
        /// <param name="reader">Reader object to read from.</param>
        public override void ReadXml(System.Xml.XmlReader reader)
        {
            base.BeginInit();

            if (reader.IsStartElement("ManyToManyRelationship"))
            {
                base.ReadXml(reader);
                this.Cardinality1 = (Cardinality)Enum.Parse(typeof(Cardinality), reader.GetAttribute("cardinality1"));
                this.Cardinality2 = (Cardinality)Enum.Parse(typeof(Cardinality), reader.GetAttribute("cardinality2"));
                this.ExactValue1 = Convert.ToInt32(reader.GetAttribute("exactValue1"));
                this.ExactValue2 = Convert.ToInt32(reader.GetAttribute("exactValue2"));

                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)
        {
            writer.WriteStartElement("ManyToManyRelationship");
            base.WriteXml(writer);

            writer.WriteAttributeString("cardinality1", this.Cardinality1.ToString());
            writer.WriteAttributeString("cardinality2", this.Cardinality2.ToString());
            writer.WriteAttributeString("exactValue1", this.ExactValue1.ToString());
            writer.WriteAttributeString("exactValue2", this.ExactValue2.ToString());

            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

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

        /// <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>
        /// 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)
        {
            // Clean physical data too
            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));
        }
        #endregion
    }
}
