using System;
using System.Linq;
using Irony.Parsing;
using Irony.Interpreter;
using System.Reflection;
using System.Threading;
using System.Collections.Generic;
using System.Windows.Forms;
namespace ScriptRunner
{




    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.ImportStaticMembers(typeof(System.Windows.Forms.Application)); 
            BuiltIns.AddMethod(BuiltInLoadFuncFromDLLMethod , "loaddll");
            //BuiltIns.AddMethod(run , "run");
            BuiltIns.AddMethod(BuiltInEvalMethod , "eval");
            BuiltIns.AddMethod(Debug_GetGlobal_Symbols , "getglobalsymbols");
            BuiltIns.AddMethod(Debug_GetLocal_Symbols , "getlocalsymbols");
            
            //BuiltIns.AddMethod(BuiltInGetOwner, "GetOwner");
        }



        //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 BuiltInEvalMethod(ScriptThread thread , object[] args)
        {
            object result = null;
            
            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;
            }

            //パース
            var parseTree = thread.App.Parser.Parse(text);
            //実行
            result = thread.App.Evaluate(parseTree , thread.CurrentScope);

            return result;
        }



        private object[] BuiltInLoadFuncFromDLLMethod(ScriptThread thread, object[] args)
        {
            Dictionary<string, Irony.Interpreter.BuiltInMethod> methodlist = new Dictionary<string, Irony.Interpreter.BuiltInMethod>();

            //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.LoadFrom(fileName);
            //クラスをすべて読み込む
            foreach(Type TargetClass in asm.GetTypes())
            {
                if (TargetClass.FullName == className)
                {

                    //デフォルトコンストラクタであればこれでOK（引数なし）
                    // BuiltInMethod.Class1 class1 = (BuiltInMethod.Class1)type.CreateInstance("BuiltInMethod.Class1");
                    //ここの指定を参考にしました。。
                    //http://www.atmarkit.co.jp/fdotnet/dotnettips/854asmcreateinstance/asmcreateinstance.html
                    //retClass =
                    //    type.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);

                    //重複していたら削除する。
                    if(BuiltIns.ContainsKey(m.Name))
                        BuiltIns.Remove(m.Name);

                    if(methodlist.ContainsKey(m.Name))
                        methodlist.Remove(m.Name);

                    //インタプリタに取り込む
                    BuiltIns.AddMethod(method, m.Name);
                    methodlist.Add(m.Name, (Irony.Interpreter.BuiltInMethod)method);
                }
            }//foreach

            return new object[] { retClass, methodlist };
        }//method

        //public class insAndMethodlist
        //{
        //    public Dictionary<string, object> _methodlist { get; set; }
        //    public object _retclass { get; set; }

        //    public insAndMethodlist(Dictionary<string, object> methodlist , object retclass)
        //    {
        //        _methodlist = methodlist;
        //        _retclass = retclass;
        //    }

        //}




        public class SymbolInfo
        {
            public string symbol { get; set; }
            public Type type { get; set; }
            public object value { get; set; }
            public int level { get; set; }
        }

        private List<SymbolInfo> Debug_GetGlobal_Symbols(ScriptThread thread , object[] args)
        {
            var result = new List<SymbolInfo>();
            foreach (var info in thread.App.DataMap.StaticScopeInfos)
                getSlotInfo(thread , result , info);
            return result;
        }

        private List<SymbolInfo> Debug_GetLocal_Symbols(ScriptThread thread , object[] args)
        {
            var result = new List<SymbolInfo>();
            getSlotInfo(thread , result , thread.CurrentScope.Info);
            return result;
        }
        
        private static void getSlotInfo(ScriptThread thread , List<SymbolInfo> result , ScopeInfo info)
        {
            foreach (var sl in info.GetSlots())
            {
                //この３つはリテラルぽい扱い
                if (sl.Name == "null" || sl.Name == "false" || sl.Name == "true")
                    continue;

                var item = new SymbolInfo();
                item.symbol = sl.Name;
                //item.type = sl.ScopeInfo.OwnerNode.AsString; //sl.Type;
                item.level = info.Level;
                
                var temp = thread.Bind(sl.Name , BindingRequestFlags.Read);
                
                //値が設定されていない場合
                if (temp.GetValueRef == null)
                    item.value = null;
                else
                {
                    item.value = temp.GetValueRef(thread);
                }
                result.Add(item);
            }
        }


        //private static object run(ScriptThread thread, object[] args)
        //{
        //    //System.Windows.Forms.Application.EnableVisualStyles();
        //    //System.Windows.Forms.Application.SetCompatibleTextRenderingDefault(false);
        //    System.Windows.Forms.Application.Run(args[0] as System.Windows.Forms.Form);
        //    return null;
        //}


        //private object BuiltInGetOwner(ScriptThread thread, object[] args)
        //{
        //    object objs = null;
        //    if(thread.App.Globals.TryGetValue("parentwindow", out objs))
        //        return objs;
        //    else
        //        return null;



        //}

    }//class

}
