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

namespace dataMagus.PlugIns.Notations.Odlg
{
	/// <summary>
	/// This class is used to represent a class in ODLG notation.
	/// </summary>
	public partial class ClassControl : ElementControl
	{
		#region Constants
		/// <summary>
		/// Defines the default gap between the title image and the title text.
		/// </summary>
        private const float DefaultImageTextGap = 20F;
		#endregion
		
		#region Members
		/// <summary>
		/// Physical class the control has been created from.
		/// </summary>
		private PhysicalObjectorientedClass _class = null;
		
        /// <summary>
        /// Maximum size of all automatically calculated textes
        /// </summary>
        private SizeF _autoFont = SizeF.Empty;

		/// <summary>
		/// Identifier if the control is expandend and the properties will be shown.
		/// </summary>
        private bool _isExpanded = true;
        
        /// <summary>
        /// Color of the border.
        /// </summary>
        private Color _borderColor = Color.DarkGray;
        
        /// <summary>
        /// Color of the class title text.
        /// </summary>
        private Color _classNameColor = Color.Black;
        
        /// <summary>
        /// Color of the property text.
        /// </summary>
        private Color _propertyNameColor = Color.Black;
        
        /// <summary>
        /// Font of the class title text.
        /// </summary>
        private Font _classNameFont = new Font(FontFamily.GenericSansSerif, 10, FontStyle.Bold);
        
        /// <summary>
        /// Font of the property text.
        /// </summary>
        private Font _propertyNameFont = new Font(FontFamily.GenericSansSerif, 8, FontStyle.Regular);
        #endregion
		
		#region Constructors / Destructors
		/// <summary>
		/// Default Constructor
		/// </summary>
		/// <remarks>Not used at runtime.</remarks>
		public ClassControl()
		{
            InitializeComponent();
		}
		
		/// <summary>
		/// Runtime constructor
		/// </summary>
		/// <param name="classItem">Physical class the control is based on.</param>
        public ClassControl(PhysicalObjectorientedClass classItem) 
        	: base(classItem)
        {
        	this._class = classItem;
            InitializeComponent();
        }
		#endregion
		
		#region Properties
		/// <summary>
		/// Identifier if the control is expandend and the properties will be shown.
		/// </summary>
		public bool IsExpanded 
		{
			get { return _isExpanded; }
			set { _isExpanded = value; }
		}
		
		/// <summary>
		/// Gap between the title image and the title text.
		/// </summary>
		public float ImageTextGap 
		{
			get { return ClassControl.DefaultImageTextGap; }
		}
		
        /// <summary>
        /// Color of the border.
        /// </summary>
        public Color BorderColor
        {
            get { return _borderColor; }
            set { _borderColor = value; }
        }

        /// <summary>
        /// Color of the class title text.
        /// </summary>
        public Color ClassNameColor
        {
            get { return _classNameColor; }
            set {_classNameColor = value; }
        }

        /// <summary>
        /// Color of the property text.
        /// </summary>
        public Color PropertyNameColor
        {
            get { return _propertyNameColor; }
            set { _propertyNameColor = value; }
        }

        /// <summary>
        /// Font of the class title text.
        /// </summary>
        public Font ClassNameFont
        {
            get { return _classNameFont; }
            set { _classNameFont = value; }
        }

        /// <summary>
        /// Font of the property text.
        /// </summary>
        public Font PropertyNameFont
        {
            get { return _propertyNameFont; }
            set { _propertyNameFont = value; }
        }
		#endregion
		
		#region Methodes
		/// <summary>
		/// Occures when the control will be painted.
		/// </summary>
		/// <param name="e">PaintEventArgs</param>
        protected override void OnPaint(PaintEventArgs e)
        {
            // before base call, calc font-size
            // else the CalcControlSize() would have _autoFont.Width = 0
            this._autoFont = this.CalcFontSize(e.Graphics);

            // call base
            base.OnPaint(e);

            // draw entity
            DrawClass(e.Graphics, new Point(0,0));
        }

        /// <summary>
        /// Draw the title of the class.
        /// </summary>
        /// <param name="context">Graphics context.</param>
        /// <param name="offset">Drawing offset.</param>
        /// <param name="text">Title text.</param>
        /// <param name="img">Image to draw</param>
        /// <param name="textFont">Font of the title.</param>
        /// <param name="textBrush">Brush of the title.</param>
        /// <param name="stringPos">Position of the title text.</param>
        /// <param name="imagePos">Position of the image.</param>
        private void DrawTitle(Graphics context, Point offset, string text, Image img, Font textFont, Brush textBrush, PointF stringPos, PointF imagePos)
        {
            DrawElementItem(context, offset, text, elementImages.Images[0], textFont, textBrush, stringPos, imagePos);
        }
        
        /// <summary>
        /// Draws a single element.
        /// </summary>
        /// <param name="context">Graphics context.</param>
        /// <param name="offset">Drawing offset.</param>
        /// <param name="text">Element text.</param>
        /// <param name="img">Image to draw</param>
        /// <param name="textFont">Font of the element text.</param>
        /// <param name="textBrush">Brush of the element text.</param>
        /// <param name="stringPos">Position of the element text.</param>
        /// <param name="imagePos">Position of the image.</param>
        private void DrawElementItem(Graphics context, Point offset, string text, Image img, Font textFont, Brush textBrush, PointF stringPos, PointF imagePos)
        {
        	// add offset
        	imagePos.X += offset.X;
        	imagePos.Y += offset.Y;
        	stringPos.X += offset.X;
        	stringPos.Y += offset.Y;
        	
            context.DrawImage(img, imagePos);
            context.DrawString(text, textFont, textBrush, stringPos);
        }

        /// <summary>
        /// Draw the border of the control.
        /// </summary>
        /// <remarks>Is used when the control should be drawn not on the screen but into another graphics context.</remarks>
        /// <param name="context">Graphics context.</param>
        /// <param name="offset">Drawing offset.</param>
        private void DrawBorder(Graphics context, Point offset)
        {
        	Pen boderPen = new Pen(this.BorderColor);
        	Brush bgBrush = new SolidBrush(this.BackColor);
        	
        	context.FillRectangle(bgBrush, new Rectangle(offset, this.Size));
        	context.DrawRectangle(boderPen, new Rectangle(offset, this.Size));
        }
        
        /// <summary>
        /// Draw the whole control.
        /// </summary>
        /// <remarks>Is used when the control should be drawn not on the screen but into another graphics context.</remarks>
        /// <param name="context">Graphics context.</param>
		public override void DrawControl(Graphics context)
		{
			// draw things on the control first
			this.DrawBorder(context, this.Location);
			
			// draw the table content
			this.DrawClass(context, this.Location);
		}
        
		/// <summary>
		/// Draw the complete class content.
		/// </summary>
        /// <param name="context">Graphics context.</param>
        /// <param name="offset">Drawing offset.</param>
        private void DrawClass(Graphics context, Point offset)
        {
        	// create brushes and pens
            Brush classNameBrush = new SolidBrush(_classNameColor);
            Brush propertyNameBrush = new SolidBrush(_propertyNameColor);
            
            // calc position of image and text
            PointF imgPos = new PointF(this.GapDelta, this.GapDelta);
            PointF strPos = new PointF(this.GapDelta + this.ImageTextGap, this.GapDelta);
            
            // draw title
            DrawTitle(context, offset, this._class.Name, elementImages.Images[0], this.ClassNameFont, classNameBrush, strPos, imgPos);

            // draw properties only when control is expanded
            if (this.IsExpanded)
            {
	            // calculate next position
	            strPos.Y += this._classNameFont.Height + this.GapDelta;
                imgPos.Y += this._classNameFont.Height + this.GapDelta;
	
	            // draw primary key props
	            IEnumerator keyEnum = this._class.GetKeyProperties();
	            while (keyEnum.MoveNext())
	            {
	            	PhysicalObjectorientedProperty key = (PhysicalObjectorientedProperty)keyEnum.Current;
	                DrawElementItem(context, offset, key.Name, elementImages.Images[1], this.PropertyNameFont, propertyNameBrush, strPos, imgPos);
	                // calculate next position
	                strPos.Y += this._propertyNameFont.Height + this.AttributeGapDelta;
	                imgPos.Y += this._propertyNameFont.Height + this.AttributeGapDelta;
	            }
	
	            // draw regular props
	            IEnumerator propEnum = this._class.GetProperties();
	            while (propEnum.MoveNext())
	            {
	            	PhysicalObjectorientedProperty prop = (PhysicalObjectorientedProperty)propEnum.Current;
	            	if (!prop.IsPartOfKey)
	            	{
                        Image dataTypeImage = elementImages.Images[2];
                        if (prop.DataType.BaseType == DataType.BaseDataType.Binary)
                        {
                            dataTypeImage = global::dataMagus.Common.DataTypeResources.Binary;
                        }
                        else if (prop.DataType.BaseType == DataType.BaseDataType.Boolean)
                        {
                            dataTypeImage = global::dataMagus.Common.DataTypeResources.Boolean;
                        }
                        else if (prop.DataType.BaseType == DataType.BaseDataType.DateTime)
                        {
                            dataTypeImage = global::dataMagus.Common.DataTypeResources.DateTime;
                        }
                        else if (prop.DataType.BaseType == DataType.BaseDataType.Float)
                        {
                            dataTypeImage = global::dataMagus.Common.DataTypeResources.Float;
                        }
                        else if (prop.DataType.BaseType == DataType.BaseDataType.Number)
                        {
                            dataTypeImage = global::dataMagus.Common.DataTypeResources.Number;
                        }
                        else if (prop.DataType.BaseType == DataType.BaseDataType.String)
                        {
                            dataTypeImage = global::dataMagus.Common.DataTypeResources.String;
                        }
                        else if (prop.DataType.BaseType == DataType.BaseDataType.Unknown)
                        {
                            dataTypeImage = global::dataMagus.Common.DataTypeResources.Unknown;
                        }

		                DrawElementItem(context, offset, prop.Name, dataTypeImage, this.PropertyNameFont, propertyNameBrush, strPos, imgPos);
		                // calculate next position
		                strPos.Y += _propertyNameFont.Height + this.AttributeGapDelta;
		                imgPos.Y += _propertyNameFont.Height + this.AttributeGapDelta;
	            	}
	            }
            }
        }
		
        /// <summary>
        /// Event handler.
        /// </summary>
        /// <param name="sender">Sender object.</param>
        /// <param name="e">EventArgs</param>
		private void ExpanderPictureClick(object sender, EventArgs e)
		{
        	// change picture
	       	if (this.IsExpanded)
	       	{
	       		this.expanderPicture.Image = global::dataMagus.PlugIns.Notations.Odlg.OtherResources.TreePlus;
	       	}
        	else 
        	{
	       		this.expanderPicture.Image = global::dataMagus.PlugIns.Notations.Odlg.OtherResources.TreeMinus;
        	}

        	// switch flag
        	this.IsExpanded = !this.IsExpanded;
        	
        	// refresh
        	base.RefreshSizeAndSchema();
		}
		
		/// <summary>
		/// Calculates the maximum font size of all textes.
		/// </summary>
		/// <param name="context">Graphics context.</param>
		/// <returns>Maximum size.</returns>
		private SizeF CalcFontSize(Graphics context)
		{
			float maxWidth = 0;
			float maxHeight = 0;

			// title
			SizeF titleSize = context.MeasureString(this._class.Name, this.ClassNameFont);
            maxWidth = Math.Max(maxWidth, titleSize.Width);
            maxHeight = Math.Max(maxHeight, titleSize.Height);
			
			// elements
            IEnumerator itemEnum = this._class.GetProperties();
            while (itemEnum.MoveNext())
            {
            	IElement element = (IElement)itemEnum.Current;
				SizeF elementSize = context.MeasureString(element.Name, this.PropertyNameFont);
	            maxWidth = Math.Max(maxWidth, elementSize.Width);
	            maxHeight = Math.Max(maxHeight, elementSize.Height);
            }
            
			return new SizeF(maxWidth, maxHeight);
		}
		
		/// <summary>
		/// Calculates the whole size of the control.
		/// </summary>
		/// <returns>Size of the control.</returns>
		public override Size CalcControlSize()
		{
            //--- Auto Width
            // calculate difference of string and image positions
            float calcWidth = this.GapDelta;      // left gap
            calcWidth += this._autoFont.Width;    	// largest width
            calcWidth += this.ImageTextGap;   		// delta between string and image position
            calcWidth += this.GapDelta;           // right gap
            if (null != this.expanderPicture.Image)
            {
            	calcWidth += this.expanderPicture.Image.Width; 	// expander picture 
            }

            //--- Auto Height
            float calcHeight = this.GapDelta;     // top gap
            calcHeight += this.ClassNameFont.Height;	// height of title
            calcHeight += this.GapDelta;          // gap between title and fields
            if (this.IsExpanded)
            {
                calcHeight += this._class.CountProperties * this.PropertyNameFont.Height;  // height of all fields
                calcHeight += (this._class.CountProperties-1) * this.AttributeGapDelta;  // gaps of all fields (without last gap -> bottom)
                calcHeight += this.GapDelta;		// bottom gap
            }
            return new Size((int)calcWidth, (int)calcHeight);
		}
		
        /// <summary>
        /// Event handler.
        /// </summary>
        /// <param name="sender">Sender object.</param>
        /// <param name="e">EventArgs</param>
		private void ClassControlLoad(object sender, EventArgs e)
		{
			this.IsExpanded = true;
       		this.expanderPicture.Image = global::dataMagus.PlugIns.Notations.Odlg.OtherResources.TreeMinus;
		}
		#endregion
	}
}
