using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Windows.Forms.Design;
using System.Windows.Forms.Design.Behavior;

namespace System.ComponentModel.Design
{
	internal static class ComponentDesignerExtension
	{
		public const System.Reflection.BindingFlags AllInstBind = System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance;

		public static object EditValue(this ComponentDesigner designer, object objectToChange, string propName)
		{
			PropertyDescriptor prop = TypeDescriptor.GetProperties(objectToChange)[propName];
			EditorServiceContext context = new EditorServiceContext(designer, prop);
			var editor = prop.GetEditor(typeof(System.Drawing.Design.UITypeEditor)) as System.Drawing.Design.UITypeEditor;
			object curVal = prop.GetValue(objectToChange);
			object newVal = editor.EditValue(context, context, curVal);
			if (newVal != curVal)
				try { prop.SetValue(objectToChange, newVal); }
				catch (CheckoutException) { }
			return newVal;
		}

		public static List<DesignerActionItem> GetAllAttributedActionItems(this DesignerActionList actionList)
		{
			var fullAIList = new List<DesignerActionItem>();
			foreach (var mbr in actionList.GetType().GetMethods(AllInstBind))
			{
				foreach (IActionGetItem attr in mbr.GetCustomAttributes(typeof(DesignerActionMethodAttribute), false))
				{
					if (mbr.ReturnType == typeof(void) && mbr.GetParameters().Length == 0)
						fullAIList.Add(attr.GetItem(actionList, mbr));
					else
						throw new FormatException("DesignerActionMethodAttribute must be applied to a method returning void and having no parameters.");
				}
			}
			foreach (var mbr in actionList.GetType().GetProperties(AllInstBind))
			{
				foreach (IActionGetItem attr in mbr.GetCustomAttributes(typeof(DesignerActionPropertyAttribute), false))
					fullAIList.Add(attr.GetItem(actionList, mbr));
			}
			fullAIList.Sort(CompareItems);
			return fullAIList;
		}

		public static DesignerVerbCollection GetAttributedVerbs(this ComponentDesigner designer)
		{
			var verbs = new DesignerVerbCollection();
			foreach (var m in designer.GetType().GetMethods(AllInstBind))
			{
				foreach (DesignerVerbAttribute attr in m.GetCustomAttributes(typeof(DesignerVerbAttribute), true))
				{
					verbs.Add(attr.GetDesignerVerb(designer, m));
				}
			}
			return verbs;
		}

		public static DesignerActionItemCollection GetFilteredActionItems(this DesignerActionList actionList, List<DesignerActionItem> fullAIList)
		{
			DesignerActionItemCollection col = new DesignerActionItemCollection();
			fullAIList.ForEach(ai => { if (CheckCondition(actionList, ai)) { col.Add(ai); } });

			// Add header items for displayed items
			int i = 0; string cat = null;
			while (i < col.Count)
			{
				string curCat = col[i].Category;
				if (string.Compare(curCat, cat, true, Globalization.CultureInfo.CurrentCulture) != 0)
				{
					col.Insert(i++, new DesignerActionHeaderItem(curCat));
					cat = curCat;
				}
				i++;
			}

			return col;
		}

		public static IDictionary<string, List<Attribute>> GetRedirectedProperties(this ComponentDesigner d)
		{
			var ret = new Dictionary<string, List<Attribute>>();
			foreach (var prop in d.GetType().GetProperties(AllInstBind))
			{
				foreach (RedirectedDesignerPropertyAttribute attr in prop.GetCustomAttributes(typeof(RedirectedDesignerPropertyAttribute), false))
				{
					List<Attribute> attributes;
					if (attr.ApplyOtherAttributes)
					{
						attributes = new List<Attribute>(Array.ConvertAll<object, Attribute>(prop.GetCustomAttributes(false), o => o as System.Attribute));
						attributes.RemoveAll(a => a is RedirectedDesignerPropertyAttribute);
					}
					else
						attributes = new List<Attribute>();
					ret.Add(prop.Name, attributes);
				}
			}
			return ret;
		}

		public static void RedirectRegisteredProperties(this ComponentDesigner d, System.Collections.IDictionary properties, IDictionary<string, List<Attribute>> redirectedProps)
		{
			foreach (var propName in redirectedProps.Keys)
			{
				PropertyDescriptor oldPropertyDescriptor = (PropertyDescriptor)properties[propName];
				if (oldPropertyDescriptor != null)
				{
					List<Attribute> attributes = redirectedProps[propName];
					properties[propName] = TypeDescriptor.CreateProperty(d.GetType(), oldPropertyDescriptor, attributes.ToArray());
				}
			}
		}

		[Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "d")]
		public static void RemoveProperties(this ComponentDesigner d, System.Collections.IDictionary properties, IEnumerable<string> propertiesToRemove)
		{
			foreach (string p in propertiesToRemove)
				if (properties.Contains(p))
					properties.Remove(p);
		}

		public static void SetComponentProperty<T>(this ComponentDesigner d, string propName, T value)
		{
			PropertyDescriptor propDesc = TypeDescriptor.GetProperties(d.Component)[propName];
			if (propDesc != null && propDesc.PropertyType == typeof(T) && !propDesc.IsReadOnly && propDesc.IsBrowsable)
				propDesc.SetValue(d.Component, value);
		}

		public static System.Windows.Forms.DialogResult ShowDialog(this ComponentDesigner designer, System.Windows.Forms.Form dialog)
		{
			EditorServiceContext context = new EditorServiceContext(designer);
			return context.ShowDialog(dialog);
		}

		private static bool CheckCondition(DesignerActionList actionList, DesignerActionItem ai)
		{
			if (ai.Properties["Condition"] != null)
			{
				var p = actionList.GetType().GetProperty((string)ai.Properties["Condition"], AllInstBind, null, typeof(bool), Type.EmptyTypes, null);
				if (p != null)
					return (bool)p.GetValue(actionList, null);
			}
			return true;
		}

		private static int CompareItems(DesignerActionItem a, DesignerActionItem b)
		{
			int c = string.Compare(a.Category ?? string.Empty, b.Category ?? string.Empty, true, Globalization.CultureInfo.CurrentCulture);
			if (c != 0)
				return c;
			c = (int)a.Properties["Order"] - (int)b.Properties["Order"];
			if (c != 0)
				return c;
			return string.Compare(a.DisplayName, b.DisplayName, true, Globalization.CultureInfo.CurrentCulture);
		}
	}

	[System.AttributeUsage(System.AttributeTargets.Property, Inherited = true, AllowMultiple = false)]
	internal sealed class RedirectedDesignerPropertyAttribute : System.Attribute
	{
		public RedirectedDesignerPropertyAttribute() { ApplyOtherAttributes = true; }

		public bool ApplyOtherAttributes { get; set; }
	}
}

namespace System.Windows.Forms.Design
{
	internal interface IActionGetItem
	{
		string Category { get; }

		DesignerActionItem GetItem(DesignerActionList actions, Reflection.MemberInfo mbr);
	}

	[System.AttributeUsage(System.AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
	internal sealed class DesignerActionMethodAttribute : System.Attribute, IActionGetItem
	{
		public DesignerActionMethodAttribute(string displayName, int displayOrder = 0)
		{
			DisplayName = displayName;
			DisplayOrder = displayOrder;
		}

		public bool AllowAssociate { get; set; }

		public string Category { get; set; }

		public string Condition { get; set; }

		public string Description { get; set; }

		public string DisplayName { get; }

		public int DisplayOrder { get; }

		public bool IncludeAsDesignerVerb { get; set; }

		DesignerActionItem IActionGetItem.GetItem(DesignerActionList actions, Reflection.MemberInfo mbr)
		{
			var ret = new DesignerActionMethodItem(actions, mbr.Name, DisplayName, Category, Description, IncludeAsDesignerVerb)
				{ AllowAssociate = AllowAssociate };
			if (!string.IsNullOrEmpty(Condition))
				ret.Properties.Add("Condition", Condition);
			ret.Properties.Add("Order", DisplayOrder);
			return ret;
		}
	}

	[System.AttributeUsage(System.AttributeTargets.Property, Inherited = true, AllowMultiple = false)]
	internal sealed class DesignerActionPropertyAttribute : System.Attribute, IActionGetItem
	{
		public DesignerActionPropertyAttribute(string displayName, int displayOrder = 0)
		{
			DisplayName = displayName;
			DisplayOrder = displayOrder;
		}

		public bool AllowAssociate { get; set; }

		public string Category { get; set; }

		public string Condition { get; set; }

		public string Description { get; set; }

		public string DisplayName { get; }

		public int DisplayOrder { get; }

		DesignerActionItem IActionGetItem.GetItem(DesignerActionList actions, Reflection.MemberInfo mbr)
		{
			var ret = new DesignerActionPropertyItem(mbr.Name, DisplayName, Category, Description)
				{ AllowAssociate = AllowAssociate };
			if (!string.IsNullOrEmpty(Condition))
				ret.Properties.Add("Condition", Condition);
			ret.Properties.Add("Order", DisplayOrder);
			return ret;
		}
	}

	[System.AttributeUsage(System.AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
	internal sealed class DesignerVerbAttribute : System.Attribute
	{
		private CommandID cmdId;
		private string menuText;

		public DesignerVerbAttribute(string menuText)
		{
			this.menuText = menuText;
		}

		public DesignerVerbAttribute(string menuText, Guid commandMenuGroup, int commandId)
		{
			this.menuText = menuText;
			cmdId = new CommandID(commandMenuGroup, commandId);
		}

		internal DesignerVerb GetDesignerVerb(object obj, Reflection.MethodInfo mi)
		{
			EventHandler handler = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), obj, mi);
			if (cmdId != null)
				return new DesignerVerb(menuText, handler, cmdId);
			return new DesignerVerb(menuText, handler);
		}
	}

	internal class EditorServiceContext : IWindowsFormsEditorService, ITypeDescriptorContext, IServiceProvider
	{
		private IComponentChangeService _componentChangeSvc;
		private ComponentDesigner _designer;
		private PropertyDescriptor _targetProperty;

		internal EditorServiceContext(ComponentDesigner designer)
		{
			_designer = designer;
		}

		internal EditorServiceContext(ComponentDesigner designer, PropertyDescriptor prop)
		{
			_designer = designer;
			_targetProperty = prop;
			if (prop == null)
			{
				prop = TypeDescriptor.GetDefaultProperty(designer.Component);
				if ((prop != null) && typeof(System.Collections.ICollection).IsAssignableFrom(prop.PropertyType))
					_targetProperty = prop;
			}
		}

		internal EditorServiceContext(ComponentDesigner designer, PropertyDescriptor prop, string newVerbText)
			: this(designer, prop)
		{
			_designer.Verbs.Add(new DesignerVerb(newVerbText, new EventHandler(OnEditItems)));
		}

		private T GetService<T>() => (T)((IServiceProvider)this).GetService(typeof(T));

		private void OnEditItems(object sender, EventArgs e)
		{
			object component = _targetProperty.GetValue(_designer.Component);
			if (component != null)
			{
				CollectionEditor editor = TypeDescriptor.GetEditor(component, typeof(System.Drawing.Design.UITypeEditor)) as CollectionEditor;
				if (editor != null)
					editor.EditValue(this, this, component);
			}
		}

		void ITypeDescriptorContext.OnComponentChanged()
		{
			ChangeService.OnComponentChanged(_designer.Component, _targetProperty, null, null);
		}

		bool ITypeDescriptorContext.OnComponentChanging()
		{
			try
			{
				ChangeService.OnComponentChanging(_designer.Component, _targetProperty);
			}
			catch (CheckoutException exception)
			{
				if (exception != CheckoutException.Canceled)
					throw;
				return false;
			}
			return true;
		}

		object IServiceProvider.GetService(Type serviceType)
		{
			if ((serviceType == typeof(ITypeDescriptorContext)) || (serviceType == typeof(IWindowsFormsEditorService)))
				return this;
			if ((_designer.Component != null) && (_designer.Component.Site != null))
				return _designer.Component.Site.GetService(serviceType);
			return null;
		}

		void IWindowsFormsEditorService.CloseDropDown()
		{
		}

		void IWindowsFormsEditorService.DropDownControl(Control control)
		{
		}

		public DialogResult ShowDialog(Form dialog)
		{
			if (dialog == null)
				throw new ArgumentNullException(nameof(dialog));
			IUIService service = GetService<IUIService>();
			if (service != null)
				return service.ShowDialog(dialog);
			return dialog.ShowDialog(_designer.Component as IWin32Window);
		}

		private IComponentChangeService ChangeService
		{
			get
			{
				if (_componentChangeSvc == null)
					_componentChangeSvc = GetService<IComponentChangeService>();
				return _componentChangeSvc;
			}
		}

		IContainer ITypeDescriptorContext.Container
		{
			get
			{
				if (_designer.Component.Site != null)
					return _designer.Component.Site.Container;
				return null;
			}
		}

		object ITypeDescriptorContext.Instance => _designer.Component;

		PropertyDescriptor ITypeDescriptorContext.PropertyDescriptor => _targetProperty;
	}

	internal abstract class RichBehavior<D> : Behavior.Behavior where D : ControlDesigner
	{
		public RichBehavior(D designer)
		{
			Designer = designer;
		}

		public D Designer { get; }
	}

	internal class RichComponentDesigner<C, A> : ComponentDesigner
		where C : Component
		where A : _BaseDesignerActionList
	{
		private A actions;
		private Adorner adorner;
		private IDictionary<string, List<Attribute>> redirectedProps;
		private DesignerVerbCollection verbs;

		public override DesignerActionListCollection ActionLists
		{
			get
			{
				if (actions == null)
					actions = Activator.CreateInstance(typeof(A), this, Component) as A;
				return new DesignerActionListCollection(new DesignerActionList[] { actions });
			}
		}

		public BehaviorService BehaviorService { get; private set; }

		public IComponentChangeService ComponentChangeService { get; private set; }

		public new C Component => (C)base.Component;

		public virtual GlyphCollection Glyphs => Adorner.Glyphs;

		public ISelectionService SelectionService { get; private set; }

		public override DesignerVerbCollection Verbs
		{
			get
			{
				if (verbs == null)
					verbs = this.GetAttributedVerbs();
				return verbs;
			}
		}

		internal Adorner Adorner
		{
			get
			{
				if (adorner == null)
				{
					adorner = new Adorner();
					BehaviorService.Adorners.Add(adorner);
				}
				return adorner;
			}
		}

		protected virtual IEnumerable<string> PropertiesToRemove => new string[0];

		public override void Initialize(IComponent component)
		{
			base.Initialize(component);
			BehaviorService = GetService<BehaviorService>();
			SelectionService = GetService<ISelectionService>();
			if (SelectionService != null)
				SelectionService.SelectionChanged += OnSelectionChanged;
			ComponentChangeService = GetService<IComponentChangeService>();
			if (ComponentChangeService != null)
				ComponentChangeService.ComponentChanged += OnComponentChanged;
		}

		protected override void Dispose(bool disposing)
		{
			if (disposing)
			{
				if (BehaviorService != null & adorner != null)
					BehaviorService.Adorners.Remove(adorner);
				ISelectionService ss = SelectionService;
				if (ss != null)
					ss.SelectionChanged -= OnSelectionChanged;
				IComponentChangeService cs = ComponentChangeService;
				if (cs != null)
					cs.ComponentChanged -= OnComponentChanged;
			}
			base.Dispose(disposing);
		}

		protected virtual S GetService<S>() where S : class => (S)GetService(typeof(S));

		protected virtual void OnComponentChanged(object sender, ComponentChangedEventArgs e)
		{
		}

		protected virtual void OnSelectionChanged(object sender, EventArgs e)
		{
		}

		protected override void PreFilterProperties(System.Collections.IDictionary properties)
		{
			base.PreFilterProperties(properties);

			// RedirectRegisteredProperties
			if (redirectedProps == null)
				redirectedProps = this.GetRedirectedProperties();
			this.RedirectRegisteredProperties(properties, redirectedProps);

			// Remove properties
			this.RemoveProperties(properties, PropertiesToRemove);
		}
	}

	internal class RichControlDesigner<C, A> : ControlDesigner
		where C : Control
		where A : _BaseDesignerActionList
	{
		private A actions;
		private Adorner adorner;
		private IDictionary<string, List<Attribute>> redirectedProps;
		private DesignerVerbCollection verbs;

		public override DesignerActionListCollection ActionLists
		{
			get
			{
				if (actions == null)
					actions = Activator.CreateInstance(typeof(A), this, Component) as A;
				return new DesignerActionListCollection(new DesignerActionList[] { actions });
			}
		}

		public new BehaviorService BehaviorService => base.BehaviorService;

		public IComponentChangeService ComponentChangeService { get; private set; }

		public new C Control => (C)base.Control;

		public virtual GlyphCollection Glyphs => Adorner.Glyphs;

		public ISelectionService SelectionService { get; private set; }

		public override DesignerVerbCollection Verbs
		{
			get
			{
				if (verbs == null)
					verbs = this.GetAttributedVerbs();
				return verbs;
			}
		}

		internal Adorner Adorner
		{
			get
			{
				if (adorner == null)
				{
					adorner = new Adorner();
					BehaviorService.Adorners.Add(adorner);
				}
				return adorner;
			}
		}

		protected virtual IEnumerable<string> PropertiesToRemove => new string[0];

		public override void Initialize(IComponent component)
		{
			base.Initialize(component);
			SelectionService = GetService<ISelectionService>();
			if (SelectionService != null)
				SelectionService.SelectionChanged += OnSelectionChanged;
			ComponentChangeService = GetService<IComponentChangeService>();
			if (ComponentChangeService != null)
				ComponentChangeService.ComponentChanged += OnComponentChanged;
		}

		protected override void Dispose(bool disposing)
		{
			if (disposing)
			{
				if (BehaviorService != null)
					BehaviorService.Adorners.Remove(adorner);
				ISelectionService ss = SelectionService;
				if (ss != null)
					ss.SelectionChanged -= OnSelectionChanged;
				IComponentChangeService cs = ComponentChangeService;
				if (cs != null)
					cs.ComponentChanged -= OnComponentChanged;
			}
			base.Dispose(disposing);
		}

		protected virtual S GetService<S>() where S : class => (S)GetService(typeof(S));

		protected virtual void OnComponentChanged(object sender, ComponentChangedEventArgs e)
		{
		}

		protected virtual void OnSelectionChanged(object sender, EventArgs e)
		{
		}

		protected override void PreFilterProperties(System.Collections.IDictionary properties)
		{
			base.PreFilterProperties(properties);

			// RedirectRegisteredProperties
			if (redirectedProps == null)
				redirectedProps = this.GetRedirectedProperties();
			this.RedirectRegisteredProperties(properties, redirectedProps);

			// Remove properties
			this.RemoveProperties(properties, PropertiesToRemove);
		}
	}

	internal abstract class _BaseDesignerActionList : DesignerActionList
	{
		private List<DesignerActionItem> fullAIList;

		public _BaseDesignerActionList(ComponentDesigner designer, IComponent component)
			: base(component)
		{
			base.AutoShow = true;
			ParentDesigner = designer;
		}

		public ComponentDesigner ParentDesigner { get; }

		public override DesignerActionItemCollection GetSortedActionItems()
		{
			// Retrieve all attributed methods and properties
			if (fullAIList == null)
				fullAIList = this.GetAllAttributedActionItems();

			// Filter for conditions and load
			return this.GetFilteredActionItems(fullAIList);
		}

		protected T GetComponentProperty<T>(string propName)
		{
			var p = ComponentProp(propName, typeof(T));
			if (p != null)
				return (T)p.GetValue(Component, null);
			return default(T);
		}

		protected void SetComponentProperty<T>(string propName, T value)
		{
			var p = ComponentProp(propName, typeof(T));
			if (p != null)
				p.SetValue(Component, value, null);
		}

		private Reflection.PropertyInfo ComponentProp(string propName, Type retType) => Component.GetType().GetProperty(propName, ComponentDesignerExtension.AllInstBind, null, retType, Type.EmptyTypes, null);
	}

	internal abstract class RichDesignerActionList<D, C> : _BaseDesignerActionList where D : ComponentDesigner where C : Component
	{
		public RichDesignerActionList(D designer, C component) : base(designer, component)
		{
			ParentDesigner = designer;
		}

		public new C Component => (C)base.Component;

		public new D ParentDesigner { get; }
	}

	internal abstract class RichGlyph<D> : Glyph, IDisposable where D : ControlDesigner
	{
		public RichGlyph(D designer, Behavior.Behavior behavior)
			: base(behavior)
		{
			Designer = designer;
		}

		public D Designer { get; }

		public virtual void Dispose()
		{
		}

		public void SetBehavior(RichBehavior<D> b) { base.SetBehavior(b); }
	}

	internal class RichParentControlDesigner<C, A> : ParentControlDesigner
		where C : Control
		where A : _BaseDesignerActionList
	{
		private A actions;
		private Adorner adorner;
		private IDictionary<string, List<Attribute>> redirectedProps;
		private DesignerVerbCollection verbs;

		public override DesignerActionListCollection ActionLists
		{
			get
			{
				if (actions == null)
					actions = Activator.CreateInstance(typeof(A), this, Component) as A;
				return new DesignerActionListCollection(new DesignerActionList[] { actions });
			}
		}

		public new BehaviorService BehaviorService => base.BehaviorService;

		public IComponentChangeService ComponentChangeService { get; private set; }

		public new C Control => (C)base.Control;

		public virtual GlyphCollection Glyphs => Adorner.Glyphs;

		public ISelectionService SelectionService { get; private set; }

		public override DesignerVerbCollection Verbs
		{
			get
			{
				if (verbs == null)
					verbs = this.GetAttributedVerbs();
				return verbs;
			}
		}

		internal Adorner Adorner
		{
			get
			{
				if (adorner == null)
				{
					adorner = new Adorner();
					BehaviorService.Adorners.Add(adorner);
				}
				return adorner;
			}
		}

		protected virtual IEnumerable<string> PropertiesToRemove => new string[0];

		public override void Initialize(IComponent component)
		{
			base.Initialize(component);
			SelectionService = GetService<ISelectionService>();
			if (SelectionService != null)
				SelectionService.SelectionChanged += OnSelectionChanged;
			ComponentChangeService = GetService<IComponentChangeService>();
			if (ComponentChangeService != null)
				ComponentChangeService.ComponentChanged += OnComponentChanged;
		}

		protected override void Dispose(bool disposing)
		{
			if (disposing)
			{
				if (BehaviorService != null & adorner != null)
					BehaviorService.Adorners.Remove(adorner);
				ISelectionService ss = SelectionService;
				if (ss != null)
					ss.SelectionChanged -= OnSelectionChanged;
				IComponentChangeService cs = ComponentChangeService;
				if (cs != null)
					cs.ComponentChanged -= OnComponentChanged;
			}
			base.Dispose(disposing);
		}

		protected virtual S GetService<S>() where S : class => (S)GetService(typeof(S));

		protected virtual void OnComponentChanged(object sender, ComponentChangedEventArgs e)
		{
		}

		protected virtual void OnSelectionChanged(object sender, EventArgs e)
		{
		}

		protected override void PreFilterProperties(System.Collections.IDictionary properties)
		{
			base.PreFilterProperties(properties);

			// RedirectRegisteredProperties
			if (redirectedProps == null)
				redirectedProps = this.GetRedirectedProperties();
			this.RedirectRegisteredProperties(properties, redirectedProps);

			// Remove properties
			this.RemoveProperties(properties, PropertiesToRemove);
		}
	}
}