#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.Drawing;
using System.Drawing.Drawing2D;
using dataMagus.Common;
#endregion

namespace dataMagus.Common.PlugIn.Notation
{
	/// <summary>
	/// This static class contains helper functionality for the notations.
	/// </summary>
	/// <remarks>
	/// Symbol point: By a symbol point is meant the point where the straight line
	/// between the middle point of the two elements subtends the controls border.
	/// In nearly all cases this point will be calculated with a margin (gap) around
	/// the control. Normally, this point can be used to draw the cardinalities or rolenames.
	/// 
	/// Straight point: By a straight point is meant the point that stands in a 90 degree
	/// angel from the symbol point to the control. It will mostly be used to draw the
	/// notations cardinalities.
	/// 
	/// The symbol point lives with its straight point in symbiosis.
	/// </remarks>
	public static class NotationHelper
	{
		#region Enumerations
		/// <summary>
		/// Location for different sides.
		/// </summary>
		public enum SideLocation
		{
			/// <summary>
			/// Side is undefined.
			/// </summary>
			Undefined,
			
			/// <summary>
			/// Location is on top.
			/// </summary>
			Top,
			
			/// <summary>
			/// Location is on bottom.
			/// </summary>
			Bottom,
			
			/// <summary>
			/// Location is on the right.
			/// </summary>
			Right,
			
			/// <summary>
			/// Location is on the left.
			/// </summary>
			Left
		}
		#endregion

		#region Delegates
		/// <summary>
		/// Handler to count the links of the implementing object between another one.
		/// </summary>
        public delegate int CountLinksBetween(IElement element);
		#endregion

		#region Methodes
        /// <summary>
        /// Calculate a multiplier which then will be multiplied with the X or Y axis 
        /// of the point. This will help to correct the position of overlaying points.
        /// Each call will decrease the multiplier and hop between the negativ and 
        /// positive numerical range.
        /// 
        /// <code>
        /// -------(2)-------	two relationships having overlaying middle points
        /// 
        ///     ___(1)___       
        ///    /         \      Y axis of both relationships has been corrected with
        /// ---           ---   the factor.
        ///    \___(1)___/      
        /// </code>
        /// </summary>
        /// <remarks>
        /// This method must not be called more times than two elements have relationships 
        /// together.
        /// </remarks>
        /// <example>
        /// If an element has 4 relationships (even) with another element, this method will 
        /// return: <code>-2 / 2 / -1 / 1</code>
        /// </example>
        /// <example>
        /// If an element has 3 relationships (odd) with another element, this method will 
        /// return: <code>-1 / 1 / 0</code>
        /// </example>
        public static int CalcCorrectionMultiplier(IElement master, CountLinksBetween masterLinksCounter, IElement slave, Dictionary<long, int> betweenCounter)
        {
        	// calc sumarized hash which does not care if entity1 is 2 or the other way round (GetHashCode() of string is Unique!)
        	// use long for this action, to prevent arithmetical overflows
        	int betweenCnt = 0;
        	long betweenHash = (long)((long)master.Id.GetHashCode() + (long)slave.Id.GetHashCode());
        	
        	// search counter for relations between these two entities
        	if (!betweenCounter.ContainsKey(betweenHash))
    	    {
        		betweenCnt = masterLinksCounter(slave);
        		// check for even number
        		if ((betweenCnt % 2) == 0)
        		{
        			betweenCnt++;
        		}
        		betweenCounter[betweenHash] = betweenCnt;
    	    }
        	else
        	{
        		betweenCnt = betweenCounter[betweenHash];
        	}

        	// decrease counter for next call
        	betweenCounter[betweenHash]--;
        	
        	// check if number is odd
        	bool isOdd = ((betweenCnt % 2.0F) > 0);
        	
        	// calc indicator
        	int multiplier = betweenCnt / 2;
        	if (isOdd)
        	{
        		multiplier*= -1;
        	}
        	
        	return multiplier;
        }

        /// <summary>
        /// Calculate helpfull points between two element controls.
        /// For the defintion of a symbol or straight point, see the class description.
        /// </summary>
        /// <param name="master">Master control.</param>
        /// <param name="slave">Slave control.</param>
        /// <param name="symbol1">Out: The symbol point of the master.</param>
        /// <param name="symbol2">Out: The symbol point of the slave.</param>
        /// <param name="straight1">Out: The straight point of the master.</param>
        /// <param name="straight2">Out: The straight point of the slave.</param>
        /// <param name="middle">Out: The middle point between master and slave.</param>
		public static void CalcPoints(ElementControl master, ElementControl slave,
			out Point symbol1, out Point symbol2, out Point straight1, out Point straight2, out Point middle)
        {
        	// redirect with divergency and gap = 0
        	NotationHelper.CalcPoints(master, slave, 0, 0, out symbol1, out symbol2, out straight1, out straight2, out middle);
        }
        
        /// <summary>
        /// Calculate helpfull points between two element controls.
        /// For the defintion of a symbol or straight point, see the class description.
        /// </summary>
        /// <param name="master">Master control.</param>
        /// <param name="slave">Slave control.</param>
        /// <param name="gap">Gap around the controls.</param>
        /// <param name="symbol1">Out: The symbol point of the master.</param>
        /// <param name="symbol2">Out: The symbol point of the slave.</param>
        /// <param name="straight1">Out: The straight point of the master.</param>
        /// <param name="straight2">Out: The straight point of the slave.</param>
        /// <param name="middle">Out: The middle point between master and slave.</param>
		public static void CalcPoints(ElementControl master, ElementControl slave, int gap,
			out Point symbol1, out Point symbol2, out Point straight1, out Point straight2, out Point middle)
        {
        	// redirect with divergency = 0
        	NotationHelper.CalcPoints(master, slave, gap, 0, out symbol1, out symbol2, out straight1, out straight2, out middle);
        }
        
        /// <summary>
        /// Calculate helpfull points between two element controls.
        /// For the defintion of a symbol or straight point, see the class description.
        /// </summary>
        /// <param name="master">Master control.</param>
        /// <param name="slave">Slave control.</param>
        /// <param name="gap">Gap around the controls.</param>
        /// <param name="divergency">Divergency on the X or Y axis depending on the location of the points.</param>
        /// <param name="symbol1">Out: The symbol point of the master.</param>
        /// <param name="symbol2">Out: The symbol point of the slave.</param>
        /// <param name="straight1">Out: The straight point of the master.</param>
        /// <param name="straight2">Out: The straight point of the slave.</param>
        /// <param name="middle">Out: The middle point between master and slave.</param>
		public static void CalcPoints(ElementControl master, ElementControl slave, int gap, int divergency,
			out Point symbol1, out Point symbol2, out Point straight1, out Point straight2, out Point middle)
		{
        	symbol1 = NotationHelper.CalcSymbolPoint(master, slave, gap, divergency);
            symbol2 = NotationHelper.CalcSymbolPoint(slave, master, gap, divergency);
            
			straight1 = NotationHelper.CalcStraightPoint(master, symbol1, gap);
			straight2 = NotationHelper.CalcStraightPoint(slave, symbol2, gap);
			middle = NotationHelper.EvalMiddle(symbol1, symbol2);
		}
		
        /// <summary>
        /// Calculate helpfull points for element controls having a self relation.
        /// The calculation is based on the top right of the control.
        /// For the defintion of a symbol or straight point, see the class description.
        /// </summary>
        /// <param name="ctrl">Control with the self relation.</param>
        /// <param name="gap">Gap around the control.</param>
        /// <param name="selfGap">Enlarge gap to calculate the selfSymbol point.</param>
        /// <param name="distance">Distance from the top right of the control.</param>
        /// <param name="symbol1">Out: The symbol point on the top.</param>
        /// <param name="symbol2">Out: The symbol point on the right.</param>
        /// <param name="straight1">Out: The straight point on the top.</param>
        /// <param name="straight2">Out: The straight point on the right.</param>
        /// <param name="selfSymbol1">Out: The self symbol point on the top. </param>
        /// <param name="selfSymbol2">Out: The self symbol point on the right.</param>
        /// <param name="edge">Out: The edge where the self relation gets together.</param>
		public static void CalcSelfPoints(ElementControl ctrl, int gap, int selfGap, int distance,
			out Point symbol1, out Point symbol2, out Point straight1, out Point straight2, 
			out Point selfSymbol1, out Point selfSymbol2, out Point edge)
		{
			straight1 = new Point(ctrl.TopRight.X - distance, ctrl.TopRight.Y);
			straight2 = new Point(ctrl.TopRight.X, ctrl.TopRight.Y + distance);
			
			symbol1 = new Point(straight1.X, ctrl.TopRight.Y - gap);
			symbol2 = new Point(straight2.X + gap, ctrl.TopRight.Y + distance);
			
			selfSymbol1 = new Point(straight1.X, ctrl.TopRight.Y - selfGap);
			selfSymbol2 = new Point(straight2.X + selfGap, ctrl.TopRight.Y + distance);
			
			edge = new Point(selfSymbol1.X + (selfSymbol2.X - selfSymbol1.X),
            	selfSymbol2.Y + (selfSymbol1.Y - selfSymbol2.Y));
		}
		
        /// <summary>
        /// Calculate symbol point between two element controls.
        /// For the defintion of a symbol or straight point, see the class description.
        /// </summary>
        /// <param name="master">Master control.</param>
        /// <param name="slave">Slave control.</param>
        /// <param name="gap">Gap around the control.</param>
        /// <param name="divergency">Divergency on the X or Y axis depending on the location of the points.</param>
        /// <returns>Symbol point on the master.</returns>
        public static Point CalcSymbolPoint(ElementControl master, ElementControl slave, int gap, int divergency)
        {
            // when symbolPoint on the bottom (or top)
            // calculate cathetus between middle of master and slave
            int cathetusA = slave.Middle.Y - master.Middle.Y;
            int cathetusB = slave.Middle.X - master.Middle.X;

            // define small cathetus A
            int cathetusAsmall = master.Height / 2;
            // add gap value
            cathetusAsmall += gap;
            
            // inverse if symbolPoint must be on the top (not bottom)
            cathetusAsmall *= (slave.Middle.Y < master.Middle.Y ? -1 : 1);
            
            // handle division by zero
            cathetusA = (cathetusA == 0 ? 1 : cathetusA);
           
            // calculate small cathetus B by trigonometrie
            // -> over proportion-formula [cathetusA/cathetusB == [cathetusAsmall/cathetusBsmall])
            int cathetusBsmall = cathetusAsmall * cathetusB / cathetusA;
            Point symbolPoint = new Point(master.Middle.X + cathetusBsmall, master.Middle.Y + cathetusAsmall);

            // if symbolPoint moves outside entity (not on bottom or top border)
            // -> try the left (or right) side
            if (!NotationHelper.IsPointInsideElement(master, symbolPoint, gap))
            {
                // calculate cathetus between middle of master and slave
                cathetusA = slave.Middle.X - master.Middle.X;
                cathetusB = slave.Middle.Y - master.Middle.Y;
                
                // define small cathetus A
                cathetusAsmall = master.Width / 2;
            	// add gab value
            	cathetusAsmall += gap;
                
                // inverse if symbolPoint must be on the right (not left)
                cathetusAsmall *= (slave.Middle.X < master.Middle.X ? -1 : 1);
                
                // handle division by zero
                cathetusA = (cathetusA == 0 ? 1 : cathetusA);
                
                // calculate small cathetus B by trigonometrie
                // -> over proportion-formula [cathetusA/cathetusB == [cathetusAsmall/cathetusBsmall])
                cathetusBsmall = cathetusAsmall * cathetusB / cathetusA;
                symbolPoint = new Point(master.Middle.X + cathetusAsmall, master.Middle.Y + cathetusBsmall);
                
	            // adjust symbolPoint by divergency on Y-axis
	           	symbolPoint.Y += divergency;
            }
            else 
            {
	            // adjust symbolPoint by divergency on X-axis
	           	symbolPoint.X += divergency;
            }
            return symbolPoint;
        }

        /// <summary>
        /// Calculate straight point of a control having a symbol point.
        /// For the defintion of a symbol or straight point, see the class description.
        /// </summary>
        /// <param name="ctrl">Control to calculate for.</param>
        /// <param name="symbolPoint">The symbol point of the control.</param>
        /// <param name="gap">Gap around the control.</param>
        /// <returns>Straight point on the control.</returns>
        public static Point CalcStraightPoint(ElementControl ctrl, Point symbolPoint, int gap)
        {
        	Point straightPoint = symbolPoint;
        	if (symbolPoint.Y < ctrl.TopLeft.Y)
        	{
        		straightPoint.Y += gap;
        	}
        	else if (symbolPoint.Y > ctrl.BottomRight.Y)
        	{
        		straightPoint.Y -= gap;
        	}
        	else if (symbolPoint.X < ctrl.TopLeft.X)
        	{
        		straightPoint.X += gap;
        	}
        	else
        	{
        		straightPoint.X -= gap;
        	}
        	return straightPoint;
        }
        
        /// <summary>
        /// Checks if a point is still inside an element control. This method can be used
        /// to check if a point is leaving the element.
        /// </summary>
        /// <param name="element">Control.</param>
        /// <param name="p">Point to be checked.</param>
        /// <param name="gap">Gap around the control.</param>
        /// <returns>true if point is still inside, otherwise false.</returns>
        public static bool IsPointInsideElement(ElementControl element, Point p, int gap)
        {
        	// enlarge entity corner points with gab
        	Point TopLeft = new Point(element.TopLeft.X - gap, element.TopLeft.Y - gap);
        	Point TopRight = new Point(element.TopRight.X + gap, element.TopRight.Y - gap);
        	Point BottomLeft = new Point(element.BottomLeft.X - gap, element.BottomLeft.Y + gap);
        	Point BottomRight = new Point(element.BottomRight.X + gap, element.BottomRight.Y + gap);
        	
        	// check if point is inside
        	return p.X >= TopLeft.X && p.X <= TopRight.X
        		&& p.Y <= BottomLeft.Y && p.Y <= BottomRight.Y;
        }
        
        /// <summary>
        /// Evaluate the best connection point of four points (ex. diamond).
        /// </summary>
        /// <param name="ctrl">Control which will be linked to the connection point.</param>
        /// <param name="top">Top point of the figure.</param>
        /// <param name="bottom">Buttom point of the figure.</param>
        /// <param name="left">Left point of the figure.</param>
        /// <param name="right">Right point of the figure.</param>
        /// <returns>Best point to link to.</returns>
        public static Point EvalBestConnectionPoint(ElementControl ctrl, Point top, Point bottom, Point left, Point right)
        {
            if (ctrl.MiddleTop.Y > bottom.Y)
            {
            	// ctrl is under symbol
            	return bottom;
            }
            else if(ctrl.MiddleBottom.Y < top.Y)
            {
            	// ctrl is over symbol
            	return top;
            }
            else if (ctrl.MiddleLeft.X > right.X)
            {
            	// ctrl is right from symbol
            	return right;
            }
            else if (ctrl.MiddleRight.X < left.X)
            {
            	return left;
            }
            return Point.Empty;
        }
        
        /// <summary>
        /// Evaluate the best connection point of four points (ex. diamond).
        /// </summary>
        /// <param name="master">Point which will be linked to the connection point.</param>
        /// <param name="top">Top point of the figure.</param>
        /// <param name="bottom">Buttom point of the figure.</param>
        /// <param name="left">Left point of the figure.</param>
        /// <param name="right">Right point of the figure.</param>
        /// <returns>Best point to link to.</returns>
        public static Point EvalBestConnectionPoint(Point master, Point top, Point bottom, Point left, Point right)
        {
            if (master.X < left.X)
            {
            	return left;
            }
            else if(master.X > right.X)
            {
            	return right;
            }
            else if (master.Y < top.Y)
            {
            	return top;
            }
            else if (master.Y > bottom.Y)
            {
            	return bottom;
            }
            return Point.Empty;
        }
        
        /// <summary>
        /// Evaluate where the symbol and straight point is located. These two points
        /// are enough for the evaluation, because they have a functional dependency on
        /// each other.
        /// </summary>
        /// <remarks>If symbol point is equal the straight point, SideLocation.Undefined will be returned.</remarks>
        /// <param name="symbolPoint">Symbol point.</param>
        /// <param name="straightPoint">Straight point.</param>
        /// <returns>Location of the points.</returns>
        public static SideLocation EvalPointsLocation(Point symbolPoint, Point straightPoint)
        {
        	// check for undefined location
        	if (symbolPoint == straightPoint)
        	{
        		return SideLocation.Undefined;
        	}
        	
        	// check side location
        	if (symbolPoint.X == straightPoint.X)
        	{
        		// is top or bottom
        		if (symbolPoint.Y < straightPoint.Y)
        		{
        			return SideLocation.Top;
        		}
        		else
        		{
        			return SideLocation.Bottom;
        		}
        	}
        	else if (symbolPoint.Y == straightPoint.Y)
        	{
        		// is left or right
        		if (symbolPoint.X < straightPoint.X)
        		{
        			return SideLocation.Left;
        		}
        		else
        		{
        			return SideLocation.Right;
        		}
        	}
        	return SideLocation.Undefined;
        }
        
        /// <summary>
        /// Create a triangle polygon whereas the difference between the start and help 
        /// point defining the height.
        /// </summary>
        /// <param name="start">Start point.</param>
        /// <param name="help">Help point.</param>
        /// <param name="width">Width.</param>
        /// <returns>Point[] containing the polygon.</returns>
        public static Point[] CreateTrianglePolygon(Point start, Point help, int width)
        {
	    	Point[] triangle = new Point[3];
	        triangle[0] = start;
	        // check if horizontal or vertical
	        if (help.X == start.X)
	        {
	            triangle[1] = new Point(help.X - width/2, help.Y);
	            triangle[2] = new Point(help.X + width/2, help.Y);
	        }
	        else
	        {
	            triangle[1] = new Point(help.X, help.Y - width/2);
	            triangle[2] = new Point(help.X, help.Y + width/2);
	        }
            return triangle;
        }
		
        /// <summary>
        /// Evaluate the middle between two points.
        /// </summary>
        /// <param name="p1">Point 1.</param>
        /// <param name="p2">Point 2.</param>
        /// <returns>Middle point.</returns>
        public static Point EvalMiddle(Point p1, Point p2)
        {
            return new Point(p1.X - ((p1.X - p2.X)/2),
				p1.Y - ((p1.Y - p2.Y)/2));
        }
        #endregion
	}
}
