using System;
using System.IO;
using System.Diagnostics;
using System.Drawing;
using System.Reflection;
using System.Xml;
using System.Collections.Generic;
using nft.util;
using nft.ui.command;

namespace nft.framework.plugin
{
	/// <summary>
	/// Utility code
	/// </summary>
	public class PluginUtil
	{
        private static Dictionary<string, Assembly> assemblies = new Dictionary<string,Assembly>();
        
        /// <summary>
		/// Load a new Contribution by reading a type from the manifest XML element.
		/// The "codeBase" attribute and the "name" attribute of
		/// a class element are used to determine the class to be loaded.
		/// </summary>
        /// <param name="p">owner plugin</param>
        /// <param name="contrib">contribution target node</param>
        public static Contribution createContributionObject(Plugin p, XmlElement elm) {
            XmlElement el = (XmlElement)XmlUtil.SelectSingleNode(elm, "class");
            Type t = loadTypeFromManifest(el);
            object[] args = new object[] { p, elm };
            return (Contribution)loadObjectFromManifest(elm, args, null);
		}

        public static IContributionFactory createCtbFactory(Plugin p, XmlElement elm) {
            XmlElement el = (XmlElement)elm.SelectSingleNode("class");
            if (el == null) // should use default factory.
                return new DefaultContributionFactory(p, elm);
            Type t = loadTypeFromManifest(el);
            object[] args = t.IsSubclassOf(typeof(Contribution)) ?
                new object[] { p, elm } : new object[] { elm };
            IContributionFactory fac = (IContributionFactory)CreateInstance(elm, t, args);
            return fac;
        }

		/// <summary>
		/// Load an any new object by reading a type from the manifest XML element.
		/// The "codeBase" attribute and the "name" attribute of
		/// a class element are used to determine the class to be loaded.
		/// </summary>
        /// <param name="elm">contribution target node</param>
        /// <param name="args">arguments array for constructor</param>
        /// <param name="defaultType">if 'elm' has no "class" element, use as creation type 
        /// (can e null)</param>
        public static object loadObjectFromManifest(XmlElement elm, object[] args, Type defaultType) {
            XmlElement el = elm.SelectSingleNode("class") as XmlElement;
            Type t = (el != null) ? loadTypeFromManifest(el) : defaultType;
            return CreateInstance(elm, t, args);
        }

        /// <summary>
        /// Load an any new object by reading a type from the manifest XML element.
        /// The "codeBase" attribute and the "name" attribute of
        /// a class element are used to determine the class to be loaded.
        /// </summary>
        /// <param name="elm">contribution target node</param>
        /// <param name="args">arguments array for constructor</param>
        public static object loadObjectFromManifest(XmlElement elm, object[] args) {
            XmlElement el = XmlUtil.SelectSingleNode(elm, "class") as XmlElement;
            Type t = loadTypeFromManifest(el);
            return CreateInstance(elm, t, args);
        }

        private static object CreateInstance(XmlElement elm, Type t, object[] args) {
            try {
                // give XmlNode as first argument of constructor.
                object result = Activator.CreateInstance(t, args);
                if (result == null) {
                    string templ = Main.resources["xml.class_load_error"].stringValue;
                    throw new Exception(string.Format(templ, t.FullName, elm.OwnerDocument.BaseURI));
                }
                return result;
            } catch (TargetInvocationException e) {
                Debug.WriteLine(e.Message);
                Debug.WriteLine(e.StackTrace);
                string templ = Main.resources["xml.class_load_error"].stringValue;
                throw new Exception(string.Format(templ, t.FullName, elm.OwnerDocument.BaseURI), e);
            }
        }
		
		/// <summary>
		/// Load a type from the name attribute and the codebase attribute .
		/// </summary>
		/// <param name="e">Typically a "class" element</param>
		public static Type loadTypeFromManifest( XmlElement e ) {
			string typeName = e.Attributes["name"].Value;
			Assembly a;			
			if( e.Attributes["codebase"]==null ) {
				// load the class from the FreeTrain.Main.dll
				a = Assembly.GetExecutingAssembly();
			} else {
				string s = e.Attributes["codebase"].Value;
				// if start with '.', it's abbreviation of nft.XXX.dll 
                // at the application base directory.
				if(s.StartsWith("."))
					s = string.Format("{0}NFT{1}.DLL",Directories.AppBaseDir,s);
				// load the class from the specified assembly
				Uri codeBase = XmlUtil.resolve( e, s );

				if( !codeBase.IsFile )
					throw new FormatException("指定されたコードベースはファイル名ではありません:"+codeBase);

				a = Assembly.LoadFrom( codeBase.LocalPath );
			}
			PluginSerializationBinder.registerAssembly(a);
			return a.GetType(typeName,true);
		}

        public static Type loadTypeFromManifest(XmlElement e, Type required) {
            Type t = loadTypeFromManifest(e);
            if (required != null) {
                if (!required.IsAssignableFrom(t)) {
                    string templ = Main.resources["xml.class_cast_error"].stringValue;
                    object[] a2 = new object[] { t.FullName, required.Name, e.BaseURI };
                    throw new InvalidCastException(string.Format(templ, a2));
                }
            }
            return t;
        }

		public static void RegisterCommand( string id, ICommandEntity_Old entity, XmlNode commandNode )
		{
			Debug.Assert(entity!=null&&id!=null&&commandNode!=null,"Invalid Command is going to regist!");
			string pid = PluginUtil.GetPruginDirName(commandNode);
			string bar = XmlUtil.GetAttribute(commandNode,"toolbar","MAIN");
			string bid = XmlUtil.GetAttribute(commandNode,"button",null);
			string mpath = XmlUtil.GetAttribute(commandNode,"menupath",null);
			if( bid != null )
				Main.mainFrame.SetToolButtonCommand(id,entity,bar,bid);
			if( mpath != null )
				Main.mainFrame.SetMenuCommand(id,entity,mpath);
		}

		public static string GetPruginDirName( XmlNode node )
		{
			return Path.GetFileName(Path.GetDirectoryName(node.OwnerDocument.BaseURI));
		}

		public static string GetPruginFullPath( XmlNode node )
		{
			return Path.GetDirectoryName(node.OwnerDocument.BaseURI);
		}

	}
}
