//===============================================================================
// Microsoft patterns & practices
// Web Service Software Factory
//===============================================================================
// Copyright  Microsoft Corporation.  All rights reserved.
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//===============================================================================
// The example companies, organizations, products, domain names,
// e-mail addresses, logos, people, places, and events depicted
// herein are fictitious.  No association with any real company,
// organization, product, domain name, email address, logo, person,
// places, or events is intended or should be inferred.
//===============================================================================

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.ComponentModel;
using Microsoft.Practices.WizardFramework;
using System.Drawing.Design;
using System.ComponentModel.Design;
using System.Configuration;
using System.Windows.Forms.Design;
using System.Reflection;
using System.Diagnostics;
using System.Drawing;
using System.Windows.Forms.VisualStyles;

namespace Microsoft.Practices.SoftwareFactories.ServiceFactory.Editors.Grid.Design
{
    /// <summary>
    /// Custom column for Wizard datagrids
    /// </summary>
	[DesignerCategory("Code")]
	public class DesignerColumn : DataGridViewColumn
	{
		private PropertyDescriptor propertyDescriptor;
		private string typeEditor;
		private string typeConverter;

        /// <summary>
        /// Initializes a new instance of the <see cref="T:DesignerColumn"/> class.
        /// </summary>
		public DesignerColumn()
			: base(new DesignerCell())
		{
		}

		protected DesignerColumn(DataGridViewCell cellTemplate) : base(cellTemplate)
		{
		}

		private DesignerCell TypedCell
		{
			get { return (DesignerCell)this.CellTemplate; }
		}

        /// <summary>
        /// Gets or sets the type editor.
        /// </summary>
        /// <value>The type editor.</value>
		[DefaultValue(null)]
		[Browsable(true)]
		[Description("The UI type editor")]
		[Category("Design")]
		public virtual string TypeEditor
		{
			get { return typeEditor; }
			set { typeEditor = value; }
		}

        /// <summary>
        /// Gets or sets the type converter.
        /// </summary>
        /// <value>The type converter.</value>
		[DefaultValue(null)]
		[Browsable(true)]
		[Category("Design")]
		[Description("The converter for the value type")]
		public string TypeConverter
		{
			get { return typeConverter; }
			set { typeConverter = value; }
		}

        /// <summary>
        /// Gets or sets the property descriptor.
        /// </summary>
        /// <value>The property descriptor.</value>
        [Browsable(false)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public PropertyDescriptor PropertyDescriptor
        {
            get { return propertyDescriptor; }
			set { propertyDescriptor = value; }
        }

		/// <summary>
		/// Gets the bound property editor.
		/// </summary>
		/// <returns></returns>
		private UITypeEditor GetBoundPropertyEditor()
		{
			PropertyDescriptor property = GetBoundProperty();
			string editorName = TypeEditor;
			if (String.IsNullOrEmpty(editorName) && property != null)
			{
				EditorAttribute editorAttr = (EditorAttribute)property.Attributes[typeof(EditorAttribute)];
				if (editorAttr != null && !String.IsNullOrEmpty(editorAttr.EditorTypeName))
				{
					editorName = editorAttr.EditorTypeName;
				}
			}

			if (!String.IsNullOrEmpty(editorName))
			{
				return (UITypeEditor)Activator.CreateInstance(LoadType(editorName));
			}
			else
			{
				return (UITypeEditor)TypeDescriptor.GetEditor(ValueType, typeof(UITypeEditor));
			}
		}

		/// <summary>
		/// Gets the bound property converter.
		/// </summary>
		/// <returns></returns>
		private TypeConverter GetBoundPropertyConverter()
		{
			PropertyDescriptor property = GetBoundProperty();
			TypeConverter converterInstance;
			string converterName = TypeConverter;
			if (String.IsNullOrEmpty(converterName) && property != null)
			{
				TypeConverterAttribute converterAttr = (TypeConverterAttribute)property.Attributes[typeof(TypeConverterAttribute)];
				if (converterAttr != null && !String.IsNullOrEmpty(converterAttr.ConverterTypeName))
				{
					converterName = converterAttr.ConverterTypeName;
				}
			}

			if (!String.IsNullOrEmpty(converterName))
			{
				Type converterType = LoadType(converterName);
				converterInstance = (TypeConverter)Activator.CreateInstance(converterType);
			}
			else
			{
				converterInstance = TypeDescriptor.GetConverter(ValueType);
			}

			return converterInstance;
		}

		/// <summary>
		/// Loads the type.
		/// </summary>
		/// <param name="typeName">Name of the type.</param>
		/// <returns></returns>
		private Type LoadType(string typeName)
		{
			if (DataGridView.Site != null)
			{
				ITypeResolutionService typeResolution = (ITypeResolutionService)DataGridView.Site.GetService(typeof(ITypeResolutionService));
				if (typeResolution != null)
				{
					return typeResolution.GetType(typeName, true);
				}
			}

			return Type.GetType(typeName, true);
		}

		/// <summary>
		/// Gets the bound property.
		/// </summary>
		/// <returns></returns>
		protected PropertyDescriptor GetBoundProperty()
		{
            if (propertyDescriptor != null)
            {
                return propertyDescriptor;
            }

            if (DataGridView.DataSource == null)
            {
                return null;
            }

			BindingMemberInfo binding = new BindingMemberInfo(DataPropertyName);
			CurrencyManager dataManager = DataGridView.BindingContext[DataGridView.DataSource, binding.BindingPath] as CurrencyManager;
            if (dataManager == null)
            {
                return null;
            }

			PropertyDescriptor property = dataManager.GetItemProperties().Find(binding.BindingField, true);
			if (property == null)
			{
				throw new ArgumentException(DataPropertyName);
			}

			return property;
		}

		public class DesignerCell : DataGridViewTextBoxCell
		{
			protected DesignerColumn OwningDesignerColumn
			{
				get { return (DesignerColumn)OwningColumn; }
			}

            /// <summary>
            /// Gets the value of the cell as formatted for display.
            /// </summary>
            /// <param name="value">The value to be formatted.</param>
            /// <param name="rowIndex">The index of the cell's parent row.</param>
            /// <param name="cellStyle">The <see cref="T:System.Windows.Forms.DataGridViewCellStyle"></see> in effect for the cell.</param>
            /// <param name="valueTypeConverter">A <see cref="T:System.ComponentModel.TypeConverter"></see> associated with the value type that provides custom conversion to the formatted value type, or null if no such custom conversion is needed.</param>
            /// <param name="formattedValueTypeConverter">A <see cref="T:System.ComponentModel.TypeConverter"></see> associated with the formatted value type that provides custom conversion from the value type, or null if no such custom conversion is needed.</param>
            /// <param name="context">A bitwise combination of <see cref="T:System.Windows.Forms.DataGridViewDataErrorContexts"></see> values describing the context in which the formatted value is needed.</param>
            /// <returns>
            /// The formatted value of the cell or null if the cell does not belong to a <see cref="T:System.Windows.Forms.DataGridView"></see> control.
            /// </returns>
            /// <exception cref="T:System.Exception">Formatting failed and either there is no handler for the <see cref="E:System.Windows.Forms.DataGridView.DataError"></see> event of the <see cref="T:System.Windows.Forms.DataGridView"></see> control or the handler set the <see cref="P:System.Windows.Forms.DataGridViewDataErrorEventArgs.ThrowException"></see> property to true. The exception object can typically be cast to type <see cref="T:System.FormatException"></see>.</exception>
			protected override object GetFormattedValue(object value, int rowIndex, ref DataGridViewCellStyle cellStyle, TypeConverter valueTypeConverter, TypeConverter formattedValueTypeConverter, DataGridViewDataErrorContexts context)
			{
				return base.GetFormattedValue(
					ParseFormattedValue(value, cellStyle, formattedValueTypeConverter, valueTypeConverter),
					rowIndex, ref cellStyle,
					valueTypeConverter ?? OwningDesignerColumn.GetBoundPropertyConverter(),
					formattedValueTypeConverter ?? OwningDesignerColumn.GetBoundPropertyConverter(), 
                    context);
			}

            /// <summary>
            /// Converts a value formatted for display to an actual cell value.
            /// </summary>
            /// <param name="formattedValue">The display value of the cell.</param>
            /// <param name="cellStyle">The <see cref="T:System.Windows.Forms.DataGridViewCellStyle"></see> in effect for the cell.</param>
            /// <param name="formattedValueTypeConverter">A <see cref="T:System.ComponentModel.TypeConverter"></see> for the display value type, or null to use the default converter.</param>
            /// <param name="valueTypeConverter">A <see cref="T:System.ComponentModel.TypeConverter"></see> for the cell value type, or null to use the default converter.</param>
            /// <returns>The cell value.</returns>
            /// <exception cref="T:System.FormatException">The <see cref="P:System.Windows.Forms.DataGridViewCell.FormattedValueType"></see> property value is null.-or-The <see cref="P:System.Windows.Forms.DataGridViewCell.ValueType"></see> property value is null.-or-formattedValue cannot be converted.</exception>
            /// <exception cref="T:System.ArgumentNullException">cellStyle is null.</exception>
            /// <exception cref="T:System.ArgumentException">formattedValue is null.-or-The type of formattedValue does not match the type indicated by the <see cref="P:System.Windows.Forms.DataGridViewCell.FormattedValueType"></see> property. </exception>
			public override object ParseFormattedValue(object formattedValue, DataGridViewCellStyle cellStyle, TypeConverter formattedValueTypeConverter, TypeConverter valueTypeConverter)
			{
				if (formattedValue != null)
				{
					if (this.ValueType.IsAssignableFrom(formattedValue.GetType()))
					{
						return formattedValue;
					}
					else
					{
						TypeConverter converter = OwningDesignerColumn.GetBoundPropertyConverter();
						if (converter.CanConvertFrom(formattedValue.GetType()) &&
							converter.IsValid(formattedValue))
						{
							return converter.ConvertFrom(formattedValue);
						}
					}
				}

				return null;
			}

            /// <summary>
            /// Gets the type of the formatted value associated with the cell.
            /// </summary>
            /// <value></value>
            /// <returns>A <see cref="T:System.Type"></see> representing the <see cref="T:System.String"></see> type in all cases.</returns>
			public override Type FormattedValueType
			{
				get
				{
					return typeof(string);
				}
			}

			private bool HasStandardValues()
			{
				TypeConverter converter = OwningDesignerColumn.GetBoundPropertyConverter();
				return converter != null &&
					   converter.GetStandardValuesSupported();
			}

			protected override void OnMouseDown(DataGridViewCellMouseEventArgs e)
			{
				base.OnMouseDown(e);
				Point cellLocation = DataGridView.GetCellDisplayRectangle(e.ColumnIndex, e.RowIndex, false).Location;
				cellLocation.Offset(e.Location);
				if (buttonBounds.Contains(cellLocation))
				{
					pressed = true;
				}
			}

			protected override void OnMouseUp(DataGridViewCellMouseEventArgs e)
			{
				base.OnMouseUp(e);
				pressed = false;
			}

			bool pressed = false;

			protected override void OnMouseClick(DataGridViewCellMouseEventArgs e)
			{
				Point cellLocation = DataGridView.GetCellDisplayRectangle(e.ColumnIndex, e.RowIndex, false).Location;
				cellLocation.Offset(e.Location);
				if (buttonBounds.Contains(cellLocation))
				{
					UITypeEditor editor = OwningDesignerColumn.GetBoundPropertyEditor();
					if (editor != null && editor.GetEditStyle() == UITypeEditorEditStyle.Modal)
					{
						ITypeDescriptorContext context = new TypeDescriptorContext(DataGridView.Site, OwningDesignerColumn.GetBoundProperty(), this);
						object value = GetValue(e.RowIndex);
						value = editor.EditValue(context, DataGridView.Site, value);
						SetValue(e.RowIndex, value);
					}

					// Notify the grid the cell was modified
					DataGridView.NotifyCurrentCellDirty(true);
					DataGridView.NotifyCurrentCellDirty(false);
				}
				else
				{
					base.OnMouseClick(e);
				}
			}

			private Rectangle buttonBounds = Rectangle.Empty;

			/// <summary>
			/// Moves the error icon to the left when the button is shown
			/// </summary>
			protected override Rectangle GetErrorIconBounds(Graphics graphics, DataGridViewCellStyle cellStyle, int rowIndex)
			{
				Rectangle iconBounds = base.GetErrorIconBounds(graphics, cellStyle, rowIndex);

                if ((State & DataGridViewElementStates.Selected) > 0)
                {
                    iconBounds.Offset(-GetSize(rowIndex).Height, 0);
                }

				return iconBounds;
			}

			/// <summary>
			/// Paints the dots button when the cell is selected
			/// </summary>
			protected override void Paint(Graphics graphics, Rectangle clipBounds, Rectangle cellBounds, int rowIndex,
				DataGridViewElementStates cellState, object value, object formattedValue, string errorText,
				DataGridViewCellStyle cellStyle, DataGridViewAdvancedBorderStyle advancedBorderStyle, DataGridViewPaintParts paintParts)
			{
				base.Paint(graphics, clipBounds, cellBounds, rowIndex, cellState, value, formattedValue, errorText, cellStyle, advancedBorderStyle, paintParts);

				if ((cellState & DataGridViewElementStates.Selected) > 0)
				{
					Rectangle buttonRect = new Rectangle(cellBounds.Right - cellBounds.Height, cellBounds.Top, cellBounds.Height - 1, cellBounds.Height - 1);
					ButtonRenderer.DrawButton(graphics, buttonRect, "...", cellStyle.Font, false, pressed ? PushButtonState.Pressed : PushButtonState.Default);
					buttonBounds = buttonRect;
				}
			}

			#region Locally used TypeDescriptorContext implementation

			private class TypeDescriptorContext : ITypeDescriptorContext, IWindowsFormsEditorService
			{
				private IServiceProvider serviceProvider;
				private PropertyDescriptor propertyDescriptor;
				private object instance;

				public TypeDescriptorContext(IServiceProvider serviceProvider, PropertyDescriptor propertyDescriptor, object instance)
				{
					this.serviceProvider = serviceProvider;
					this.propertyDescriptor = propertyDescriptor;
					this.instance = instance;
				}

				public IContainer Container
				{
					get { throw new NotImplementedException(); }
				}

				public object Instance
				{
					get { return instance; }
				}

				public void OnComponentChanged()
				{
					throw new NotImplementedException();
				}

				public bool OnComponentChanging()
				{
					throw new NotImplementedException();
				}

				public PropertyDescriptor PropertyDescriptor
				{
					get { return propertyDescriptor; }
				}

				public object GetService(Type serviceType)
				{
                    if (serviceType == typeof(IWindowsFormsEditorService))
                    {
                        return this;
                    }
					return serviceProvider.GetService(serviceType);
				}

				#region IWindowsFormsEditorService Members

				void IWindowsFormsEditorService.CloseDropDown()
				{
					throw new NotImplementedException();
				}

				void IWindowsFormsEditorService.DropDownControl(Control control)
				{
					throw new NotImplementedException();
				}

				DialogResult IWindowsFormsEditorService.ShowDialog(Form dialog)
				{
					IUIService uiService = (IUIService)GetService(typeof(IUIService));
                    if (uiService != null)
                    {
                        return uiService.ShowDialog(dialog);
                    }
					return dialog.ShowDialog();
				}

				#endregion
			}

			#endregion
		}
	}
}
