#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.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
using dataMagus.Common;
using dataMagus.Forms.Handlers;
using dataMagus.Common.PlugIn.Notation;
using dataMagus.Common.Logical;
#endregion

namespace dataMagus.Forms.Controls
{
	/// <summary>
	/// Control to be used to draw a schema with.
	/// </summary>
    [Serializable]
	public partial class SchemaControl : UserControl
	{
		#region Constants
		/// <summary>
		/// Defines the default ant line dash pattern.
		/// </summary>
		private static readonly float[] DefaultAntLineDashPattern = new float[] { 3F, 3F };
		#endregion
		
		#region Members
		/// <summary>
		/// Current drawn schema.
		/// </summary>
        [NonSerialized]
		private ISchema _schema = null;

		/// <summary>
		/// Pen for drawing the ant lines.
		/// </summary>
		[NonSerialized]
        private Pen _antLinePen = new Pen(Color.Black);
        
        /// <summary>
        /// Identicator if ant line has to be drawn right now.
        /// </summary>
        [NonSerialized]
        private bool _antLineMode = false;
        
        /// <summary>
        /// Start point when drawing ant lines.
        /// </summary>
        [NonSerialized]
        private Point _antStartPoint = Point.Empty;
        
        /// <summary>
        /// Ant line rectangle when stopping drawing ant lines.
        /// </summary>
        [NonSerialized]
        private Rectangle _antLineRect = Rectangle.Empty;
        
        /// <summary>
        /// Handler instance for the schema control.
        /// </summary>
        [NonSerialized]
        private SchemaControlHandler _handler = null;
		#endregion
		
		#region Constructors / Destructors
		/// <summary>
		/// Default constructor.
		/// </summary>
		public SchemaControl()
		{
			InitializeComponent();
			this._handler = new SchemaControlHandler(this);
		}
		#endregion
		
		#region Properties
		/// <summary>
		/// Current drawn schema.
		/// </summary>
		public ISchema Schema 
		{
			get { return this._schema; }
			set 
			{
				if (this._schema != value)
				{
					if (null != this._schema)
					{
						this._handler.CleanUpSchema(this._schema);
						this._schema.SchemaChanged -= new SchemaChangedHandler(this.OnSchemaChanged);
					}
					
					this._schema = value;
					
					if (null != this._schema)
					{
						this._schema.SchemaChanged += new SchemaChangedHandler(this.OnSchemaChanged);
						this._handler.CreateSchema(this._schema);
						this.Refresh();
					}
				}
			}
		}
		#endregion
		
		#region Methodes
		/// <summary>
		/// Reset all application options on all element controls the schema contains.
		/// </summary>
		public void ResetApplicationOptions()
		{
			foreach (Control ctrl in this.Controls)
			{
				if (ctrl is ElementControl)
				{
					ElementControl elementCtrl = (ElementControl)ctrl;
            		elementCtrl.SnapSize = ApplicationOptions.Instance.SnapSize;
					elementCtrl.ForceRefreshOnDrag = ApplicationOptions.Instance.ForceRefreshOnDrag;
				}
			}
		}
		
		/// <summary>
		/// Change the notation on the handler and refresh the schema.
		/// </summary>
		public void ChangedNotation()
		{
			// cleanup and create new one
			this._handler.CleanUpSchema(this._schema);
			this._handler.CreateSchema(this._schema);
			
			// refresh controls
			this.Refresh();
			// refresh relationships-drawing
			this.Invalidate();
			this.Update();
		}
		
		/// <summary>
		/// Event handler.
		/// </summary>
		/// <param name="sender">Sender object.</param>
		/// <param name="e">SchemaEventArgs</param>
		public virtual void OnSchemaChanged(object sender, SchemaEventArgs e)
		{
			try
			{
	        	if (SchemaChangedAction.ExchangeSchema == e.Action)
	        	{
	        		// exchange and rebuilded it (Schema will be created automatically)
	        		this.Schema = e.Schema;
	        	}
	        	else if (SchemaChangedAction.RebuildedSchema == e.Action)
	        	{
	        		// rebuild completely when the schema has been rebuilded
					this._handler.CleanUpSchema(e.Schema);
					this._handler.CreateSchema(e.Schema);
					this.Refresh();
	        	}
	        	else
	        	{
	    			// just redirect the event (ex. when a new entitiy joins)
					this._handler.HandleOnSchemaChanged(e);
	        	}
			}
			catch (Exception ex)
			{
				MessageForm.ShowDialog(ex);
			}
		}
		
		/// <summary>
		/// Find the element control corresponding to the given element.
		/// </summary>
		/// <param name="element">Element to search the control for.</param>
		/// <returns>The found control or null if nothing has been found.</returns>
		public ElementControl FindControl(IElement element)
		{
			return _handler.FindControl(element);
		}

		/// <summary>
		/// Draw the complete schema to a graphics context.
		/// </summary>
		/// <param name="context">Graphics context.</param>
		public void DrawSchema(Graphics context)
		{
			// set properties on context
			SmoothingMode oldSmoothingMode = context.SmoothingMode;
			PixelOffsetMode oldPixelOffsetMode = context.PixelOffsetMode;

			// enable better quality
			context.SmoothingMode = SmoothingMode.AntiAlias;
			context.PixelOffsetMode = PixelOffsetMode.HighQuality;
			foreach (Control ctrl in this.Controls)
			{
				if (ctrl is ElementControl)
				{
					((ElementControl)ctrl).DrawControl(context);
				}
			}
			context.SmoothingMode = oldSmoothingMode;
			context.PixelOffsetMode = oldPixelOffsetMode;

			// draw relationships
			this.DrawRelationships(context);
		}
		
		/// <summary>
		/// Draw all relationships to a graphics context.
		/// </summary>
		/// <param name="context">Graphics context.</param>
		public void DrawRelationships(Graphics context)
		{
			// set properties on context
			SmoothingMode oldSmoothingMode = context.SmoothingMode;
			PixelOffsetMode oldPixelOffsetMode = context.PixelOffsetMode;
			
			// enable better quality
			context.SmoothingMode = SmoothingMode.AntiAlias;
			context.PixelOffsetMode = PixelOffsetMode.HighQuality;
			this._handler.HandleOnPaint(context);
			context.SmoothingMode = oldSmoothingMode;
			context.PixelOffsetMode = oldPixelOffsetMode;
		}
		
		/// <summary>
		/// Occures when the control will be painted.
		/// </summary>
		/// <param name="e">PaintEventArgs</param>
		protected override void OnPaint(PaintEventArgs e)
		{
			// draw the schema
			this.DrawRelationships(e.Graphics);
			
            // draw ant-line
            if (this._antLineMode)
            {
                e.Graphics.DrawRectangle(_antLinePen, _antLineRect);
            }
            
            // call base
			base.OnPaint(e);
		}
		
		/// <summary>
		/// Raised when pressing the mouse down button on the control.
        /// </summary>
        /// <param name="e">MouseEventArgs</param>
		protected override void OnMouseDown(MouseEventArgs e)
		{
			try
			{
	            // call base
	            base.OnMouseDown(e);
	
	            // draw ant-line for selection
	            this._antLineRect = new Rectangle();
	            this._antLinePen.DashPattern = SchemaControl.DefaultAntLineDashPattern;
	            this._antStartPoint = e.Location;
	            this._antLineMode = true;
			}
			catch (Exception ex)
			{
				MessageForm.ShowDialog(ex);
			}
		}
		
		/// <summary>
		/// Raised when pressing the mouse up button on the control.
        /// </summary>
        /// <param name="e">MouseEventArgs</param>
		protected override void OnMouseUp(MouseEventArgs e)
		{
			try
			{
	            // call base
	            base.OnMouseUp(e);
	
	            // stop drawing ant-line
	            if (this._antLineMode)
	            {
	            	// unselect all
	            	this._handler.UnSelectAll();
	
			    	// select entities in range
			    	this.SelectElementsInRange(this._antLineRect);
			    	
			    	// stop mode an empty antlines
			    	this._antLineMode = false;
	            	this._antLineRect = Rectangle.Empty;
	            	this.Invalidate();
	            }
	            this.Invalidate();
			}
			catch (Exception ex)
			{
				MessageForm.ShowDialog(ex);
			}
		}

		/// <summary>
		/// Raised when the mouse moves on the control.
        /// </summary>
        /// <param name="e">MouseEventArgs</param>
		protected override void OnMouseMove(MouseEventArgs e)
		{
            // call base
            base.OnMouseMove(e);

            if (_antLineMode)
            {
                // draw ant-line
                int deltaX = e.X - _antStartPoint.X;
                int deltaY = e.Y - _antStartPoint.Y;
                
                if (deltaX >= 0 && deltaY >= 0)
                {
                	this._antLineRect.Location = _antStartPoint;
                }
                else if (deltaX <= 0 && deltaY >= 0)
                {
                	this._antLineRect.Location = new Point(e.Location.X, _antStartPoint.Y);
                }
                else if (deltaX >= 0 && deltaY <= 0)
                {
                	this._antLineRect.Location = new Point(_antStartPoint.X, e.Location.Y);
                }
                else
                {
                	this._antLineRect.Location = e.Location;
                }

        		this._antLineRect.Width = Math.Abs(deltaX);
        		this._antLineRect.Height = Math.Abs(deltaY);
        		this.Invalidate();
            }
		}
	
		/// <summary>
		/// Select all element controls which are in range of the given rectangle.
		/// </summary>
		/// <param name="rect">Range as rectangle.</param>
		private void SelectElementsInRange(Rectangle rect)
        {
        	foreach (Control ctrl in this.Controls)
        	{
        		if (ctrl is ElementControl)
        		{
	    			ElementControl elementCtrl = (ElementControl)ctrl;
	    			Point rectStart = rect.Location;
	    			Point rectEnd = new Point(rect.X + rect.Width, rect.Y + rect.Height);
	    			
	    			if (elementCtrl.TopLeft.X > rect.Location.X && elementCtrl.TopLeft.Y > rect.Location.Y
	    			    && elementCtrl.BottomRight.X < rectEnd.X && elementCtrl.BottomRight.Y < rectEnd.Y)
	    			{
	        			elementCtrl.IsSelected = true;
	    			}
        		}
        	}
        }
		
		/// <summary>
		/// Select a single element control found by a given element.
		/// </summary>
		/// <param name="element">Element to be selected.</param>
		public void SelectElement(IElement element)
		{
			ElementControl ctrl = this.FindControl(element);
			if (null != ctrl)
			{
				ctrl.IsSelected = true;
			}
		}
		
		/// <summary>
		/// Event handler.
		/// </summary>
		/// <param name="sender">Sender object.</param>
		/// <param name="e">SchemaEventArgs</param>
		private void SchemaControlMouseDown(object sender, MouseEventArgs e)
		{
			try
			{
				// just redirect to handler
				this._handler.HandleMouseDown(e);
			}
			catch (Exception ex)
			{
				MessageForm.ShowDialog(ex);
			}
		}
		#endregion
	}
}
