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

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

        #region Members
        /// <summary>
        /// Physical relationship the control has been created from.
        /// </summary>
        private PhysicalRelationalRelationship _relationship = null;
        
        /// <summary>
        /// Control of the first element between the relationship.
        /// </summary>
		private ElementControl _element1 = null;
		
		/// <summary>
		/// Control of the second element between the relationship.
		/// </summary>
		private ElementControl _element2 = null;
		
		/// <summary>
		/// Indicator if the relationship names should show up.
		/// </summary>
		private bool _showRelationshipName = true;
		
		/// <summary>
		/// Indicator if the cardinalities should show up.
		/// </summary>
		private bool _showCardinalities = true;
		
		/// <summary>
		/// Margin of the relationship lines around the tables.
		/// </summary>
		private int _marginTable = 8;
		
		/// <summary>
		/// With of the cardinality signs.
		/// </summary>
		private int _cardinalityWidth = 10;
        
		/// <summary>
		/// Gap between the relationships when having more than one from two equal elements
		/// to each other.
		/// </summary>
		private int _multiRelationGap = 20;
        
        /// <summary>
        /// Thickness of the line.
        /// </summary>
		private float _lineThikness = 1.6F;
        
        /// <summary>
        /// Color of the line.
        /// </summary>
		private Color _lineColor = Color.DarkGray;
        
        /// <summary>
        /// Color of the cardinality signs.
        /// </summary>
		private Color _cardinalityColor = Color.Black;
        
        /// <summary>
        /// Color of the relation text.
        /// </summary>
		private Color _relationTextColor = Color.Black;
        
        /// <summary>
        /// Font of the relation text.
        /// </summary>
		private Font _relationTextFont = new Font(FontFamily.GenericSansSerif, 8, FontStyle.Regular);
       
        /// <summary>
        /// Holds the current distance for self relationships.
        /// </summary>
        private static Dictionary<string, int> _currentSelfDistance = new Dictionary<string, int>();
        
		/// <summary>
		/// Conter holds the multiplier for more than one relationship between equal tables.
		/// </summary>
        private static Dictionary<long, int> _betweenRelationCounter = new Dictionary<long, int>();
        #endregion

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

        #region Properties
		/// <summary>
		/// Indicator if the relationship names should show up.
		/// </summary>
		public bool ShowRelationshipName 
		{
			get { return _showRelationshipName; }
			set { _showRelationshipName = 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>
        /// Color of the line.
        /// </summary>
		public Color LineColor 
		{
			get { return _lineColor; }
			set { _lineColor = value; }
		}
        
        /// <summary>
        /// Color of the cardinality signs.
        /// </summary>
		public Color CardinalityColor 
		{
			get { return _cardinalityColor; }
			set { _cardinalityColor = value; }
		}
		
        /// <summary>
        /// Color of the relation text.
        /// </summary>
		public Color RelationTextColor 
		{
			get { return _relationTextColor; }
			set { _relationTextColor = value; }
		}
		
        /// <summary>
        /// Font of the relation text.
        /// </summary>
		public Font RelationTextFont 
		{
			get { return _relationTextFont; }
			set { _relationTextFont = value; }
		}
		
		/// <summary>
		/// With of the cardinality signs.
		/// </summary>
		public int CardinalityWidth 
		{
			get { return _cardinalityWidth; }
			set { _cardinalityWidth = value; }
		}
		#endregion

        #region Methodes
        /// <summary>
        /// Tells that the drawing of the relationships will begin.
        /// </summary>
        public static void BeginDrawingRelationships()
        {
        	RelationshipDrawer._betweenRelationCounter.Clear();
        	RelationshipDrawer._currentSelfDistance.Clear();
        }

        /// <summary>
        /// Draws the relationship.
        /// </summary>
        /// <param name="context">Graphics context.</param>
        public void DrawRelationship(Graphics context)
        {
            if (this._element1 == this._element2)
            {
            	this.DrawSelfRelationship(context);
            }
            else
            {
            	this.DrawDoubleRelationship(context);
            }
        }
        
        /// <summary>
        /// Draws the relationship between two different tables.
        /// </summary>
        /// <param name="context">Graphics context.</param>
        public void DrawDoubleRelationship(Graphics context)
        {
        	Pen linePen = new Pen(this.LineColor, this.LineThikness);
        	Brush textBrush = new SolidBrush(this.RelationTextColor);
        	
        	string labelNameText = this._relationship.Name;
        	SizeF labelSize = context.MeasureString(labelNameText, this.RelationTextFont);
        	
            // evaluate multiplier to calculate divergency for multiple relationships
            int multiplier = NotationHelper.CalcCorrectionMultiplier(this._relationship.Table1, this._relationship.Table1.CountRelationshipLinksBetween,
				this._relationship.Table2, RelationshipDrawer._betweenRelationCounter);
            int divergency = multiplier * this._multiRelationGap;
        	
        	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._marginTable, 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.ShowRelationshipName)
            {
	            Point textLocation = new Point(middle.X - (int)labelSize.Width / 2, middle.Y);
	            context.DrawString(this._relationship.Name, this.RelationTextFont, textBrush, textLocation);
            }
        }
        
        /// <summary>
        /// Draws the self relationship.
        /// </summary>
        /// <param name="context">Graphics context.</param>
        public void DrawSelfRelationship(Graphics context)
        {
        	Pen linePen = new Pen(this.LineColor, this.LineThikness);
        	Brush textBrush = new SolidBrush(this.RelationTextColor);

        	string labelNameText = this._relationship.Name;
        	SizeF labelSize = context.MeasureString(labelNameText, this.RelationTextFont);

            // calculate the current selfgap, so the next selfrelation knows where to position
            int distance = RelationshipDrawer.DefaultSelfDistance;
            if (RelationshipDrawer._currentSelfDistance.ContainsKey(this._element1.Element.Id))
            {
            	distance = RelationshipDrawer._currentSelfDistance[this._element1.Element.Id];
            }
            RelationshipDrawer._currentSelfDistance[this._element1.Element.Id] = distance + RelationshipDrawer.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._marginTable, 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 label
            if (this.ShowRelationshipName)
            {
	            Point textLocation = new Point(edge.X - (int)labelSize.Width, edge.Y);
	            context.DrawString(labelNameText, this.RelationTextFont, textBrush, textLocation);
            }
            
            // draw cardinality
            this.DrawCardinality(symbolA, symbolB, straightA, straightB, context);
        }

        /// <summary>
        /// Draw the cardinalities on both sides.
        /// </summary>
        /// <param name="symbolA">Symbol point of the first table.</param>
        /// <param name="symbolB">Symbol point of the second table.</param>
        /// <param name="straightA">Straight point of the first table.</param>
        /// <param name="straightB">Straight point of the first table.</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._relationship is OneToOnePhysicalRelationalRelationship)
            {
            	OneToOnePhysicalRelationalRelationship castedRel = (OneToOnePhysicalRelationalRelationship)this._relationship;
            	if (castedRel.Cardinality1 == OneToOnePhysicalRelationalRelationship.Cardinality.OnlyOne)
            	{
            		this.DrawCardinalityOne(symbolA, straightA, context);
            	}
            	else if (castedRel.Cardinality1 == OneToOnePhysicalRelationalRelationship.Cardinality.ZeroOrOne)
            	{
            		this.DrawCardinalityZero(symbolA, straightA, context);
            		this.DrawCardinalityOne(symbolA, straightA, context);
            	}
            	
            	if (castedRel.Cardinality2 == OneToOnePhysicalRelationalRelationship.Cardinality.OnlyOne)
            	{
            		this.DrawCardinalityOne(symbolB, straightB, context);
            	}
            	else if (castedRel.Cardinality2 == OneToOnePhysicalRelationalRelationship.Cardinality.ZeroOrOne)
            	{
            		this.DrawCardinalityZero(symbolB, straightB, context);
            		this.DrawCardinalityOne(symbolB, straightB, context);
            	}
            }
            else if (this._relationship is OneToManyPhysicalRelationalRelationship)
            {
            	OneToManyPhysicalRelationalRelationship castedRel = (OneToManyPhysicalRelationalRelationship)this._relationship;
            	if (castedRel.Cardinality1 == OneToManyPhysicalRelationalRelationship.CardinalityForOne.OnlyOne)
            	{
            		this.DrawCardinalityOne(symbolA, straightA, context);
            	}
            	else if (castedRel.Cardinality1 == OneToManyPhysicalRelationalRelationship.CardinalityForOne.ZeroOrOne)
            	{
            		this.DrawCardinalityZero(symbolA, straightA, context);
            		this.DrawCardinalityOne(symbolA, straightA, context);
            	}

            	if (castedRel.Cardinality2 == OneToManyPhysicalRelationalRelationship.CardinalityForMany.OneOrMore)
            	{
            		this.DrawCardinalityOne(symbolB, straightB, context);
            		this.DrawCardinalityMore(symbolB, straightB, context);
            	}
            	else if (castedRel.Cardinality2 == OneToManyPhysicalRelationalRelationship.CardinalityForMany.ZeroOneOrMore)
            	{
            		this.DrawCardinalityZero(symbolB, straightB, context);
            		this.DrawCardinalityOne(symbolB, straightB, context);
            		this.DrawCardinalityMore(symbolB, straightB, context);
            	}
            }
        }
        
        /// <summary>
        /// Draw the zero cardinality on one side.
        /// </summary>
        /// <param name="symbol">Symbol point of the first table.</param>
        /// <param name="straight">Straight point of the first table.</param>
        /// <param name="context">Graphics context.</param>
        private void DrawCardinalityZero(Point symbol, Point straight, Graphics context)
        {
        	Pen cardinalityPen = new Pen(this.CardinalityColor);
        	
        	context.DrawLine(cardinalityPen, symbol, straight);
        	int size = this.CardinalityWidth;
        	int sizeSqueezed = size / 2;
        	int drawGap = (size / 2) + 2;
        	NotationHelper.SideLocation siteLoc = NotationHelper.EvalPointsLocation(symbol, straight);
        	if (NotationHelper.SideLocation.Top == siteLoc)
        	{
	        	context.FillEllipse(Brushes.White, symbol.X - sizeSqueezed, symbol.Y - drawGap, size, sizeSqueezed);
	        	context.DrawEllipse(cardinalityPen, symbol.X - sizeSqueezed, symbol.Y - drawGap, size, sizeSqueezed);
        	}
        	else if (NotationHelper.SideLocation.Bottom == siteLoc)
        	{
        		drawGap = 1;	// special one
	        	context.FillEllipse(Brushes.White, symbol.X - sizeSqueezed, symbol.Y + drawGap, size, sizeSqueezed);
	        	context.DrawEllipse(cardinalityPen, symbol.X - sizeSqueezed, symbol.Y + drawGap, size, sizeSqueezed);
        	}
        	else if (NotationHelper.SideLocation.Left == siteLoc)
        	{
	        	context.FillEllipse(Brushes.White, symbol.X - drawGap, symbol.Y - sizeSqueezed, sizeSqueezed, size);
	        	context.DrawEllipse(cardinalityPen, symbol.X - drawGap, symbol.Y - sizeSqueezed, sizeSqueezed, size);
        	}
        	else if (NotationHelper.SideLocation.Right == siteLoc)
        	{
        		drawGap = 2;	// special one
	        	context.FillEllipse(Brushes.White, symbol.X + drawGap, symbol.Y - sizeSqueezed, sizeSqueezed, size);
	        	context.DrawEllipse(cardinalityPen, symbol.X + drawGap, symbol.Y - sizeSqueezed, sizeSqueezed, size);
        	}
        }

        /// <summary>
        /// Draw the one cardinality on one side.
        /// </summary>
        /// <param name="symbol">Symbol point of the first table.</param>
        /// <param name="straight">Straight point of the first table.</param>
        /// <param name="context">Graphics context.</param>
        private void DrawCardinalityOne(Point symbol, Point straight, Graphics context)
        {
        	Pen cardinalityPen = new Pen(this.CardinalityColor);
        	
        	context.DrawLine(cardinalityPen, symbol, straight);
        	int size = this.CardinalityWidth / 2;
        	NotationHelper.SideLocation siteLoc = NotationHelper.EvalPointsLocation(symbol, straight);
        	if (NotationHelper.SideLocation.Top == siteLoc
        	   || NotationHelper.SideLocation.Bottom == siteLoc)
        	{
	        	Point start = new Point(symbol.X - size, symbol.Y);
	        	Point end = new Point(symbol.X + size, symbol.Y);
	        	context.DrawLine(cardinalityPen, start, end);
        	}
        	else if (NotationHelper.SideLocation.Left == siteLoc
        	        || NotationHelper.SideLocation.Right == siteLoc)
        	{
	        	Point start = new Point(symbol.X, symbol.Y - size);
	        	Point end = new Point(symbol.X, symbol.Y + size);
	        	context.DrawLine(cardinalityPen, start, end);
        	}
        }
        
        /// <summary>
        /// Draw the more cardinality on one side.
        /// </summary>
        /// <param name="symbol">Symbol point of the first table.</param>
        /// <param name="straight">Straight point of the first table.</param>
        /// <param name="context">Graphics context.</param>
        private void DrawCardinalityMore(Point symbol, Point straight, Graphics context)
        {
        	Pen cardinalityPen = new Pen(this.CardinalityColor);
        	
        	context.DrawLine(cardinalityPen, symbol, straight);
        	NotationHelper.SideLocation siteLoc = NotationHelper.EvalPointsLocation(symbol, straight);
        	if (NotationHelper.SideLocation.Top == siteLoc
        	   || NotationHelper.SideLocation.Bottom == siteLoc)
        	{
        		int delta = Math.Abs(symbol.Y - straight.Y) / 2;
	        	Point foot1 = new Point(straight.X - delta, straight.Y);
	        	Point foot2 = new Point(straight.X + delta, straight.Y);
	        	context.DrawLine(cardinalityPen, symbol, foot1);
	        	context.DrawLine(cardinalityPen, symbol, foot2);
        	}
        	else if (NotationHelper.SideLocation.Left == siteLoc
        	        || NotationHelper.SideLocation.Right == siteLoc)
        	{
        		int delta = Math.Abs(symbol.X - straight.X) / 2;
	        	Point foot1 = new Point(straight.X, straight.Y - delta);
	        	Point foot2 = new Point(straight.X, straight.Y + delta);
	        	context.DrawLine(cardinalityPen, symbol, foot1);
	        	context.DrawLine(cardinalityPen, symbol, foot2);
        	}
        }
        #endregion
	}
}
