using System;
using System.Linq;
using Irony.Parsing;
using Irony.Interpreter;
using System.Reflection;
using Gecko;

namespace scriptTestDLL
{

    public class MyScriptEvaluatorRuntime : LanguageRuntime
    {

        public MyScriptEvaluatorRuntime(LanguageData language)
            : base(language)
        {
        }

        public override void Init()
        {
            base.Init();
            //add built-in methods, special form IIF, import Math and Environment methods
            BuiltIns.AddMethod(BuiltInPrintMethod , "print");
            BuiltIns.AddMethod(BuiltInFormatMethod , "format");
            BuiltIns.AddSpecialForm(SpecialFormsLibrary.Iif , "iif" , 3 , 3);
            BuiltIns.ImportStaticMembers(typeof(System.Math));
            BuiltIns.ImportStaticMembers(typeof(Environment));
            //追加の初期化をおこなう
            BuiltIns.AddMethod(BuiltInLoadFuncFromDLLMethod , "loaddll");

        }



        //Built-in methods
        private object BuiltInPrintMethod(ScriptThread thread , object[] args)
        {
            string text = string.Empty;
            switch (args.Length)
            {
                case 1:
                    text = string.Empty + args[0]; //compact and safe conversion ToString()
                    break;
                case 0:
                    break;
                default:
                    text = string.Join(" " , args);
                    break;
            }
            thread.App.WriteLine(text);
            return null;
        }
        private object BuiltInFormatMethod(ScriptThread thread , object[] args)
        {
            if (args == null || args.Length == 0) return null;
            var template = args[0] as string;
            if (template == null)
                this.ThrowScriptError("Format template must be a string.");
            if (args.Length == 1) return template;
            //create formatting args array
            var formatArgs = args.Skip(1).ToArray();
            var text = string.Format(template , formatArgs);
            return text;

        }




        private object BuiltInLoadFuncFromDLLMethod(ScriptThread thread , object[] args)
        {
            //DLLパス、クラス名が指定されてないときは何もしない
            if (args.Length != 2)
                return null;

            //一番目がDLLパス、二番目がフルクラス名（ネームスペース＋クラス名）
            var fileName = args[0] as string;
            var className = args[1] as string;

            object retClass = null;

            //アセンブリ読み込み
            var asm = Assembly.LoadFile(fileName);
            //クラスをすべて読み込む
            foreach (Type TargetClass in asm.GetTypes())
            {
                if (TargetClass.FullName == className)
                {

                    //デフォルトコンストラクタであればこれでOK（引数なし）
                    // BuiltInMethod.Class1 class1 = (BuiltInMethod.Class1)asm.CreateInstance("BuiltInMethod.Class1");
                    //ここの指定を参考にしました。。
                    //http://www.atmarkit.co.jp/fdotnet/dotnettips/854asmcreateinstance/asmcreateinstance.html
                    //retClass =
                    //    asm.CreateInstance(TargetClass.FullName ,
                    //                        true ,
                    //                        BindingFlags.CreateInstance ,
                    //                        null ,
                    //                        new object[] { /*this.Browser , this.frm as System.Windows.Forms.Form */ } ,
                    //                        null ,
                    //                        null
                    //                        );
                    retClass = asm.CreateInstance(TargetClass.FullName , true);
                    break;
                }
            }//foreach

            //見つけられなかったら、何もしない
            if (retClass == null)
                return null;

            //クラスメソッドを取り出して、スクリプトのテーブルに追加する
            //メソッドの返値、引数の型をチェックする
            foreach (var m in retClass.GetType().GetMethods(/*BindingFlags.Public | BindingFlags.NonPublic |
                                                    BindingFlags.Instance | BindingFlags.Static |
                                                    BindingFlags.DeclaredOnly*/))
            {
                //返値がobjectでなければ追加しない。
                if (m.ReturnType != typeof(object))
                    continue;

                //引数を確認
                ParameterInfo[] prms = m.GetParameters();
                if (prms.Length != 2) continue;

                if (prms[0].ParameterType == typeof(ScriptThread) || prms[1].ParameterType == typeof(object[]))
                {
                    //２番目のパラメーターは、メソッドに渡される隠しパラメーターで、thisで参照されるもの。
                    Irony.Interpreter.BuiltInMethod method = (Irony.Interpreter.BuiltInMethod)Delegate
                                        .CreateDelegate(typeof(Irony.Interpreter.BuiltInMethod) , retClass , m);
                    //インタプリタに取り込む
                    BuiltIns.AddMethod(method , m.Name);
                }
            }//foreach

            return retClass;
        }//method

    }//class

}
