#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.Collections;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Windows.Forms;
using dataMagus.Common;
using dataMagus.Common.PlugIn;
using dataMagus.Common.PlugIn.Notation;
using dataMagus.Common.Physical.Objectoriented;
#endregion

namespace dataMagus.PlugIns.Notations.Odlg
{
	/// <summary>
	/// This class is used to draw a ODLG association.
	/// </summary>
	public class AssociationDrawer
	{
        #region Constants
        /// <summary>
        /// Defines the default distance for self relationships.
        /// </summary>
        private const int DefaultSelfDistance = 15;
        #endregion

        #region Members
        /// <summary>
        /// Physical association the control has been created from.
        /// </summary>
        private PhysicalObjectorientedAssociation _association = null;

        /// <summary>
        /// Control of the first element between the association.
        /// </summary>
        private ElementControl _element1 = null;
		
		/// <summary>
		/// Control of the second element between the association.
		/// </summary>
        private ElementControl _element2 = null;
		
		/// <summary>
		/// Indicator if the association names should show up.
		/// </summary>
        private bool _showAssociationName = true;
		
		/// <summary>
		/// Indicator if the rolenames should show up.
		/// </summary>
        private bool _showRolenames = true;
		
		/// <summary>
		/// Indicator if the cardinalities should show up.
		/// </summary>
        private bool _showCardinalities = true;

        /// <summary>
        /// Height of the arrow of a is-a relationship.
        /// </summary>
		private int _isaArrowHeight = 20;
		
		/// <summary>
		/// Margin of the association lines around the classs.
		/// </summary>
		private int _marginClass = 8;
		
		/// <summary>
		/// Gap between the associations when having more than one from two equal elements
		/// to each other.
		/// </summary>
		private int _multiAssociationGap = 20;
        
        /// <summary>
        /// Thickness of the line.
        /// </summary>
		private float _lineThikness = 1.6F;
        
        /// <summary>
        /// Thickness of the is-a line.
        /// </summary>
		private float _isaLineThikness = 6F;
        
        /// <summary>
        /// Color of the line.
        /// </summary>
		private Color _lineColor = Color.DarkGray;
        
        /// <summary>
        /// Color of the is-a line.
        /// </summary>
		private Color _isaLineColor = Color.Black;
        
        /// <summary>
        /// Color of the cardinality signs.
        /// </summary>
		private Color _cardinalityColor = Color.Black;
        
        /// <summary>
        /// Color of the association text.
        /// </summary>
		private Color _associationTextColor = Color.Black;
        
        /// <summary>
        /// Color of the rolename text.
        /// </summary>
		private Color _rolenameTextColor = Color.Black;
        
        /// <summary>
        /// Font of the association text.
        /// </summary>
		private Font _associationTextFont = new Font(FontFamily.GenericSansSerif, 8, FontStyle.Regular);
        
        /// <summary>
        /// Font of the rolename text.
        /// </summary>
		private Font _rolenameFont = new Font(FontFamily.GenericSansSerif, 8, FontStyle.Regular);
        
        /// <summary>
        /// Holds the current distance for self associations.
        /// </summary>
        private static Dictionary<string, int> _currentSelfDistance = new Dictionary<string, int>();
        
		/// <summary>
		/// Conter holds the multiplier for more than one association between equal classes.
		/// </summary>
        private static Dictionary<long, int> _betweenAssociationCounter = new Dictionary<long, int>();
        #endregion

        #region Constructors / Destructors
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="association">Physical association to draw.</param>
        /// <param name="element1">Control of the first element between the association.</param>
        /// <param name="element2">Control of the second element between the association.</param>
        public AssociationDrawer(PhysicalObjectorientedAssociation association, ElementControl element1, ElementControl element2)
        {
			this._association = association;
			this._element1 = element1;
			this._element2 = element2;
        }
        #endregion

        #region Properties
		/// <summary>
		/// Indicator if the association names should show up.
		/// </summary>
		public bool ShowAssociationName 
		{
			get { return _showAssociationName; }
			set { _showAssociationName = value; }
		}
		
		/// <summary>
		/// Indicator if the rolenames should show up.
		/// </summary>
		public bool ShowRolenames 
		{
			get { return _showRolenames; }
			set { _showRolenames = value; }
		}
		
		/// <summary>
		/// Indicator if the cardinalities should show up.
		/// </summary>
		public bool ShowCardinalities 
		{
			get { return _showCardinalities; }
			set { _showCardinalities = value; }
		}
        
        /// <summary>
        /// Thickness of the line.
        /// </summary>
		public float LineThikness 
		{
			get { return _lineThikness; }
			set { _lineThikness = value; }
		}
        
        /// <summary>
        /// Thickness of the is-a line.
        /// </summary>
		public float IsaLineThikness 
		{
			get { return _isaLineThikness; }
			set { _isaLineThikness = value; }
		}
        
        /// <summary>
        /// Color of the line.
        /// </summary>
		public Color LineColor 
		{
			get { return _lineColor; }
			set { _lineColor = value; }
		}
        
        /// <summary>
        /// Color of the is-a line.
        /// </summary>
		public Color IsaLineColor 
		{
			get { return _isaLineColor; }
			set { _isaLineColor = value; }
		}
        
        /// <summary>
        /// Color of the cardinality signs.
        /// </summary>
		public Color CardinalityColor 
		{
			get { return _cardinalityColor; }
			set { _cardinalityColor = value; }
		}
		
        /// <summary>
        /// Color of the association text.
        /// </summary>
		public Color AssociationTextColor 
		{
			get { return _associationTextColor; }
			set { _associationTextColor = value; }
		}		

        /// <summary>
        /// Color of the rolename text.
        /// </summary>
		public Color RolenameTextColor
		{
			get { return _rolenameTextColor; }
			set { _rolenameTextColor = value; }
		}
		
        /// <summary>
        /// Font of the association text.
        /// </summary>
		public Font AssociationTextFont 
		{
			get { return _associationTextFont; }
			set { _associationTextFont = value; }
		}
		
        /// <summary>
        /// Font of the rolename text.
        /// </summary>
		public Font RolenameFont 
		{
			get { return _rolenameFont; }
			set { _rolenameFont = value; }
		}
        #endregion

        #region Methodes
        /// <summary>
        /// Tells that the drawing of the associations will begin.
        /// </summary>
        public static void BeginDrawingAssociations()
        {
        	AssociationDrawer._betweenAssociationCounter.Clear();
        	AssociationDrawer._currentSelfDistance.Clear();
        }
        
        /// <summary>
        /// Draws the association.
        /// </summary>
        /// <param name="context">Graphics context.</param>
        public void DrawAssociation(Graphics context)
        {
            if (this._element1 == this._element2)
            {
            	this.DrawSelfAssociation(context);
            }
            else
            {
            	if (this._association is IsAPhysicalObjectorientedAssociation)
            	{
            		this.DrawIsaAssociation(context);
            	}
            	else
            	{
            		this.DrawDoubleAssociation(context);
            	}
            }
        }

        /// <summary>
        /// Draws the is-a association.
        /// </summary>
        /// <param name="context">Graphics context.</param>
        public void DrawIsaAssociation(Graphics context)
        {
        	Pen isaLinePen = new Pen(this.IsaLineColor, this.IsaLineThikness);
        	Brush isaBrush = new SolidBrush(this.IsaLineColor);
        	
        	Point symbolA = Point.Empty;
            Point symbolB = Point.Empty;
            Point straightA = Point.Empty;
            Point straightB = Point.Empty;
            Point middle = Point.Empty;
            NotationHelper.CalcPoints(this._element1, this._element2, this._isaArrowHeight,
				out symbolA, out symbolB, out straightA, out straightB, out middle);
            
            // draw lines
			context.DrawLine(isaLinePen, symbolA, straightB);

			NotationHelper.SideLocation siteLoc = NotationHelper.EvalPointsLocation(symbolA, straightA);
        	if (NotationHelper.SideLocation.Top == siteLoc
        	   || NotationHelper.SideLocation.Bottom == siteLoc)
        	{
        		// draw arrow
        		int arrowWidth = Math.Abs(symbolA.Y - straightA.Y);
        		Point[] arrow = NotationHelper.CreateTrianglePolygon(straightA, symbolA, arrowWidth);
	            context.FillPolygon(isaBrush, arrow);
        	}
        	else if (NotationHelper.SideLocation.Left == siteLoc
        	        || NotationHelper.SideLocation.Right == siteLoc)
        	{
        		// draw arrow
        		int arrowWidth = Math.Abs(symbolA.X - straightA.X);
        		Point[] arrow = NotationHelper.CreateTrianglePolygon(straightA, symbolA, arrowWidth);
	            context.FillPolygon(isaBrush, arrow);
        	}
        }
        
        /// <summary>
        /// Draws the association between two different classs.
        /// </summary>
        /// <param name="context">Graphics context.</param>
        public void DrawDoubleAssociation(Graphics context)
        {
        	Pen linePen = new Pen(this.LineColor, this.LineThikness);
        	Brush textBrush = new SolidBrush(this.AssociationTextColor);
        	
        	string labelNameText = this._association.Name;
        	SizeF labelSize = context.MeasureString(labelNameText, this.RolenameFont);

        	// evaluate multiplier to calculate divergency for multiple relationships
            int multiplier = NotationHelper.CalcCorrectionMultiplier(this._association.Class1, this._association.Class1.CountAssociationLinksBetween,
				this._association.Class2, AssociationDrawer._betweenAssociationCounter);
            int divergency = multiplier * this._multiAssociationGap;
        	
        	Point symbolA = Point.Empty;
            Point symbolB = Point.Empty;
            Point straightA = Point.Empty;
            Point straightB = Point.Empty;
            Point middle = Point.Empty;
            NotationHelper.CalcPoints(this._element1, this._element2, this._marginClass, divergency,
				out symbolA, out symbolB, out straightA, out straightB, out middle);
            
            // draw lines
            context.DrawLine(linePen, symbolA, straightA);
            context.DrawLine(linePen, symbolB, straightB);
			context.DrawLine(linePen, symbolA, symbolB);
            
            // draw cardinality
            this.DrawCardinality(symbolA, symbolB, straightA, straightB, context);
			
            // draw label
            if (this.ShowAssociationName)
            {
    	        Point textLocation = new Point(middle.X - (int)labelSize.Width / 2, middle.Y);
	            context.DrawString(this._association.Name, this.AssociationTextFont, textBrush, textLocation);
            }
			
            // draw rolenames
            this.DrawDoubleRolenames(symbolA, symbolB, straightA, straightB, context);
        }
        
        /// <summary>
        /// Draws the rolenames between two different classs.
        /// </summary>
        /// <param name="symbolA">Symbol point of the first class.</param>
        /// <param name="symbolB">Symbol point of the second class.</param>
        /// <param name="straightA">Straight point of the first class.</param>
        /// <param name="straightB">Straight point of the first class.</param>
        /// <param name="context">Graphics context.</param>
        public void DrawDoubleRolenames(Point symbolA, Point symbolB, 
			Point straightA, Point straightB, Graphics context)
        {
        	if (!this.ShowRolenames)
        	{
        		return;
        	}

        	Brush textBrush = new SolidBrush(this.RolenameTextColor);
        	
        	NotationHelper.SideLocation locationA = NotationHelper.EvalPointsLocation(symbolA, straightA);
        	SizeF sizeRoleA = context.MeasureString(this._association.Rolename1, this.RolenameFont);
        	if (NotationHelper.SideLocation.Top == locationA)
        	{
	        	int diff = Math.Abs(symbolA.Y - straightA.Y);
        		symbolA.X -= (int)sizeRoleA.Width/2;
        		symbolA.Y -= ((int)sizeRoleA.Height + diff);
	            context.DrawString(this._association.Rolename1, this.RolenameFont, textBrush, symbolA);
        	}
        	else if (NotationHelper.SideLocation.Bottom == locationA)
        	{
	        	int diff = Math.Abs(symbolA.Y - straightA.Y);
        		symbolA.X -= (int)sizeRoleA.Width/2;
        		symbolA.Y += diff;
	            context.DrawString(this._association.Rolename1, this.RolenameFont, textBrush, symbolA);
        	}
        	else if (NotationHelper.SideLocation.Left == locationA)
        	{
	        	int diff = Math.Abs(symbolA.X - straightA.X);
	        	symbolA.X -= ((int)sizeRoleA.Width + diff);
	            context.DrawString(this._association.Rolename1, this.RolenameFont, textBrush, symbolA);
        	}
        	else if (NotationHelper.SideLocation.Right == locationA)
        	{
	        	int diff = Math.Abs(symbolA.X - straightA.X);
        		symbolA.X += diff;
	            context.DrawString(this._association.Rolename1, this.RolenameFont, textBrush, symbolA);
        	}
        	
        	NotationHelper.SideLocation locationB = NotationHelper.EvalPointsLocation(symbolB, straightB);
        	SizeF sizeRoleB = context.MeasureString(this._association.Rolename2, this.RolenameFont);
        	if (NotationHelper.SideLocation.Top == locationB)
        	{
	        	int diff = Math.Abs(symbolB.Y - straightB.Y);
        		symbolB.X -= (int)sizeRoleB.Width/2;
        		symbolB.Y -= ((int)sizeRoleB.Height + diff);
	            context.DrawString(this._association.Rolename2, this.RolenameFont, textBrush, symbolB);
        	}
        	else if (NotationHelper.SideLocation.Bottom == locationB)
        	{
	        	int diff = Math.Abs(symbolB.Y - straightB.Y);
        		symbolB.X -= (int)sizeRoleB.Width/2;
        		symbolB.Y += diff;
	            context.DrawString(this._association.Rolename2, this.RolenameFont, textBrush, symbolB);
        	}
        	else if (NotationHelper.SideLocation.Left == locationB)
        	{
	        	int diff = Math.Abs(symbolB.X - straightB.X);
	        	symbolB.X -= ((int)sizeRoleB.Width + diff);
	            context.DrawString(this._association.Rolename2, this.RolenameFont, textBrush, symbolB);
        	}
        	else if(NotationHelper.SideLocation.Right == locationB)
        	{
	        	int diff = Math.Abs(symbolB.X - straightB.X);
        		symbolB.X += diff;
	            context.DrawString(this._association.Rolename2, this.RolenameFont, textBrush, symbolB);
        	}
        }
        
        /// <summary>
        /// Draws the rolenames for a self association.
        /// </summary>
        /// <param name="selfSymbolA">Self symbol point A.</param>
        /// <param name="symbolB">Symbol point B.</param>
        /// <param name="straightB">Straight point B.</param>
        /// <param name="context">Graphics context.</param>
        public void DrawSelfRolenames(Point selfSymbolA, Point symbolB, Point straightB, Graphics context)
        {
        	if (!this.ShowRolenames)
        	{
        		return;
        	}
        	
        	Brush textBrush = new SolidBrush(this.RolenameTextColor);
        	
        	SizeF sizeRoleA = context.MeasureString(this._association.Rolename1, this.RolenameFont);
    		selfSymbolA.Y -= (int)sizeRoleA.Height;
            context.DrawString(this._association.Rolename1, this.RolenameFont, textBrush, selfSymbolA);
        	
            int diff = Math.Abs(symbolB.X - straightB.X);
            symbolB.X += diff;
        	SizeF sizeRoleB = context.MeasureString(this._association.Rolename2, this.RolenameFont);
            context.DrawString(this._association.Rolename2, this.RolenameFont, textBrush, symbolB);
        }
        
        /// <summary>
        /// Draws the self association.
        /// </summary>
        /// <param name="context">Graphics context.</param>
        public void DrawSelfAssociation(Graphics context)
        {
        	Pen linePen = new Pen(this.LineColor, this.LineThikness);

        	string labelNameText = this._association.Name;
        	SizeF labelSize = context.MeasureString(labelNameText, this.RolenameFont);

            // calculate the current selfgap, so the next selfrelation knows where to position
            int distance = AssociationDrawer.DefaultSelfDistance;
            if (AssociationDrawer._currentSelfDistance.ContainsKey(this._element1.Element.Id))
            {
            	distance = AssociationDrawer._currentSelfDistance[this._element1.Element.Id];
            }
            AssociationDrawer._currentSelfDistance[this._element1.Element.Id] = distance + AssociationDrawer.DefaultSelfDistance;
        	
        	// evaluate gap for self-relationship
            int selfGap = distance * 2;
        	Point symbolA = Point.Empty;
            Point symbolB = Point.Empty;
            Point straightA = Point.Empty;
            Point straightB = Point.Empty;
        	Point selfSymbolA = Point.Empty;
            Point selfSymbolB = Point.Empty;
            Point edge = Point.Empty;
            NotationHelper.CalcSelfPoints(this._element1, this._marginClass, selfGap, distance,
				out symbolA, out symbolB, out straightA, out straightB, out selfSymbolA, out selfSymbolB, out edge);
            
            // draw lines
            context.DrawLine(linePen, straightA, symbolA);
            context.DrawLine(linePen, symbolA, selfSymbolA);
            context.DrawLine(linePen, straightB, symbolB);
            context.DrawLine(linePen, symbolB, selfSymbolB);
            context.DrawLine(linePen, selfSymbolA, edge);
            context.DrawLine(linePen, selfSymbolB, edge);
            
            // draw cardinality
            this.DrawCardinality(symbolA, symbolB, straightA, straightB, context);
            
            // draw rolenames
            this.DrawSelfRolenames(selfSymbolA, symbolB, straightB, context);
        }
        
        /// <summary>
        /// Draw the cardinalities on both sides.
        /// </summary>
        /// <param name="symbolA">Symbol point of the first class.</param>
        /// <param name="symbolB">Symbol point of the second class.</param>
        /// <param name="straightA">Straight point of the first class.</param>
        /// <param name="straightB">Straight point of the first class.</param>
        /// <param name="context">Graphics context.</param>
        public void DrawCardinality(Point symbolA, Point symbolB, 
			Point straightA, Point straightB, Graphics context)
        {
        	if (!this.ShowCardinalities)
        	{
        		return;
        	}
        	
            // draw cardinality
            if (this._association is OneToOnePhysicalObjectorientedAssociation)
            {
        		this.DrawCardinalityOne(symbolA, straightA, context);
        		this.DrawCardinalityOne(symbolB, straightB, context);
            }
            else if (this._association is OneToManyPhysicalObjectorientedAssociation)
            {
        		this.DrawCardinalityOne(symbolA, straightA, context);
        		this.DrawCardinalityMore(symbolB, straightB, context);
            }
            else if (this._association is ManyToManyPhysicalObjectorientedAssociation)
            {
        		this.DrawCardinalityMore(symbolA, straightA, context);
        		this.DrawCardinalityMore(symbolB, straightB, context);
            }
        }
        
        /// <summary>
        /// Draw the 1 cardinality on one side.
        /// </summary>
        /// <param name="symbol">Symbol point of the first class.</param>
        /// <param name="straight">Straight point of the first class.</param>
        /// <param name="context">Graphics context.</param>
        private void DrawCardinalityOne(Point symbol, Point straight, Graphics context)
        {
        	Pen cardinalityPen = new Pen(this.CardinalityColor);
        	Brush cardinalityBrush = new SolidBrush(this.CardinalityColor);
        	
        	context.DrawLine(cardinalityPen, symbol, straight);
        	NotationHelper.SideLocation siteLoc = NotationHelper.EvalPointsLocation(symbol, straight);
        	if (NotationHelper.SideLocation.Top == siteLoc
        	   || NotationHelper.SideLocation.Bottom == siteLoc)
        	{
        		// draw arrow
        		int arrowWidth = Math.Abs(symbol.Y - straight.Y);
        		Point[] arrow = NotationHelper.CreateTrianglePolygon(straight, symbol, arrowWidth);
	            context.FillPolygon(cardinalityBrush, arrow);
	            context.DrawPolygon(cardinalityPen, arrow);
        	}
        	else if (NotationHelper.SideLocation.Left == siteLoc
        	        || NotationHelper.SideLocation.Right == siteLoc)
        	{
        		// draw arrow
        		int arrowWidth = Math.Abs(symbol.X - straight.X);
        		Point[] arrow = NotationHelper.CreateTrianglePolygon(straight, symbol, arrowWidth);
	            context.FillPolygon(cardinalityBrush, arrow);
	            context.DrawPolygon(cardinalityPen, arrow);
        	}
        }
        
        /// <summary>
        /// Draw the more cardinality on one side.
        /// </summary>
        /// <param name="symbol">Symbol point of the first class.</param>
        /// <param name="straight">Straight point of the first class.</param>
        /// <param name="context">Graphics context.</param>
        private void DrawCardinalityMore(Point symbol, Point straight, Graphics context)
        {
        	Pen cardinalityPen = new Pen(this.CardinalityColor);
        	Brush cardinalityBrush = new SolidBrush(this.CardinalityColor);

        	context.DrawLine(cardinalityPen, symbol, straight);
        	NotationHelper.SideLocation siteLoc = NotationHelper.EvalPointsLocation(symbol, straight);
        	if (NotationHelper.SideLocation.Top == siteLoc
        	   || NotationHelper.SideLocation.Bottom == siteLoc)
        	{
        		// draw arrow
        		int arrowWidth = Math.Abs(symbol.Y - straight.Y);
        		Point[] arrow = NotationHelper.CreateTrianglePolygon(straight, symbol, arrowWidth);
	            context.FillPolygon(cardinalityBrush, arrow);
	            context.DrawPolygon(cardinalityPen, arrow);

				// second arrow
				int diff = Math.Abs(symbol.Y - straight.Y);
				Point newStraight = symbol;
				Point newSymbol = symbol;
				newSymbol.Y += NotationHelper.SideLocation.Top == siteLoc ? -diff : diff;
				arrow = NotationHelper.CreateTrianglePolygon(newStraight, newSymbol, arrowWidth);
	            context.FillPolygon(cardinalityBrush, arrow);
	            context.DrawPolygon(cardinalityPen, arrow);
        	}
        	else if (NotationHelper.SideLocation.Left == siteLoc
        	        || NotationHelper.SideLocation.Right == siteLoc)
        	{
        		// draw arrow
        		int arrowWidth = Math.Abs(symbol.X - straight.X);
        		Point[] arrow = NotationHelper.CreateTrianglePolygon(straight, symbol, arrowWidth);
	            context.FillPolygon(cardinalityBrush, arrow);
	            context.DrawPolygon(cardinalityPen, arrow);

				// second arrow
				int diff = Math.Abs(symbol.X - straight.X);
				Point newStraight = symbol;
				Point newSymbol = symbol;
				newSymbol.X += NotationHelper.SideLocation.Left == siteLoc ? -diff : diff;
				arrow = NotationHelper.CreateTrianglePolygon(newStraight, newSymbol, arrowWidth);
	            context.FillPolygon(cardinalityBrush, arrow);
	            context.DrawPolygon(cardinalityPen, arrow);
        	}
        }
        #endregion
	}
}
