using System;
using System.Collections.Generic;
using System.Reflection;
using System.Dynamic;
using System.Linq;

using Z.Expressions;
using Newtonsoft.Json;
using System.Text;

// https://eval-expression.net/
// https://eval-expression.net/online-examples

namespace TopNugetPackages
{
    public class MainMetaProgrammingDriver
    {
        public class ConcreteType
        {
            public int ID { get; set; }
        }

        private void TestSimple()
        {
            // Parameter: Anonymous Type
            int result = Eval.Execute<int>("X + Y", new { X = 1, Y = 6 });

            Console.WriteLine(result);

            // Parameter: Argument Position
            result = Eval.Execute<int>("{0} + {1}", 1, 8);

            Console.WriteLine(result);

            // Parameter: Class Member
            dynamic expandoObject = new ExpandoObject();
            expandoObject.X = 1;
            expandoObject.Y = 9;
            result = Eval.Execute<int>("X + Y", expandoObject);

            Console.WriteLine(result);

            // Parameter: Dictionary Key
            var values = new Dictionary<string, object>() { { "X", 4 }, { "Y", 2 } };
            result = Eval.Execute<int>("X + Y", values);

            Console.WriteLine(result);
        }

        private void TestComplex()
        {
            try
            {
                List<string> expressions = new List<string>()
                {
                    "Math.Pow(2, 3)",
                    "Math.pOW(2, 3)"
                };

                var context = new EvalContext();
                context.UseCache = false;
                foreach (var expression in expressions)
                {
                    var result = context.Execute(expression);
                    Console.WriteLine("{0}: {1}", expression, result);
                }

                Console.WriteLine("\nAfter context.IsCaseSensitive = false; \n");

                context.IsCaseSensitive = true;

                foreach (var expression in expressions)
                {
                    var result = context.Execute(expression);
                    Console.WriteLine("{0}: {1}", expression, result);
                }

                Console.WriteLine("\nAfter context.BindingFlags = BindingFlags.IgnoreCase; \n");

                context.BindingFlags = BindingFlags.Public;

                foreach (var expression in expressions)
                {
                    var result = context.Execute(expression);
                    Console.WriteLine("{0}: {1}", expression, result);
                }

            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }
        }

        // demonstrate new object creation and assignment
        private void TestConcreteType()
        {
            EvalManager.DefaultContext.RegisterType(typeof(ConcreteType));

            var list = new List<int>() { 1, 2, 3, 4, 5 };

            var list2 = list.SelectDynamic(x => "new ConcreteType() { ID = x }").Cast<ConcreteType>().ToList();

            var json = JsonConvert.SerializeObject(list2, Formatting.Indented);
            Console.WriteLine(json);
        }

        // demonstrate list of meta-program
        private void TestMyMetaExpression()
        {
            try
            {
                var exprs = new List<string>()
                {
                    "Math.Pow(2, 3)",
                    "Math.Max(3, 9)",
                    "Console.WriteLine(\"Hello World\")",
                };

                var context = new EvalContext() { UseCache = false };

                foreach (var expr in exprs)
                {
                    var result = context.Execute(expr);
                    Console.WriteLine("{0}: {1}", expr, result);
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }
        }

        // demonstrate simple if-else conditional statements
        private void TestMetaIsPosOrNeg(int num)
        {
            try
            {
                var context = new EvalContext() { UseCache = false };
                var metacode = new StringBuilder();

                metacode.Append($"int x = {num};");
                metacode.Append("if (x > 0)");
                metacode.Append("{ Console.WriteLine($\"POSITIVE. X = {x}\"); }");
                metacode.Append("else");
                metacode.Append("{ Console.WriteLine($\"NEGATIVE. X = {x}\"); }");

                var result = context.Execute(metacode.ToString());
                Console.WriteLine(result);
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }
        }

        // Demonstrate meta-programming with calculating factorial and returning the result
        private long TestMetaFactorial(int number)
        {
            try
            {
                var context = new EvalContext() { UseCache = false };
                var metacode = new StringBuilder();

                metacode.Append($"long fact = 1;");
                metacode.Append($"int numb = {number};");
                metacode.Append("Console.WriteLine($\"Calculating Factorial of {numb}.\");");

                metacode.Append("for (int i = 1; i <= numb; i++) {");
                metacode.Append("    fact = fact * i;");
                metacode.Append("}");

                metacode.Append("Console.WriteLine($\"{numb}! = {fact}.\");");
                metacode.Append("return fact;");

                var result = context.Execute(metacode.ToString());                
                Console.WriteLine($"Return value is: {result}\r\n");

                return Convert.ToInt64(result);
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }
            return 0;
        }

        public void DoIt()
        {
            TestSimple();
            TestComplex();
            TestConcreteType();
            TestMyMetaExpression();

            TestMetaIsPosOrNeg(2);
            TestMetaIsPosOrNeg(-12);
            TestMetaIsPosOrNeg(8);
            TestMetaIsPosOrNeg(-27);
            TestMetaIsPosOrNeg(28);
            TestMetaIsPosOrNeg(-81);

            TestMetaFactorial(3);
            TestMetaFactorial(5);
            TestMetaFactorial(12);
        }
    }
}