using System.Collections.Generic;
using System.Linq.Expressions;
using System;
using System.Linq;
using UnityEngine;

namespace REST_JSON_API
{
    abstract public partial class SOMDoc
    {
        protected static class SOMDocToLambdaExpression<T>
        {
            // TODO: Populate Dictionaries
            #region ExpressionDictionaries

            delegate LambdaExpression CustomFunction(LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params);
            private static readonly Dictionary<string, CustomFunction> MMTtoLambdaMaker = new()
        {
            { MMTConstants.Sin,
                MakeSin },
            { MMTConstants.Cos,
                MakeCos },
            { MMTConstants.SquareRoot,
                MakeCos },
            { MMTConstants.Tuple,
                MakeTupel },
            { MMTConstants.MakeObjectArray,
                MakeObjArray },
            { "InstantList",
                MakeInstantList },
            { MMTConstants.ListEnd,
                MakeListEnd },
            { MMTConstants.ListLiteral,
                InsertFrontListLiteral },
            { MMTConstants.ListType,
                Identity0 },
        };

            private static readonly Dictionary<string, ExpressionType> MMTtoBinaryExpressionType = new()
        {
            { MMTConstants.Add,
                ExpressionType.Add},
            { "AddAssign",
                ExpressionType.AddAssign},
            { "AddAssignChecked",
                ExpressionType.AddAssignChecked},
            { "AddChecked",
                ExpressionType.AddChecked},
            { "And",
                ExpressionType.And},
            { "AndAlso",
                ExpressionType.AndAlso},
            { "AndAssign",
                ExpressionType.AndAssign},
            { "Assign",
                ExpressionType.Assign},
            { MMTConstants.Divide,
                ExpressionType.Divide},
            { "DivideAssign",
                ExpressionType.DivideAssign},
            { "Equal",
                ExpressionType.Equal},
            { "ExclusiveOr",
                ExpressionType.ExclusiveOr},
            { "ExclusiveOrAssign",
                ExpressionType.ExclusiveOrAssign},
            { "GreaterThan",
                ExpressionType.GreaterThan},
            { "GreaterThanOrEqual",
                ExpressionType.GreaterThanOrEqual},
            { "LeftShift",
                ExpressionType.LeftShift},
            { "LeftShiftAssign",
                ExpressionType.LeftShiftAssign},
            { "LessThan",
                ExpressionType.LessThan},
            { "LessThanOrEqual",
                ExpressionType.LessThanOrEqual},
            { "Modulo",
                ExpressionType.Modulo},
            { "ModuloAssign",
                ExpressionType.ModuloAssign},
            { MMTConstants.Multiply,
                ExpressionType.Multiply},
            { "MultiplyAssign",
                ExpressionType.MultiplyAssign},
            { "MultiplyAssignChecked",
                ExpressionType.MultiplyAssignChecked},
            { "MultiplyChecked",
                ExpressionType.MultiplyChecked},
            { "NotEqual",
                ExpressionType.NotEqual},
            { "Or",
                ExpressionType.Or},
            { "OrAssign",
                ExpressionType.OrAssign},
            { "OrElse",
                ExpressionType.OrElse},
            { "Power",
                ExpressionType.Power},
            { "PowerAssign",
                ExpressionType.PowerAssign},
            { "RightShift",
                ExpressionType.RightShift},
            { "RightShiftAssign",
                ExpressionType.RightShiftAssign},
            { MMTConstants.Subtract,
                ExpressionType.Subtract},
            { "SubtractAssign",
                ExpressionType.SubtractAssign},
            { "SubtractAssignChecked",
                ExpressionType.SubtractAssignChecked},
            { "SubtractChecked",
                ExpressionType.SubtractChecked},
        };

            private static readonly Dictionary<string, ExpressionType> MMTtoUnaryExpressionType = new()
        {
            //{ "Constant", // Not Unary
            //    ExpressionType.Constant},
            { "Convert",
                ExpressionType.Convert},
            { "ConvertChecked",
                ExpressionType.ConvertChecked},
            { "Decrement",
                ExpressionType.Decrement},
            { "Increment",
                ExpressionType.Increment},
            { "Negate",
                ExpressionType.Negate},
            { "NegateChecked",
                ExpressionType.NegateChecked},
            { "Not",
                ExpressionType.Not},
            { "OnesComplement",
                ExpressionType.OnesComplement},
            { "PostDecrementAssign",
                ExpressionType.PostDecrementAssign},
            { "PostIncrementAssign",
                ExpressionType.PostIncrementAssign},
            { "PreDecrementAssign",
                ExpressionType.PreDecrementAssign},
            { "PreIncrementAssign",
                ExpressionType.PreIncrementAssign},
            { "UnaryPlus",
                ExpressionType.UnaryPlus},
        };

            #endregion ExpressionDictionaries

            //TODO: case ((f->x)->y) instead of  assumed (f->(x->y))
            public static LambdaExpression MakeLambdaExpression(string URI, LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params)
            {
                void ThrowArgumentException(ExpressionType expression_cast, int expected)
                {
                    throw new ArgumentException(string.Format(
                        "\"Wrong number of Arguments. Required: {2}. Supplied: {3}.\\n\\tFor URI:\\\"{0}\\\"\\n\\tmapped to:\\\"{1}\\\"\"",
                        URI, expression_cast, expected, lambda_applicant.Count()
                    ));
                }

                ParameterExpression[] lambda_params =
                    lambda_applicant
                    .SelectMany(l => l.Parameters)
                    .ToArray(); //PERF: .ToList().Sort() => .BinarySearch; //Too much overhead?
                ParameterExpression[] found_bound_params =
                    bound_params
                    .Where(p => lambda_params.Contains(p))
                    .ToArray();

                if (MMTtoUnaryExpressionType.TryGetValue(URI, out var unnary_type))
                {
                    if (found_bound_params.Count() < 1)
                        ThrowArgumentException(unnary_type, 1);

                    Type UnarySecondArgument = found_bound_params.Count() < 2 ? null : found_bound_params[1].Type;

                    return Expression.Lambda(Expression.MakeUnary(unnary_type, lambda_applicant[0].Body, UnarySecondArgument), found_bound_params);
                }
                else
                if (MMTtoBinaryExpressionType.TryGetValue(URI, out var binary_type))
                {
                    if (lambda_applicant.Count() != 2)
                        ThrowArgumentException(binary_type, 2);

                    return Expression.Lambda(Expression.MakeBinary(binary_type, lambda_applicant[0].Body, lambda_applicant[1].Body), found_bound_params);
                }
                else
                if (MMTtoLambdaMaker.TryGetValue(URI, out var lamda_maker))
                {
                    return lamda_maker(lambda_applicant, lambda_arguments, found_bound_params);
                }
                else
                if (MMTConstants.OMS_TO_TYPE.TryGetValue(URI, out Type type))
                {
                    return Expression.Lambda(Expression.Default(type), null);
                }

                throw new NotImplementedException("Could not map URI: \"" + URI + "\"");
            }

            private static LambdaExpression ExpresionFuncToLambda(LambdaExpression func, string name, LambdaExpression[] args_lamda, ParameterExpression[] bound_params, uint nTargs_fallback)
                => Expression.Lambda(Expression.Invoke(func, args_lamda.Select(l => l.Body)), name, bound_params);

            private static LambdaExpression ParseFuncUUToExpression<U>(Func<U, U> func)
                => (Expression<Func<U, U>>)((U x) => func(x));

            private static LambdaExpression MakeSin(LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params)
                => ExpresionFuncToLambda(
                          lambda_applicant[0].ReturnType == typeof(float) ? ParseFuncUUToExpression<float>(MathF.Sin)
                        : lambda_applicant[0].ReturnType == typeof(double) ? ParseFuncUUToExpression<double>(Math.Sin)
                        : throw new NotImplementedException("Sinus for " + lambda_applicant[0].ReturnType),

                        "Sin", lambda_arguments.Length > 0 ? lambda_arguments : lambda_applicant, bound_params, 1
                    );

            private static LambdaExpression MakeCos(LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params)
                => ExpresionFuncToLambda(
                          lambda_applicant[0].ReturnType == typeof(float) ? ParseFuncUUToExpression<float>(MathF.Cos)
                        : lambda_applicant[0].ReturnType == typeof(double) ? ParseFuncUUToExpression<double>(Math.Cos)
                        : throw new NotImplementedException("Cosinus for " + lambda_applicant[0].ReturnType),

                        "Cos", lambda_arguments.Length > 0 ? lambda_arguments : lambda_applicant, bound_params, 1
                    );

            private static LambdaExpression MakeTupel(LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params)
            {
                if (lambda_applicant.Length == 3
                 && lambda_applicant.All(l => l.ReturnType == typeof(float)))

                    return ExpresionFuncToLambda(
                             (Expression<Func<float, float, float, Vector3>>)((x, y, z) => new Vector3(x, y, z)),
                             "UnityEngineVector3", lambda_applicant, bound_params, 3
                         );

                return MakeObjArray(lambda_applicant, lambda_arguments, bound_params);
            }

            private static LambdaExpression MakeObjArray(LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params)
                => Expression.Lambda(
                        Expression.NewArrayInit(
                            typeof(object),
                            lambda_applicant.Select(l => Expression.Convert(l.Body, typeof(object)))
                        ),
                        bound_params
                    );

            private static LambdaExpression MakeInstantList(LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params)
                => Expression.Lambda(
                        Expression.ListInit(
                            Expression.New(typeof(List<>).MakeGenericType(lambda_applicant[0].ReturnType)),
                            lambda_arguments.Select(l => l.Body) // Expression.Convert(l.Body, lambda_applicant[0].ReturnType))
                        ),
                        bound_params
                    );

            private static LambdaExpression InsertFrontListLiteral(LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params)
                => Expression.Lambda(
                        Expression.Call(
                            lambda_applicant[0].Body,
                            lambda_applicant[0].ReturnType.GetMethod("Insert"),
                            Expression.Constant(0, typeof(int)),
                            lambda_applicant[1].Body
                        ),
                        bound_params
                    );

            private static LambdaExpression MakeListEnd(LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params)
                => Expression.Lambda(
                        Expression.New(typeof(List<>).MakeGenericType(lambda_applicant[0].ReturnType)),
                        null
                    );

            private static LambdaExpression Identity0(LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params)
                => lambda_applicant[0];

        }
    }
}