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;
using System.Text.RegularExpressions;

namespace nft.framework.plugin
{
	/// <summary>
	/// Utility code
	/// </summary>
	public class PluginUtil
	{
        private static Dictionary<string, Assembly> assemblies = new Dictionary<string,Assembly>();

        /// <summary>
        /// Plugin, Contribution の IDとして適切な文字のみで構成されているかチェックするための正規表現
        /// </summary>
        public static readonly Regex IdCharValidator;

        static PluginUtil()
        {
            IdCharValidator = new Regex(@"^[a-zA-Z0-9_\.\-]+$");
        }

        /// <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["factory-class"];
            if (elm["id"].IsNull) {
                elm.OverWrite("id", "factory#" + elm["name|type"]);
            }
            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 = I18n.T("Failed to create an instance of the class '{0}'.");
                    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 = I18n.T("Failed to create an instance of the class '{0}'.");
                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;
            string s;
            ParamsReader cb = e["codebase"];
			if( cb.IsNull ) {
				// load the class from the FreeTrain.Main.dll
				a = Assembly.GetExecutingAssembly();
                s = a.ManifestModule.Name;
			} else {
				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);
            try
            {
                return a.GetType(typeName, true);
            }
            catch (TypeLoadException te)
            {
                string msg = string.Format("[{1}]にクラス名='{0}'が見つかりませんでした。表記を確認してください。", typeName, s);
                Debug.WriteLine(msg);
                throw new FormatException(msg, te);
            }
		}

        public static Type loadTypeFromManifest(ParamsReader e, Type required) {
            Type t = loadTypeFromManifest(e);
            if (required != null) {
                if (!required.IsAssignableFrom(t)) {
                    string templ = I18n.T("Cannot cast the class'{0}' to the class'{1}'.");
                    object[] a2 = new object[] { t.FullName, required.Name, e.SourceURI };
                    throw new InvalidCastException(string.Format(templ, a2));
                }
            }
            return t;
        }

        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;
        }

	}
}
