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;
using nft.framework.loader;

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, ParamsReader elm) {
            ParamsReader el = elm["class"];
            Type t = loadTypeFromManifest(el);
            object[] args = new object[] { p, elm };
            return (Contribution)loadObjectFromManifest(elm, args, null);
		}

        public static IContributionFactory createCtbFactory(Plugin p, ParamsReader elm) {
            ParamsReader el = elm["class"];
            if (el.IsNull) // 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(ParamsReader elm, object[] args, Type defaultType) {
            ParamsReader el = elm["class"];
            Type t = (el.IsNull) ? defaultType : loadTypeFromManifest(el);
            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(ParamsReader elm, object[] args) {
            ParamsReader el = elm["class"];
            Type t = loadTypeFromManifest(el);
            return CreateInstance(elm, t, args);
        }

        private static object CreateInstance(ParamsReader 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.SourceURI));
                }
                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.SourceURI), 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( ParamsReader e ) {
			string typeName = e["name"].InnerText;
			Assembly a;
            ParamsReader cb = e["codebase"];
			if( cb.IsNull ) {
				// load the class from the FreeTrain.Main.dll
				a = Assembly.GetExecutingAssembly();
			} else {
				string s = cb.InnerText;
				// 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
                IFileSource f = cb.GetFileSource(s);                
				if( f==null )
					throw new FormatException("指定されたコードベースはファイル名ではありません:"+cb.InnerText);
                a = PluginSerializationBinder.SafeLoadAssembly(f);
                
			}
			PluginSerializationBinder.registerAssembly(a);
            //Debug.Assert(typeName != null);
			return a.GetType(typeName,true);
		}

        public static Type loadTypeFromManifest(ParamsReader 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.SourceURI };
                    throw new InvalidCastException(string.Format(templ, a2));
                }
            }
            return t;
        }

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

        [Obsolete("Not available.",true)]
		public static string GetPluginDirName( XmlNode node )
		{
			return Path.GetFileName(Path.GetDirectoryName(node.OwnerDocument.BaseURI));
		}

        public static string GetPluginDirName(ParamsReader node) {
            return Path.GetFileName(node.SourceURI);
        }

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

        public static ParamsReader LoadAnotherXml(ParamsReader source)
        {
            IFileSource fs = source.GetFileSource(source.InnerText);
            XmlDocument doc = XmlUtil.LoadFile(fs);
            XmlParamParser parser = new XmlParamParser(doc);
            ParamsReader reader = new ParamsReader(doc.BaseURI, parser);
            return reader;
        }

	}
}
