// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.

using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using Xunit;

namespace System.Web.Mvc.ExpressionUtil.Test
{
    public class CachedExpressionCompilerTest
    {
        private delegate Func<TIn, TOut> Compiler<TIn, TOut>(Expression<Func<TIn, TOut>> expr);

        [Fact]
        public void Compiler_CompileFromConstLookup()
        {
            // Arrange
            Expression<Func<string, int>> expr = model => 42;
            var compiler = GetCompilerMethod<string, int>("CompileFromConstLookup");

            // Act
            var func = compiler(expr);
            int result = func("any model");

            // Assert
            Assert.Equal(42, result);
        }

        [Fact]
        public void Compiler_CompileFromFingerprint()
        {
            // Arrange
            Expression<Func<string, int>> expr = s => 20 * s.Length;
            var compiler = GetCompilerMethod<string, int>("CompileFromFingerprint");

            // Act
            var func = compiler(expr);
            int result = func("hello");

            // Assert
            Assert.Equal(100, result);
        }

        [Fact]
        public void Compiler_CompileFromIdentityFunc()
        {
            // Arrange
            Expression<Func<string, string>> expr = model => model;
            var compiler = GetCompilerMethod<string, string>("CompileFromIdentityFunc");

            // Act
            var func = compiler(expr);
            string result = func("hello");

            // Assert
            Assert.Equal("hello", result);
        }

        [Fact]
        public void Compiler_CompileFromMemberAccess_CapturedLocal()
        {
            // Arrange
            string capturedLocal = "goodbye";
            Expression<Func<string, string>> expr = _ => capturedLocal;
            var compiler = GetCompilerMethod<string, string>("CompileFromMemberAccess");

            // Act
            var func = compiler(expr);
            string result = func("hello");

            // Assert
            Assert.Equal("goodbye", result);
        }

        [Fact]
        public void Compiler_CompileFromMemberAccess_ParameterInstanceMember()
        {
            // Arrange
            Expression<Func<string, int>> expr = s => s.Length;
            var compiler = GetCompilerMethod<string, int>("CompileFromMemberAccess");

            // Act
            var func = compiler(expr);
            int result = func("hello");

            // Assert
            Assert.Equal(5, result);
        }

        [Fact]
        public void Compiler_CompileFromMemberAccess_StaticMember()
        {
            // Arrange
            Expression<Func<string, string>> expr = _ => String.Empty;
            var compiler = GetCompilerMethod<string, string>("CompileFromMemberAccess");

            // Act
            var func = compiler(expr);
            string result = func("hello");

            // Assert
            Assert.Equal("", result);
        }

        [Fact]
        public void Compiler_CompileSlow()
        {
            // Arrange
            Expression<Func<string, string>> expr = s => new StringBuilder(s).ToString();
            var compiler = GetCompilerMethod<string, string>("CompileSlow");

            // Act
            var func = compiler(expr);
            string result = func("hello");

            // Assert
            Assert.Equal("hello", result);
        }

        [Fact]
        public void Process()
        {
            // Arrange
            Expression<Func<string, string>> expr = s => new StringBuilder(s).ToString();

            // Act
            var func = CachedExpressionCompiler.Process(expr);
            string result = func("hello");

            // Assert
            Assert.Equal("hello", result);
        }

        // helper to create a delegate to a private method on the compiler
        private static Compiler<TIn, TOut> GetCompilerMethod<TIn, TOut>(string methodName)
        {
            Type openCompilerType = typeof(CachedExpressionCompiler).GetNestedType("Compiler`2", BindingFlags.NonPublic);
            Type closedCompilerType = openCompilerType.MakeGenericType(typeof(TIn), typeof(TOut));
            MethodInfo targetMethod = closedCompilerType.GetMethod(methodName, BindingFlags.Static | BindingFlags.NonPublic);
            return (Compiler<TIn, TOut>)Delegate.CreateDelegate(typeof(Compiler<TIn, TOut>), targetMethod);
        }
    }
}