using System;
using System.Linq;
//using System.Windows.Forms;
using System.Collections.Generic;
using myBuiltInMethod;
using System.Reflection;

namespace ScriptRunner
{

    /// <summary>
    /// 属性でマークされた
    /// インターフェースクラスのメソッドを
    /// スクリプト用の組み込みメソッドとして登録する
    /// </summary>
    public class ExportMethodBase
    {

        protected Dictionary<string, Irony.Interpreter.BuiltInMethod> methodlist;
        protected Type InterfaseClass;
        protected object LibInstance;

        public ExportMethodBase(Type interfacesClass)//, object LibInstance
        {
            this.InterfaseClass = interfacesClass;
            this.LibInstance = Activator.CreateInstance(interfacesClass); //LibInstance;
        }


        public virtual Dictionary<string, Irony.Interpreter.BuiltInMethod> CheckAttrAndExport()
        {
            methodlist = new Dictionary<string, Irony.Interpreter.BuiltInMethod>();
            foreach (var method in InterfaseClass.GetMethods())
	        {
                var att = Attribute.GetCustomAttribute(method, typeof(ExportMethodAttribute)) as ExportMethodAttribute;
                if(att != null)
                    methodlist.Add(method.Name, makeMethod(method));
            }
            return methodlist;
        }

        /// <summary>
        /// Irony.Interpreter.BuiltInMethod　デリゲートのインスタンスを作成して返す
        /// </summary>
        /// <param name="info">インターフェースクラスのメソッド</param>
        /// <returns></returns>
        protected virtual Irony.Interpreter.BuiltInMethod makeMethod(MethodInfo info)
        {
            //メソッド呼び出し用のBindingFlags
            BindingFlags bf = BindingFlags.InvokeMethod
                            | BindingFlags.Public
                            | BindingFlags.NonPublic
                            | BindingFlags.Instance
                            | BindingFlags.IgnoreCase
                            | BindingFlags.OptionalParamBinding;

            Irony.Interpreter.BuiltInMethod method = (Irony.Interpreter.ScriptThread thread, object[] args) =>
                {
                    //argsの数をチェック。オプション引数が省略されているかもしれないので考慮。
                    //
                    int n = 0;
                    foreach(var p in info.GetParameters())
                    {
                        if(p.IsOptional) n++;
                    }
                    if(args.Length < info.GetParameters().Length - n)
                        throw new ArgumentException(
                            string.Format("引数の数が足りません　{0} {1}", info.Name, info.GetParameters().Length));

                    //パラメータの既定値が設定されたメソッドを呼び出すには、InvokeMember を使用します。
                    //これらのメソッドをバインドするには、リフレクションでは BindingFlags.OptionalParamBinding を指定することが必要です。
                    //既定値を持つパラメータに対しては、別の値を指定するか、または Missing.Value を提供して既定値を使用できます。

                    object[] ar = new object[info.GetParameters().Length];
                    args.CopyTo(ar, 0);

                    //引数省略パターン  Missing.Valueで埋める
                    if(args.Length < info.GetParameters().Length && n > 0)
                    {
                        for(int i = 0; i < n; i++)
                            ar[args.Length + i] = Missing.Value;
                    }

                    return InterfaseClass.InvokeMember(info.Name,
                                                    bf,
                                                    null,
                                                    LibInstance,
                                                    ar ,
                                                    null ,
                                                    null ,
                                                    null);
                };
            return method;


        }//Method


    }//class

    /// <summary>
    /// メソッド実行後に追加処理を呼び出しする属性　に対応
    /// </summary>
    public class ExportMethod2 : ExportMethodBase
    {
        public delegate void ExtMethoddel();
        Type ExtendAttribute;
        Delegate ExtendDelegate;

        public ExportMethod2(Type interfacesClass, Type ExtendAttribute = null, Delegate ExtendDelegate = null)
            : base(interfacesClass)// , LibInstance
        {
            this.ExtendAttribute = ExtendAttribute;
            this.ExtendDelegate = ExtendDelegate;
        }


        public override Dictionary<string, Irony.Interpreter.BuiltInMethod> CheckAttrAndExport()
        {
            methodlist = new Dictionary<string, Irony.Interpreter.BuiltInMethod>();
            foreach(var method in InterfaseClass.GetMethods())
            {
                var att = Attribute.GetCustomAttribute(method, typeof(ExportMethodAttribute)) as ExportMethodAttribute;
                if(att != null)
                    methodlist.Add(method.Name, makeMethod(method));
            }
            return methodlist;
        }

        protected override Irony.Interpreter.BuiltInMethod makeMethod(MethodInfo info)
        {
            //メソッド呼び出し用のBindingFlags
            BindingFlags bf = BindingFlags.InvokeMethod
                            | BindingFlags.Public
                            | BindingFlags.NonPublic
                            | BindingFlags.Instance
                            | BindingFlags.IgnoreCase
                            | BindingFlags.OptionalParamBinding;

            var att = Attribute.GetCustomAttribute(info, ExtendAttribute);
            
            
            Irony.Interpreter.BuiltInMethod ret = (Irony.Interpreter.ScriptThread thread, object[] args) =>
            {
                //argsの数をチェック。オプション引数が省略されているかもしれないので考慮。
                //
                int n = 0;
                foreach(var p in info.GetParameters())
                {
                    if(p.IsOptional) n++;
                }
                if(args.Length < info.GetParameters().Length - n)
                    throw new ArgumentException(
                        string.Format("引数の数が足りません　{0} {1}", info.Name, info.GetParameters().Length));

                //パラメータの既定値が設定されたメソッドを呼び出すには、InvokeMember を使用します。
                //これらのメソッドをバインドするには、リフレクションでは BindingFlags.OptionalParamBinding を指定することが必要です。
                //既定値を持つパラメータに対しては、別の値を指定するか、または Missing.Value を提供して既定値を使用できます。

                object[] ar = new object[info.GetParameters().Length];
                args.CopyTo(ar, 0);

                //引数省略パターン  Missing.Valueで埋める
                if(args.Length < info.GetParameters().Length && n > 0)
                {
                    for(int i = 0; i < n; i++)
                        ar[args.Length + i] = Missing.Value;
                }

                var retObj =  InterfaseClass.InvokeMember(info.Name,
                                                bf,
                                                null,
                                                LibInstance,
                                                ar,
                                                null,
                                                null,
                                                null);
                //追加処理
                if(att != null && ExtendDelegate != null)
                {
                    ExtendDelegate.DynamicInvoke(null);
                    System.Diagnostics.Debug.WriteLine("Extmethod called ");
                }

                return retObj;

            };
            return ret;


        }//Method


    }//class

}


