//
// System.Drawing.Design.ToolboxItem.cs
//
// Authors:
//   Alejandro Sánchez Acosta
//   Andreas Nahr (ClassDevelopment@A-SoftTech.com)
//   Jordi Mas i Hernandez, jordimash@gmail.com
//   Sebastien Pouliot  <sebastien@ximian.com>
//
// (C) Alejandro Sánchez Acosta
// (C) 2003 Andreas Nahr
// Copyright (C) 2004-2006 Novell, Inc (http://www.novell.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//

using System.Collections;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Reflection;
using System.Runtime.Serialization;
using System.Security.Permissions;

namespace System.Drawing.Design 
{
	[Serializable]
	[PermissionSet (SecurityAction.LinkDemand, Unrestricted = true)]
	[PermissionSet (SecurityAction.InheritanceDemand, Unrestricted = true)]
	[MonoTODO ("Implementation is incomplete.")]
	public class ToolboxItem : ISerializable {

		private bool locked = false;
		private Hashtable properties = new Hashtable ();
		
		public ToolboxItem ()
		{
		}

		public ToolboxItem (Type toolType)
		{
			Initialize (toolType);
		}

		public AssemblyName AssemblyName {
			get { return (AssemblyName) properties["AssemblyName"]; }
			set { SetValue ("AssemblyName", value); }
		}

		public Bitmap Bitmap {
			get { return (Bitmap) properties["Bitmap"]; }
			set { SetValue ("Bitmap", value); }
		}

		public string DisplayName {
			get { return GetValue ("DisplayName"); }
			set { SetValue ("DisplayName", value); }
		}

		public ICollection Filter {
			get {
				ICollection filter = (ICollection) properties["Filter"];
				if (filter == null)
					filter = new ToolboxItemFilterAttribute[0];
				return filter;
			}
			set { SetValue ("Filter", value); }
		}
		public virtual bool Locked {
			get { return locked; }
		}

		public string TypeName {
			get { return GetValue ("TypeName"); }
			set { SetValue ("TypeName", value); }
		}
		public string Company {
			get { return (string) properties["Company"]; }
			set { SetValue ("Company", value); }
		}

		public virtual string ComponentType {
			get { return ".NET Component"; }
		}

		public AssemblyName[] DependentAssemblies {
			get { return (AssemblyName[]) properties["DependentAssemblies"]; }
			set {
				AssemblyName[] names = new AssemblyName [value.Length];
				for (int i=0; i < names.Length; i++)
					names [i] = value [i];
				SetValue ("DependentAssemblies", names);
			}
		}

		public string Description {
			get { return (string) properties["Description"]; }
			set { SetValue ("Description", value); }
		}

		public bool IsTransient {			
			get {
				object o = properties ["IsTransient"];
				return (o == null) ? false : (bool) o;
			}
			set { SetValue ("IsTransient", value); }
		}

		public IDictionary Properties {
			 get { return properties; }
		}

		public virtual string Version { 
			get { return string.Empty; }
		}				

		protected void CheckUnlocked ()
		{
			if (locked)
				throw new InvalidOperationException ("The ToolboxItem is locked");
		}

		public IComponent[] CreateComponents () 
		{
			return CreateComponents (null);
		}

		public IComponent[] CreateComponents (IDesignerHost host)
		{
			OnComponentsCreating (new ToolboxComponentsCreatingEventArgs (host));
			IComponent[] Comp = CreateComponentsCore (host);
			OnComponentsCreated (new ToolboxComponentsCreatedEventArgs (Comp));
			return Comp;
		}

		// FIXME - get error handling logic correct
		protected virtual IComponent[] CreateComponentsCore (IDesignerHost host)
		{
			if (host == null)
				throw new ArgumentNullException("host");
			
			IComponent[] components;
			Type type = GetType(host, AssemblyName, TypeName, true);
			if (type == null)
				components = new IComponent[] { };
			else
				components = new IComponent[] { host.CreateComponent(type) };

			return components;
		}

		protected virtual IComponent[] CreateComponentsCore (IDesignerHost host, IDictionary defaultValues)
		{
			IComponent[] components = CreateComponentsCore (host);
			foreach (Component c in components) {
				IComponentInitializer initializer = host.GetDesigner (c) as IComponentInitializer;
				initializer.InitializeNewComponent (defaultValues);
			}
			return components;
		} 

		public IComponent[] CreateComponents (IDesignerHost host, IDictionary defaultValues)
		{
			OnComponentsCreating (new ToolboxComponentsCreatingEventArgs (host));
			IComponent[] components = CreateComponentsCore (host,  defaultValues);
			OnComponentsCreated (new ToolboxComponentsCreatedEventArgs (components));

			return components;
		} 

		protected virtual object FilterPropertyValue (string propertyName, object value)
		{
			switch (propertyName) {
			case "AssemblyName":
				return (value == null) ? null : (value as ICloneable).Clone ();
			case "DisplayName":
			case "TypeName":
				return (value == null) ? String.Empty : value;
			case "Filter":
				return (value == null) ? new ToolboxItemFilterAttribute [0] : value;
			default:
				return value;
			}
		}

		protected virtual void Deserialize (SerializationInfo info, StreamingContext context)
		{			
			AssemblyName = (AssemblyName)info.GetValue ("AssemblyName", typeof (AssemblyName));
			Bitmap = (Bitmap)info.GetValue ("Bitmap", typeof (Bitmap));
			Filter = (ICollection)info.GetValue ("Filter", typeof (ICollection));
			DisplayName = info.GetString ("DisplayName");
			locked = info.GetBoolean ("Locked");
			TypeName = info.GetString ("TypeName");
		}

		// FIXME: too harsh??
		public override bool Equals (object obj)
		{
			ToolboxItem ti = (obj as ToolboxItem);
			if (ti == null)
				return false;
			if (obj == this)
				return true;
			return (ti.AssemblyName.Equals (AssemblyName) &&
				ti.Locked.Equals (locked) &&
				ti.TypeName.Equals (TypeName) &&
				ti.DisplayName.Equals (DisplayName) &&
				ti.Bitmap.Equals (Bitmap));
		}
		
		public override int GetHashCode ()
		{
			// FIXME: other algorithm?
			return string.Concat (TypeName, DisplayName).GetHashCode ();
		}

		public Type GetType (IDesignerHost host)
		{
			return GetType (host, this.AssemblyName,  this.TypeName,  false);
		}

		protected virtual Type GetType (IDesignerHost host, AssemblyName assemblyName, string typeName, bool reference)
		{
			if (typeName == null)
				throw new ArgumentNullException ("typeName");

			if (host == null)
				return null;

			//get ITypeResolutionService from host, as we have no other IServiceProvider here
			ITypeResolutionService typeRes = host.GetService (typeof (ITypeResolutionService)) as ITypeResolutionService;
			Type type = null;
			if (typeRes != null) {
				//TODO: Using Assembly loader to throw errors. Silent fail and return null?
				typeRes.GetAssembly (assemblyName, true);
				if (reference)
					typeRes.ReferenceAssembly (assemblyName);
				type = typeRes.GetType (typeName, true);
			} else {
				Assembly assembly = Assembly.Load (assemblyName);
				if (assembly != null)
					type = assembly.GetType (typeName);
			}
			return type;
		}

		// FIXME - Should we be returning empty bitmap, or null?
		public virtual void Initialize (Type type) 
		{
			CheckUnlocked ();
			if (type == null)
				return;

			AssemblyName = type.Assembly.GetName();
			DisplayName = type.Name;
			TypeName = type.FullName;
			
			// seems to be a right place to create the bitmap
			System.Drawing.Image image = null;
			foreach (object attribute in type.GetCustomAttributes(true)) {
				ToolboxBitmapAttribute tba = attribute as ToolboxBitmapAttribute;
				if (tba != null) {
					image = tba.GetImage (type);
					break;
				}
			}
			//fallback: check for image even if not attribute
			if (image == null)
				image = ToolboxBitmapAttribute.GetImageFromResource (type, null, false);
			
			if (image != null) {
				Bitmap = (image as Bitmap);
				if (Bitmap == null)
					Bitmap = new Bitmap (image);
			}

			Filter = type.GetCustomAttributes (typeof (ToolboxItemFilterAttribute), true);
		}
			
		void ISerializable.GetObjectData (SerializationInfo info, StreamingContext context)
		{
			Serialize (info, context);
		}

		public virtual void Lock () 
		{
			locked = true;
		}

		protected virtual void OnComponentsCreated (ToolboxComponentsCreatedEventArgs args)
		{
			if (ComponentsCreated != null)
				this.ComponentsCreated (this, args);
		}

		protected virtual void OnComponentsCreating (ToolboxComponentsCreatingEventArgs args)
		{
			if (ComponentsCreating != null)
				this.ComponentsCreating (this, args);
		}

		protected virtual void Serialize (SerializationInfo info, StreamingContext context)
		{
			info.AddValue ("AssemblyName", AssemblyName);
			info.AddValue ("Bitmap", Bitmap);
			info.AddValue ("Filter", Filter);
			info.AddValue ("DisplayName", DisplayName);
			info.AddValue ("Locked", locked);
			info.AddValue ("TypeName", TypeName);
		}

		public override string ToString()
		{
			return DisplayName;
		}

		protected void ValidatePropertyType (string propertyName, object value, Type expectedType, bool allowNull)
		{
			if (!allowNull && (value == null))
				throw new ArgumentNullException ("value");

			if ((value != null) && !expectedType.Equals (value.GetType ())) {
				string msg = Locale.GetText ("Type mismatch between value ({0}) and expected type ({1}).",
					value.GetType (), expectedType);
				throw new ArgumentException (msg, "value");
			}
		}

		protected virtual object ValidatePropertyValue (string propertyName, object value)
		{
			switch (propertyName) {
			case "AssemblyName":
				ValidatePropertyType (propertyName, value, typeof (AssemblyName), true);
				break;
			case "Bitmap":
				ValidatePropertyType (propertyName, value, typeof (Bitmap), true);
				break;
			case "Company":
			case "Description":
			case "DisplayName":
			case "TypeName":
				ValidatePropertyType (propertyName, value, typeof (string), true);
				if (value == null)
					value = String.Empty;
				break;
			case "IsTransient":
				ValidatePropertyType (propertyName, value, typeof (bool), false);
				break;
			case "Filter":
				ValidatePropertyType (propertyName, value, typeof (ToolboxItemFilterAttribute[]), true);
				if (value == null)
					value = new ToolboxItemFilterAttribute [0];
				break;
			case "DependentAssemblies":
				ValidatePropertyType (propertyName, value, typeof (AssemblyName[]), true);
				break;
			default:
				break;
			}
			return value;
		}

		private void SetValue (string propertyName, object value)
		{
			CheckUnlocked ();
			properties [propertyName] = ValidatePropertyValue (propertyName, value);
		}
		private string GetValue (string propertyName)
		{
			string s = (string) properties [propertyName];
			return (s == null) ? String.Empty : s;
		}

		public event ToolboxComponentsCreatedEventHandler ComponentsCreated;

		public event ToolboxComponentsCreatingEventHandler ComponentsCreating;
	}
}