using System;
using System.Text;
using System.Reflection;
using System.Collections.Generic;
using nft.util;
using System.Diagnostics;
using System.Windows.Forms;
using System.Threading.Tasks;

namespace nft.framework
{
    public class TestUtil
    {
        protected const BindingFlags flag = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
        protected static ProgressMonitor monitor = new ProgressMonitor(1);

        public static IEnumerable<Type> EnumTestEntryTypes(Assembly assembly) {
            foreach (Type type in assembly.GetTypes()) {
                if (type.GetCustomAttributes(typeof(TestEntryAttribute), true).Length > 0) {
                    yield return type;
                }
            }
        }

        public static IEnumerable<TestInfo> EnumTestEntries(Assembly assembly) {
            Type[] tarr = assembly.GetTypes();
            monitor.SetMaximum(1, tarr.Length);
            foreach (Type type in tarr) {
                monitor.Progress(1,1,type.Name);
                foreach (MethodInfo minf in type.GetMethods(flag)) {
                    object[] arr = minf.GetCustomAttributes(typeof(TestEntryAttribute), true);
                    if (arr.Length == 1) {
                        yield return new TestInfo(minf, arr[0] as TestEntryAttribute);
                    } else {
                        int c = 0;
                        foreach (object o in arr) {
                            yield return new TestInfo(minf, o as TestEntryAttribute, c++);
                        }
                    }
                }
            }
        }

        public static ProgressMonitor ProgressMonitor {
            get { return monitor; }
        }

        [TestEntry(new object[]{"あああ", 123, 2.0f})]
        //[TestEntry(new object[] { "いいい", 1 })] // Argument Count Mismatch
        //[TestEntry("Test2", new object[] { "ううう", "x", 0.1f })] // Argument Type Mismatch
        //[TestEntry(new object[] { "えええ", 123, float.NaN })] // Cause TargetInvocationException
        private static bool TestOfTestUtil(string arg1, int arg2, float arg3) {
            float r = arg3 * arg2;
            if (float.IsNaN(r)) throw new Exception("Wrong result!");
            Debug.WriteLine(String.Format("Test called: arg text={0} arg arg2 x arg3={1}",arg1,r));
            return true;
        }
    }

    public enum TestState { NotTested, Success, Failed, InvokeError }

    public class TestInfo
    {
        public readonly MethodInfo Method;
        public readonly TestEntryAttribute TestAttribute;
        public readonly String ID;
        public TestState _state;
        public TestInfo(MethodInfo mi, TestEntryAttribute a) : this(mi, a, -1){
        }
        public TestInfo(MethodInfo mi, TestEntryAttribute a, int idx) {
            this._state = TestState.NotTested;
            this.Method = mi;
            this.TestAttribute = a;
            string str =  mi.DeclaringType.Name + "#" + mi.Name;
            if(idx>0){
                str += "<" + idx + ">";
            }
            this.ID = str;
            if (a.Caption == null) {
                a.Caption = this.ID;
            }
        }

        public object LastResult = null;

        public object Run() {
            try {
                LastResult = this.Method.Invoke(null, TestAttribute.Arguments);
                if (LastResult is Task) ((Task)LastResult).Wait();
                _state = TestState.Success;
            } catch (TargetInvocationException ex) {
                _state = TestState.Failed;
                LastResult = ex.GetBaseException();
            } catch (Exception ex2){
                _state = TestState.InvokeError;
                string emsg = ex2.Message;
                if (ex2 is ArgumentException || ex2 is TargetParameterCountException) {
                    emsg = "Argument Mismatch 変数指定ミス\n\r" + emsg;
                } else if (ex2 is TargetException) {
                    emsg = "Non static method 静的メソッドではないと実行できません\n\r" + emsg;
                } else if (ex2 is TargetException) {
                    emsg = ex2.GetType().Name+ "\n\r" + emsg;
                }
                string msg = String.Format("{0}\n\r{1}", emsg, Dump());
                MessageBox.Show(msg, "ERROR: Invalid TestEntry.");
            }
            return LastResult;
        }

        public TestState TestState {
            get { return _state; }
        }

        public bool IsError {
            get { return _state == TestState.Failed || _state == TestState.InvokeError; }
        }

        public string Dump() {
            StringBuilder sb = new StringBuilder(ID);
            if (!ID.Equals(TestAttribute.Caption)) {
                sb.AppendFormat(" [ {0} ]", TestAttribute.Caption);
            }
            sb.AppendLine();
            sb.Append(DumpArguments());
            return sb.ToString();
        }

        public string DumpArguments() {
            StringBuilder sb = new StringBuilder();
            object[] args = TestAttribute.Arguments;
            if (args != null && args.Length > 0) {
                sb.Append("{ ");
                sb.Append(args[0].ToString());
                for (int i = 1; i < args.Length; i++) {
                    object o = args[i];
                    sb.Append(",");
                    sb.Append(o.ToString());
                }
                sb.Append(" }");
            } else {
                sb.Append("- No Arguments -");
            }
            return sb.ToString();
        }
    }
}
