#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.Diagnostics;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using System.Drawing;
using dataMagus.Common.Logical;
#endregion

namespace dataMagus.Common.Physical.Objectoriented
{
	/// <summary>
	/// This class is used to convert an logical schema into a physical objectoriented one.
    /// If we have a relationship between two entities, we name them entity 1 or left entity
    /// respectively entity 2 or right entity. The same goes with an association between two classes.
	/// </summary>
    public class PhysicalObjectorientedSchemaConverter
    {
        #region Enumerations
        /// <summary>
        /// Possible answer results by user interaction.
        /// </summary>
        /// <remarks>Answer codes are used to store.</remarks>
        private enum OneToManyAnswers
        {
        	/// <summary>
        	/// Processing an association having no properties.
        	/// </summary>
            WithoutLinkClassWithoutProperties = 40,
            
            /// <summary>
        	/// Processing an association having properties to be transfered.
            /// </summary>
            WithoutLinkClassTransferProperties = 41,
            
            /// <summary>
        	/// Processing an association an creating a link class.
            /// </summary>
            WithLinkClass = 42
        }
        #endregion

        #region Members
        /// <summary>
        /// Current physical objectoriented schema.
        /// </summary>
        private PhysicalObjectorientedSchema _currentSchema;
        
        /// <summary>
        /// Object used to interact with the user.
        /// </summary>
        private IUserInteraction _userInteraction;
        
        /// <summary>
        /// Holds a list of all physical ids.
        /// </summary>
        private List<string> _physicalIds = new List<string>();
        
        /// <summary>
		/// Counter to create uniqueness of physical ids.
        /// </summary>
        private static int _physicalIdCounter = 0;
        #endregion

        #region Constructors / Destructors
        /// <summary>
        /// Default constructor
        /// </summary>
        public PhysicalObjectorientedSchemaConverter()
        {   
        }
        #endregion

        #region Properties
        /// <summary>
        /// Object used to interact with the user.
        /// </summary>
        public IUserInteraction UserInteraction
        {
            get { return this._userInteraction; }
            set { this._userInteraction = value; }
        }
        #endregion

        #region Methods
        /// <summary>
        /// Convert the logical schema into the physical objectoriented one.
        /// </summary>
        /// <param name="schema">Physical objectoriented schema to convert in.</param>
        public void Convert(IPhysicalSchema schema)
        {
            this._physicalIds.Clear();
            PhysicalObjectorientedSchemaConverter._physicalIdCounter = 0;

            // We need to have a reference for user interactions
            if (null == this.UserInteraction)
            {
                throw new Exception("UserInteraction must not be null");
            }

            // Buffer the schema
            this._currentSchema = (PhysicalObjectorientedSchema)schema;

            // Set the init mode because we don't want to fire changed events at the moment
            this._currentSchema.BeginInit();

            // Create a physical table for each logical entity
            IEnumerator entitiesEnumerator = DataContainer.Instance.LogicalSchema.GetEntities();
            while (entitiesEnumerator.MoveNext())
            {
                LogicalEntity entity = (LogicalEntity)entitiesEnumerator.Current;
                this._currentSchema.AddClass(this.CreateClass(entity));
            }

            // Loop through the logical relationships and transfer them to physical ones
            IEnumerator relEnumerator = DataContainer.Instance.LogicalSchema.GetRelationships();
            while (relEnumerator.MoveNext())
            { 
                LogicalRelationship rel = (LogicalRelationship)relEnumerator.Current;
            
                PhysicalObjectorientedClass class1 = this.FindClassForLogicalEntity(rel.Entity1);
                PhysicalObjectorientedClass class2 = this.FindClassForLogicalEntity(rel.Entity2);

                if (rel is IsALogicalRelationship)
                {
                    this.AddIsAAssociation((IsALogicalRelationship)rel, class1, class2);
                }
                else if (rel is OneToManyLogicalRelationship)
                {
                    this.AddOneToManyAssociation((OneToManyLogicalRelationship)rel, class1, class2);
                }
                else if (rel is OneToOneLogicalRelationship)
                {
                    this.AddOneToOneAssociation((OneToOneLogicalRelationship)rel, class1, class2);
                }
                else if (rel is ManyToManyLogicalRelationship)
                {
                    this.AddManyToManyAssociation((ManyToManyLogicalRelationship)rel, class1, class2);
                }
            }

            // End the init mode and fire the changed event
            this._currentSchema.EndInit(true);
        }
        
		/// <summary>
		/// Ensure that there exists a physical id only once.
		/// </summary>
		/// <param name="physicalId">Physical id to check.</param>
		/// <returns>New physical id.</returns>
        private string EnsureUniquePhysicalId(string physicalId)
        {
            string id = physicalId;
            if (this._physicalIds.Contains(physicalId))
            {
                id = String.Format("{0}#{1}", id, ++PhysicalObjectorientedSchemaConverter._physicalIdCounter);
                
            	// inform tracer about the problem, but don't let the converter burst
            	Trace.TraceError(string.Format("Error: The generated physical ID ({0}) is not unique! A new one ({1}) has been created:", physicalId, id));
            }
            this._physicalIds.Add(id);
            return id;
        }

        /// <summary>
        /// Create a physical id for a relationship.
        /// </summary>
        /// <param name="entityLeft">Left entity.</param>
        /// <param name="classRight">Right class.</param>
        /// <returns>New physical id.</returns>
        private string CreateRelationshipPhysicalId(LogicalEntity entityLeft, PhysicalObjectorientedClass classRight)
        {
            string id = String.Format("{0}_{1}", entityLeft.Id, classRight.Id);
            return this.EnsureUniquePhysicalId(id);
        }

        /// <summary>
        /// Create a physical id for a relationship.
        /// </summary>
        /// <param name="relationship">Logical relationship.</param>
        /// <returns>New physical id.</returns>
        private string CreateRelationshipPhysicalId(LogicalRelationship relationship)
        {
            string id = relationship.Id;
            return this.EnsureUniquePhysicalId(id);
        }

        /// <summary>
        /// Create a physical id for a relationship.
        /// </summary>
        /// <param name="entityLeft">Left entity.</param>
        /// <param name="classRight">Right class.</param>
        /// <param name="rel">Logical relationship.</param>
        /// <returns>New physical id.</returns>
        private string CreateRelationshipPhysicalId(LogicalEntity entityLeft, PhysicalObjectorientedClass classRight, LogicalRelationship rel)
        {
            string id = String.Format("{0}_{1}_{2}", entityLeft.Id, classRight.Id, rel.Id);
            return this.EnsureUniquePhysicalId(id);
        }

        /// <summary>
        /// Create a physical id for a relationship.
        /// </summary>
        /// <param name="classLeft">Left class.</param>
        /// <param name="entityRight">Right entity.</param>
        /// <param name="rel">Logical relationship.</param>
        /// <returns>New physical id.</returns>
        private string CreateRelationshipPhysicalId(PhysicalObjectorientedClass classLeft, LogicalEntity entityRight, LogicalRelationship rel)
        {
            string id = String.Format("{0}_{1}_{2}", classLeft.Id, entityRight.Id, rel.Id);
            return this.EnsureUniquePhysicalId(id);
        }

        /// <summary>
        /// Create a physical id for a link class.
        /// </summary>
        /// <param name="relationship">Logical relationship.</param>
        /// <returns>New physical id.</returns>
        private string CreateLinkClassPhysicalId(LogicalRelationship relationship)
        {
            string id = String.Format("{0}", relationship.Id);
            return this.EnsureUniquePhysicalId(id);
        }

		/// <summary>
		/// Add an is-a association.
		/// </summary>
		/// <param name="rel">Logical xxx relationship.</param>
		/// <param name="class1">Physical class 1.</param>
		/// <param name="class2">Physical class 2.</param>
        private void AddIsAAssociation(IsALogicalRelationship rel,
            PhysicalObjectorientedClass class1, PhysicalObjectorientedClass class2)
        { 
            // There is no need to ask the user about his options --> they're clear to us!

            // Create a normal is-a-association
            IsAPhysicalObjectorientedAssociation physicalAssoc = new IsAPhysicalObjectorientedAssociation(
                this._currentSchema, 
                this.CreateRelationshipPhysicalId(rel), 
                rel,
                Helper.ToTitleCase(rel.Name),
                class1, class2,
                Helper.ToTitleCase(rel.Rolename1),
                Helper.ToTitleCase(rel.Rolename2));

            this._currentSchema.AddAssociation(physicalAssoc);
        }

		/// <summary>
		/// Add an n:m association.
		/// </summary>
		/// <param name="rel">Logical n:m relationship.</param>
		/// <param name="class1">Physical class 1.</param>
		/// <param name="class2">Physical class 2.</param>
        private void AddManyToManyAssociation(ManyToManyLogicalRelationship rel,
            PhysicalObjectorientedClass class1, PhysicalObjectorientedClass class2)
        {
            // There is no need to ask the user about his options --> they're clear to us

            // If there are attributes on the logical many-to-many relationship, we create a link class
            // and two one-to-many associations. If not, all we need is a many-to-many association.
            if (rel.CountAttributes > 0)
            {
                // There are attributes on the relationship

                // Create a link class
                PhysicalObjectorientedClass linkClass = this.CreateLinkClass(rel);
                this._currentSchema.AddClass(linkClass);

                // Create an association from the class on the left to the link class
                PhysicalObjectorientedAssociation assocClass1ToLinkClass = this.CreateOneToManyLeftToLinkClass(
                    rel, class1, linkClass,
                    OneToManyPhysicalObjectorientedAssociation.CardinalityForOne.OnlyOne,
                    this.MapManyToManyCardinalityIntoOneToManyCardinalityForMany(rel.Cardinality2));

                this._currentSchema.AddAssociation(assocClass1ToLinkClass);

                // Create an association from the link class to the class on the right side
                PhysicalObjectorientedAssociation assocLinkClassToClass2 = this.CreateOneToManyRightToLinkClass(
                    rel, class2, linkClass,
                    OneToManyPhysicalObjectorientedAssociation.CardinalityForOne.OnlyOne,
                    this.MapManyToManyCardinalityIntoOneToManyCardinalityForMany(rel.Cardinality1));

                this._currentSchema.AddAssociation(assocLinkClassToClass2);
            }
            else
            { 
                // There are no attributes on the relationship
                ManyToManyPhysicalObjectorientedAssociation physicalAssoc = new ManyToManyPhysicalObjectorientedAssociation(
                    this._currentSchema, 
                    this.CreateRelationshipPhysicalId(rel), 
                    rel,
                    Helper.ToTitleCase(rel.Name),
                    class1, class2,
                    Helper.ToTitleCase(rel.Rolename1),
                    Helper.ToTitleCase(rel.Rolename2),
                    this.MapManyToManyCardinality(rel.Cardinality1),
                    this.MapManyToManyCardinality(rel.Cardinality2));

                this._currentSchema.AddAssociation(physicalAssoc);
            }

        }

		/// <summary>
		/// Add a 1:1 association.
		/// </summary>
		/// <param name="rel">Logical 1:1 relationship.</param>
		/// <param name="class1">Physical class 1.</param>
		/// <param name="class2">Physical class 2.</param>
        private void AddOneToOneAssociation(OneToOneLogicalRelationship rel,
            PhysicalObjectorientedClass class1, PhysicalObjectorientedClass class2)
        { 
            // There is no need to aske the user about his options --> they're clear to us

            // If there are attributes on the logical one-to-one relationship, we create a link class
            // and two one-to-one associations. If not, all we need is a one-to-one association.
            if (rel.CountAttributes > 0)
            { 
                // There are attributes on the relationship

                // Create a link class
                PhysicalObjectorientedClass linkClass = this.CreateLinkClass(rel);
                this._currentSchema.AddClass(linkClass);

                // Create an association from the class on the left to the link class
                PhysicalObjectorientedAssociation assocClass1ToLinkClass = this.CreateOneToOneLeftToLinkClass(
                    rel, class1, linkClass,
                    OneToOnePhysicalObjectorientedAssociation.Cardinality.OnlyOne,
                    this.MapOneToOneCardinality(rel.Cardinality2));

                this._currentSchema.AddAssociation(assocClass1ToLinkClass);

                // Create an association from the link class to the class on the right side
                PhysicalObjectorientedAssociation assocLinkClassToClass2 = this.CreateOneToOneRightToLinkClass(
                    rel, class2, linkClass,
                    OneToOnePhysicalObjectorientedAssociation.Cardinality.OnlyOne,
                    this.MapOneToOneCardinality(rel.Cardinality1));

                this._currentSchema.AddAssociation(assocLinkClassToClass2);
            }
            else 
            {
                // There are no attributes on the relationship
                OneToOnePhysicalObjectorientedAssociation physicalAssoc = new OneToOnePhysicalObjectorientedAssociation(
                    this._currentSchema, 
                    this.CreateRelationshipPhysicalId(rel), 
                    rel,
                    Helper.ToTitleCase(rel.Name),
                    class1, class2,
                    Helper.ToTitleCase(rel.Rolename1),
                    Helper.ToTitleCase(rel.Rolename2),
                    this.MapOneToOneCardinality(rel.Cardinality1),
                    this.MapOneToOneCardinality(rel.Cardinality2));

                this._currentSchema.AddAssociation(physicalAssoc);
            }
        }

		/// <summary>
		/// Add a 1:n association.
		/// </summary>
		/// <param name="rel">Logical 1:n relationship.</param>
		/// <param name="class1">Physical class 1.</param>
		/// <param name="class2">Physical class 2.</param>
        private void AddOneToManyAssociation(OneToManyLogicalRelationship rel,
            PhysicalObjectorientedClass class1, PhysicalObjectorientedClass class2)
        { 
            // Do we already have a valid answer?
            object savedAnswer = DataContainer.Instance.PhysicalData.GetInteractionDataItem(
                this._currentSchema, rel.Id);

            string title = String.Format("{0} {1}", global::dataMagus.Common.OtherResources.PhysicalObjectorientedAssociation, rel.Name);

            // What kind of association is it?
            bool hasPropertiesOnAssociation = (rel.CountAttributes > 0);
            bool validAnswer = false;
            OneToManyAnswers givenAnswer = OneToManyAnswers.WithoutLinkClassTransferProperties;
            if (null != savedAnswer)
            {
                // Is the answer still valid?
                if (!hasPropertiesOnAssociation)
                {
                    givenAnswer = OneToManyAnswers.WithoutLinkClassWithoutProperties;
                    validAnswer = true;
                }
                else
                {
                    if (OneToManyAnswers.WithLinkClass.Equals(savedAnswer) ||
                        OneToManyAnswers.WithoutLinkClassTransferProperties.Equals(savedAnswer))
                    {
                        givenAnswer = (OneToManyAnswers)savedAnswer;
                        validAnswer = true;
                    }
                }
            }
            else 
            {
                if (!hasPropertiesOnAssociation)
                {
                    givenAnswer = OneToManyAnswers.WithoutLinkClassWithoutProperties;
                    validAnswer = true;
                }
            }

            if (!validAnswer)
            { 
                // We need to ask questions about the options
                // Question : Do you want to have a link class?
                string question = global::dataMagus.Common.OtherResources.PhysicalObjectorientedQuestionBeginning;
                Dictionary<int, string> allowedAnswers = new Dictionary<int, string>();
                allowedAnswers[0] = String.Format(global::dataMagus.Common.OtherResources.PhysicalObjectorientedQuestionOneToManyTransferProperties, class2.Name);
                allowedAnswers[1] = String.Format(global::dataMagus.Common.OtherResources.PhysicalObjectorientedQuestionOneToManyLinkClass, class1.Name, class2.Name);
                
                int answer = this.UserInteraction.AskQuestion(title, question, String.Empty, allowedAnswers);
                if (1 == answer)
                {
                    givenAnswer = OneToManyAnswers.WithLinkClass;
                }
                else
                {
                    givenAnswer = OneToManyAnswers.WithoutLinkClassTransferProperties;
                }
            }

            // Save the answer
            DataContainer.Instance.PhysicalData.UpdateInteractionDataItem(
                this._currentSchema, rel.Id, givenAnswer);

            // We have a valid answer --> create physical elements for it
            if (OneToManyAnswers.WithoutLinkClassTransferProperties == givenAnswer ||
                OneToManyAnswers.WithoutLinkClassWithoutProperties == givenAnswer)
            {
                // Create a normal one-to-many association and transfer properties on the association to
                // the class on the right if necessary

                // Create an one-to-many association
                OneToManyPhysicalObjectorientedAssociation physicalAssoc = new OneToManyPhysicalObjectorientedAssociation(
                    this._currentSchema,
                    this.CreateRelationshipPhysicalId(rel), 
                    rel, 
                    Helper.ToTitleCase(rel.Name), class1, class2,
                    Helper.ToTitleCase(rel.Rolename1),
                    Helper.ToTitleCase(rel.Rolename2),
                    this.MapOneToManyCardinalityForOne(rel.Cardinality1),
                    this.MapOneToManyCardinalityForMany(rel.Cardinality2));

                this._currentSchema.AddAssociation(physicalAssoc);
                    
                // Transfer any attributes on the relationship to the class on the right
                IEnumerator relEnumerator = rel.GetAttributes();
                while (relEnumerator.MoveNext())
                {
                    LogicalAttribute attribute = (LogicalAttribute)relEnumerator.Current;
                    class2.AddProperty(this.CreateProperty(class2, attribute));
                }
            }
            else if (OneToManyAnswers.WithLinkClass == givenAnswer)
            {
                // Create a link class and two associations

                // Create a link class
                PhysicalObjectorientedClass linkClass = this.CreateLinkClass(rel);
                this._currentSchema.AddClass(linkClass);

                // Create an association from the class on the left to the link class
                PhysicalObjectorientedAssociation assocClass1ToLinkClass = this.CreateOneToManyLeftToLinkClass(
                    rel, class1, linkClass,
                    OneToManyPhysicalObjectorientedAssociation.CardinalityForOne.OnlyOne,
                    this.MapOneToManyCardinalityForMany(rel.Cardinality2));

                this._currentSchema.AddAssociation(assocClass1ToLinkClass);

                // Create an association from the link class to the class on the right side
                PhysicalObjectorientedAssociation assocLinkClassToClass2 = this.CreateOneToOneRightToLinkClass(
                    rel, class2, linkClass,
                    OneToOnePhysicalObjectorientedAssociation.Cardinality.OnlyOne,
                    this.MapOneToManyCardinalityForOneIntoOneToOneCardinality(rel.Cardinality1));

                this._currentSchema.AddAssociation(assocLinkClassToClass2);
            }
            else
            {
                throw new NotImplementedException("Found unhandled one-to-many relationship answer.");
            }
        }

		/// <summary>
		/// Create a physical objectoriented class.
		/// </summary>
		/// <param name="entity">Logical entity the class is based on.</param>
		/// <returns>New class.</returns>
        private PhysicalObjectorientedClass CreateClass(LogicalEntity entity)
        {
            PhysicalObjectorientedClass classElement = new PhysicalObjectorientedClass(this._currentSchema,
                entity.Id, entity, Helper.ToTitleCase(entity.Name));
        	if (classElement.Location == Point.Empty)
        	{
            	classElement.Location = new Point(entity.Location.X, entity.Location.Y);
        	}
        	
            IEnumerator attributeEnumerator = entity.GetAttributes();
            while (attributeEnumerator.MoveNext())
            {
                LogicalAttribute attribute = (LogicalAttribute)attributeEnumerator.Current;
                PhysicalObjectorientedProperty property = this.CreateProperty(classElement, attribute);
                classElement.AddProperty(property);
                if (entity.ContainsKeyAttribute(attribute))
                {
                    classElement.AddKeyProperty(property);
                }
            }
            return classElement;
        }

        /// <summary>
        /// Create a physical physical objectoriented property.
        /// </summary>
        /// <param name="parent">Parent class the property will be added to.</param>
        /// <param name="attribute">Logical attribute the property is based on.</param>
        /// <returns>New property.</returns>
        private PhysicalObjectorientedProperty CreateProperty(PhysicalObjectorientedClass parent, LogicalAttribute attribute)
        {
            return new PhysicalObjectorientedProperty(this._currentSchema, parent, 
                attribute.Id, attribute, Helper.ToTitleCase(attribute.Name));
        }

        /// <summary>
        /// Finds a class which is based on a logical entity.
        /// </summary>
        /// <param name="entity">Entity to search for.</param>
        /// <returns>The class on success or null if nothing has been found.</returns>
        private PhysicalObjectorientedClass FindClassForLogicalEntity(LogicalEntity entity)
        {
            IEnumerator classEnumerator = this._currentSchema.GetClasses();
            while (classEnumerator.MoveNext())
            {
                PhysicalObjectorientedClass classElement = (PhysicalObjectorientedClass)classEnumerator.Current;
                if (classElement.Id == entity.Id)
                {
                    return classElement;
                }
            }
            return null;
        }

        /// <summary>
		/// Create a physical objectoriented link class.
        /// </summary>
		/// <param name="relationship">Logical relationship the class is based on.</param>
        /// <returns></returns>
        private PhysicalObjectorientedClass CreateLinkClass(LogicalRelationship relationship)
        {
            PhysicalObjectorientedClass linkClass = new PhysicalObjectorientedClass(this._currentSchema,
                this.CreateLinkClassPhysicalId(relationship),
                relationship, Helper.ToTitleCase(relationship.Name));
        	if (linkClass.Location == Point.Empty)
        	{
        		linkClass.Location = new Point(relationship.Location.X, relationship.Location.Y);
        	}
        	
            if (relationship is IAttributable)
            {
                IEnumerator attributesEnumerator = ((IAttributable)relationship).GetAttributes();
                List<LogicalAttribute> keyAttributes = new List<LogicalAttribute>();
                if (relationship is ManyToManyLogicalRelationship)
                {
                    IEnumerator keyAttributesEnumerator = ((ManyToManyLogicalRelationship)relationship).GetKeyAttributes();
                    while (keyAttributesEnumerator.MoveNext())
                    {
                        LogicalAttribute attribute = (LogicalAttribute)keyAttributesEnumerator.Current;
                        keyAttributes.Add(attribute);
                    }
                }
                while (attributesEnumerator.MoveNext())
                {
                    LogicalAttribute attribute = (LogicalAttribute)attributesEnumerator.Current;
                    PhysicalObjectorientedProperty property = this.CreateProperty(linkClass, attribute);
                    linkClass.AddProperty(property);
                    // also add the key attributes
                    if (0 < keyAttributes.Count && keyAttributes.Contains(attribute))
                    {
                        linkClass.AddKeyProperty(property);
                    }
                }
            }
            return linkClass;
        }

        /// <summary>
        /// Create 1:n physical objectoriented association for the left side of the new link class.
        /// </summary>
        /// <param name="relationship">Logical relationship the physical is based on.</param>
        /// <param name="classLeft">Left class.</param>
        /// <param name="linkClass">Link class.</param>
        /// <param name="cardinality1">Cardinality on the left class.</param>
        /// <param name="cardinality2">Cardinality on the link class.</param>
        /// <returns></returns>
        private OneToManyPhysicalObjectorientedAssociation CreateOneToManyLeftToLinkClass(LogicalRelationship relationship,
            PhysicalObjectorientedClass classLeft, PhysicalObjectorientedClass linkClass,
            OneToManyPhysicalObjectorientedAssociation.CardinalityForOne cardinality1,
            OneToManyPhysicalObjectorientedAssociation.CardinalityForMany cardinality2)
        {
            string relClass1ToLinkTableName = String.Format("{0}{1}", Helper.ToTitleCase(relationship.Entity1.Name), Helper.ToTitleCase(linkClass.Name));

            OneToManyPhysicalObjectorientedAssociation relClass1ToLinkTable = new OneToManyPhysicalObjectorientedAssociation(
                this._currentSchema,
                this.CreateRelationshipPhysicalId(relationship.Entity1, linkClass),
                null,
                relClass1ToLinkTableName,
                classLeft,
                linkClass,
                String.Format("{0}{1}", Helper.ToTitleCase(relationship.Entity1.Name), relClass1ToLinkTableName),
                String.Format("{0}{1}", Helper.ToTitleCase(linkClass.Name), relClass1ToLinkTableName),
                cardinality1,
                cardinality2);

            return relClass1ToLinkTable;
        }

        /// <summary>
        /// Create 1:n physical objectoriented association for the right side of the new link class.
        /// </summary>
        /// <param name="relationship">Logical relationship the physical is based on.</param>
        /// <param name="classRight">Right class.</param>
        /// <param name="linkClass">Link class.</param>
        /// <param name="cardinality1">Cardinality on the right class.</param>
        /// <param name="cardinality2">Cardinality on the link class.</param>
        /// <returns></returns>
        private OneToManyPhysicalObjectorientedAssociation CreateOneToManyRightToLinkClass(LogicalRelationship relationship,
            PhysicalObjectorientedClass classRight, PhysicalObjectorientedClass linkClass,
            OneToManyPhysicalObjectorientedAssociation.CardinalityForOne cardinality1,
            OneToManyPhysicalObjectorientedAssociation.CardinalityForMany cardinality2)
        {
            string relClass2ToLinkClassName = String.Format("{0}{1}", Helper.ToTitleCase(linkClass.Name), Helper.ToTitleCase(relationship.Entity2.Name));

            OneToManyPhysicalObjectorientedAssociation relClass2ToLinkClass = new OneToManyPhysicalObjectorientedAssociation(
                this._currentSchema,
                this.CreateRelationshipPhysicalId(relationship.Entity2, linkClass, relationship),
                null,
                relClass2ToLinkClassName,
                classRight,
                linkClass,
                String.Format("{0}{1}", Helper.ToTitleCase(relClass2ToLinkClassName), relationship.Entity2.Name),
                String.Format("{0}{1}", Helper.ToTitleCase(linkClass.Name), relClass2ToLinkClassName),
                cardinality1,
                cardinality2);

            return relClass2ToLinkClass;
        }

        /// <summary>
        /// Create 1:1 physical objectoriented association for the left side of the new link class.
        /// </summary>
        /// <param name="relationship">Logical relationship the physical is based on.</param>
        /// <param name="classLeft">Left class.</param>
        /// <param name="linkClass">Link class.</param>
        /// <param name="cardinality1">Cardinality on the left class.</param>
        /// <param name="cardinality2">Cardinality on the link class.</param>
        /// <returns></returns>
        private OneToOnePhysicalObjectorientedAssociation CreateOneToOneLeftToLinkClass(LogicalRelationship relationship,
            PhysicalObjectorientedClass classLeft, PhysicalObjectorientedClass linkClass,
            OneToOnePhysicalObjectorientedAssociation.Cardinality cardinality1,
            OneToOnePhysicalObjectorientedAssociation.Cardinality cardinality2)
        {
            string relClass1ToLinkClassName = String.Format("{0}{1}", Helper.ToTitleCase(relationship.Entity1.Name), Helper.ToTitleCase(linkClass.Name));
            OneToOnePhysicalObjectorientedAssociation relClass1ToLinkClass = new OneToOnePhysicalObjectorientedAssociation(
                this._currentSchema,
                this.CreateRelationshipPhysicalId(relationship.Entity1, linkClass, relationship),
                null,
                relClass1ToLinkClassName,
                classLeft,
                linkClass,
                String.Format("{0}{1}", Helper.ToTitleCase(relationship.Entity1.Name), relClass1ToLinkClassName),
                String.Format("{0}{1}", Helper.ToTitleCase(linkClass.Name), relClass1ToLinkClassName),
                cardinality1,
                cardinality2);

            return relClass1ToLinkClass;
        }

        /// <summary>
        /// Create 1:1 physical objectoriented association for the right side of the new link class.
        /// </summary>
        /// <param name="relationship">Logical relationship the physical is based on.</param>
        /// <param name="classRight">Right class.</param>
        /// <param name="linkClass">Link class.</param>
        /// <param name="cardinality1">Cardinality on the right class.</param>
        /// <param name="cardinality2">Cardinality on the link class.</param>
        /// <returns></returns>
        private OneToOnePhysicalObjectorientedAssociation CreateOneToOneRightToLinkClass(LogicalRelationship relationship,
            PhysicalObjectorientedClass classRight, PhysicalObjectorientedClass linkClass,
            OneToOnePhysicalObjectorientedAssociation.Cardinality cardinality1,
            OneToOnePhysicalObjectorientedAssociation.Cardinality cardinality2)
        {
            string relLinkClassToClass2Name = String.Format("{0}{1}", Helper.ToTitleCase(linkClass.Name), Helper.ToTitleCase(relationship.Entity2.Name));

            OneToOnePhysicalObjectorientedAssociation relLinkClassToClass2 = new OneToOnePhysicalObjectorientedAssociation(
                this._currentSchema,
                this.CreateRelationshipPhysicalId(linkClass, relationship.Entity2, relationship),
                null,
                relLinkClassToClass2Name,
                classRight,
                linkClass,
                String.Format("{0}{1}", Helper.ToTitleCase(relationship.Entity2.Name), relLinkClassToClass2Name),
                String.Format("{0}{1}", Helper.ToTitleCase(linkClass.Name), relLinkClassToClass2Name),
                cardinality1,
                cardinality2);

            return relLinkClassToClass2;
        }

        /// <summary>
        /// Map the logical 1:N cardinaltiy to the physical cardinality for one.
        /// </summary>
        /// <param name="logicalCardinality">Logical cardinality.</param>
        /// <returns>New physical cardinality.</returns>
        private OneToManyPhysicalObjectorientedAssociation.CardinalityForOne MapOneToManyCardinalityForOne(
            OneToManyLogicalRelationship.CardinalityForOne logicalCardinality)
        {
            OneToManyPhysicalObjectorientedAssociation.CardinalityForOne mappedCardinality = OneToManyPhysicalObjectorientedAssociation.CardinalityForOne.ZeroOrOne;
            if (logicalCardinality.Equals(OneToManyLogicalRelationship.CardinalityForOne.OnlyOne))
            {
                mappedCardinality = OneToManyPhysicalObjectorientedAssociation.CardinalityForOne.OnlyOne;
            }
            return mappedCardinality;
        }

        /// <summary>
        /// Map the logical 1:N cardinaltiy to the physical cardinality for many.
        /// </summary>
        /// <param name="logicalCardinality">Logical cardinality.</param>
        /// <returns>New physical cardinality.</returns>
        private OneToManyPhysicalObjectorientedAssociation.CardinalityForMany MapOneToManyCardinalityForMany(
            OneToManyLogicalRelationship.CardinalityForMany logicalCardinality)
        {
            OneToManyPhysicalObjectorientedAssociation.CardinalityForMany mappedCardinality = OneToManyPhysicalObjectorientedAssociation.CardinalityForMany.ZeroOneOrMore;
            if (logicalCardinality.Equals(OneToManyLogicalRelationship.CardinalityForMany.OneOrMore) ||
                logicalCardinality.Equals(OneToManyLogicalRelationship.CardinalityForMany.Exactly))
            {
                mappedCardinality = OneToManyPhysicalObjectorientedAssociation.CardinalityForMany.OneOrMore;
            }
            return mappedCardinality;
        }

        /// <summary>
        /// Map the logical n:m cardinaltiy to the physical cardinality.
        /// </summary>
        /// <param name="logicalCardinality">Logical cardinality.</param>
        /// <returns>New physical cardinality.</returns>
        private ManyToManyPhysicalObjectorientedAssociation.Cardinality MapManyToManyCardinality(
            ManyToManyLogicalRelationship.Cardinality logicalCardinality)
        {
            ManyToManyPhysicalObjectorientedAssociation.Cardinality mappedCardinality = ManyToManyPhysicalObjectorientedAssociation.Cardinality.ZeroOneOrMore;
            if (logicalCardinality.Equals(ManyToManyLogicalRelationship.Cardinality.OneOrMore) ||
                logicalCardinality.Equals(ManyToManyLogicalRelationship.Cardinality.Exactly))
            {
                mappedCardinality = ManyToManyPhysicalObjectorientedAssociation.Cardinality.OneOrMore;
            }
            return mappedCardinality;
        }

        /// <summary>
        /// Map the logical 1:n cardinaltiy to the physical cardinality for many.
        /// </summary>
        /// <param name="logicalCardinality">Logical cardinality.</param>
        /// <returns>New physical cardinality.</returns>
        private OneToManyPhysicalObjectorientedAssociation.CardinalityForMany MapManyToManyCardinalityIntoOneToManyCardinalityForMany(
            ManyToManyLogicalRelationship.Cardinality logicalCardinality)
        { 
            OneToManyPhysicalObjectorientedAssociation.CardinalityForMany mappedCardinality = OneToManyPhysicalObjectorientedAssociation.CardinalityForMany.ZeroOneOrMore;
            if (logicalCardinality.Equals(ManyToManyLogicalRelationship.Cardinality.OneOrMore) ||
                logicalCardinality.Equals(ManyToManyLogicalRelationship.Cardinality.Exactly))
            {
                mappedCardinality = OneToManyPhysicalObjectorientedAssociation.CardinalityForMany.OneOrMore;
            }
            return mappedCardinality;
        }

        /// <summary>
        /// Map the logical 1:1 cardinaltiy to the physical cardinality.
        /// </summary>
        /// <param name="logicalCardinality">Logical cardinality.</param>
        /// <returns>New physical cardinality.</returns>
        private OneToOnePhysicalObjectorientedAssociation.Cardinality MapOneToManyCardinalityForOneIntoOneToOneCardinality(
            OneToManyLogicalRelationship.CardinalityForOne logicalCardinality)
        {
            OneToOnePhysicalObjectorientedAssociation.Cardinality mappedCardinality = OneToOnePhysicalObjectorientedAssociation.Cardinality.ZeroOrOne;
            if (logicalCardinality.Equals(OneToManyLogicalRelationship.CardinalityForOne.OnlyOne))
            {
                mappedCardinality = OneToOnePhysicalObjectorientedAssociation.Cardinality.OnlyOne;
            }
            return mappedCardinality;
        }

        /// <summary>
        /// Map the logical 1:1 cardinaltiy to the physical cardinality.
        /// </summary>
        /// <param name="logicalCardinality">Logical cardinality.</param>
        /// <returns>New physical cardinality.</returns>
        private OneToOnePhysicalObjectorientedAssociation.Cardinality MapOneToOneCardinality(
            OneToOneLogicalRelationship.Cardinality logicalCardinality)
        {
            OneToOnePhysicalObjectorientedAssociation.Cardinality mappedCardinality = OneToOnePhysicalObjectorientedAssociation.Cardinality.ZeroOrOne;
            if (logicalCardinality.Equals(OneToOnePhysicalObjectorientedAssociation.Cardinality.OnlyOne))
            {
                mappedCardinality = OneToOnePhysicalObjectorientedAssociation.Cardinality.OnlyOne;
            }
            return mappedCardinality;
        }
        #endregion
    }
}
