// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Xunit;

namespace System.Linq.Expressions.Tests
{
    public class NewWithByRefParameterTests
    {
        private readonly int Always2 = 2;

        private class ByRefNewType
        {
            public ByRefNewType(ref int x, ref int y)
            {
                if (x < 0)
                {
                    throw new ArgumentOutOfRangeException(nameof(x));
                }

                ++x;
                y *= x;
            }
        }

        private class OutNewType
        {
            public OutNewType(out int x)
            {
                x = 42;
            }
        }

        private delegate ByRefNewType ByRefNewFactory2(ref int x, ref int y);

        private delegate ByRefNewType ByRefNewFactory1(ref int x);

        private delegate OutNewType OutNewTypeFactory(out int x);

        [Theory, ClassData(typeof(CompilationTypes))]
        public void CreateByRef(bool useInterpreter)
        {
            ParameterExpression pX = Expression.Parameter(typeof(int).MakeByRefType());
            ParameterExpression pY = Expression.Parameter(typeof(int).MakeByRefType());
            ByRefNewFactory2 del =
                Expression.Lambda<ByRefNewFactory2>(
                    Expression.New(typeof(ByRefNewType).GetConstructors()[0], pX, pY), pX, pY).Compile(useInterpreter);
            int x = 3;
            int y = 4;
            Assert.NotNull(del(ref x, ref y));
            Assert.Equal(4, x);
            Assert.Equal(16, y);
        }

        private void CreateByRefAliasing(bool useInterpreter)
        {
            ParameterExpression pX = Expression.Parameter(typeof(int).MakeByRefType());
            ParameterExpression pY = Expression.Parameter(typeof(int).MakeByRefType());
            ByRefNewFactory2 del =
                Expression.Lambda<ByRefNewFactory2>(
                    Expression.New(typeof(ByRefNewType).GetConstructors()[0], pX, pY), pX, pY).Compile(useInterpreter);
            int x = 3;
            Assert.NotNull(del(ref x, ref x));
            Assert.Equal(16, x);
        }

        [Fact, ActiveIssue("https://github.com/dotnet/runtime/issues/19286")]
        public void CreateByRefAliasingInterpreted()
        {
            CreateByRefAliasing(useInterpreter: true);
        }

        [ActiveIssue("https://github.com/dotnet/runtime/issues/19286", typeof(PlatformDetection), nameof(PlatformDetection.IsLinqExpressionsBuiltWithIsInterpretingOnly))]
        [Fact]
        public void CreateByRefAliasingCompiled()
        {
            CreateByRefAliasing(useInterpreter: false);
        }

        [Theory, ClassData(typeof(CompilationTypes))]
        public void CreateByRefReferencingReadonly(bool useInterpreter)
        {
            ParameterExpression p = Expression.Parameter(typeof(int).MakeByRefType());
            ByRefNewFactory1 del =
                Expression.Lambda<ByRefNewFactory1>(
                    Expression.New(
                        typeof(ByRefNewType).GetConstructors()[0],
                        Expression.Field(Expression.Constant(this), "Always2"), p), p).Compile(useInterpreter);
            int x = 19;
            Assert.NotNull(del(ref x));
            Assert.Equal(2, Always2);
            Assert.Equal(57, x);
        }

        [Theory, ClassData(typeof(CompilationTypes))]
        public void CreateByRefReferencingOnlyReadonly(bool useInterpreter)
        {
            Func<ByRefNewType> del =
                Expression.Lambda<Func<ByRefNewType>>(
                    Expression.New(
                        typeof(ByRefNewType).GetConstructors()[0],
                        Expression.Field(Expression.Constant(this), "Always2"),
                        Expression.Field(Expression.Constant(this), "Always2"))).Compile(useInterpreter);
            Assert.NotNull(del());
            Assert.Equal(2, Always2);
        }

        [Theory, ClassData(typeof(CompilationTypes))]
        public void CreateByRefThrowing(bool useInterpreter)
        {
            ParameterExpression pX = Expression.Parameter(typeof(int).MakeByRefType());
            ParameterExpression pY = Expression.Parameter(typeof(int).MakeByRefType());
            ByRefNewFactory2 del =
                Expression.Lambda<ByRefNewFactory2>(
                    Expression.New(typeof(ByRefNewType).GetConstructors()[0], pX, pY), pX, pY).Compile(useInterpreter);
            int x = -9;
            int y = 4;
            AssertExtensions.Throws<ArgumentOutOfRangeException>("x", () => del(ref x, ref y));
        }

        [Theory, ClassData(typeof(CompilationTypes))]
        public void CreateOut(bool useInterpreter)
        {
            ParameterExpression p = Expression.Parameter(typeof(int).MakeByRefType());
            OutNewTypeFactory del =
                Expression.Lambda<OutNewTypeFactory>(Expression.New(typeof(OutNewType).GetConstructors()[0], p), p)
                    .Compile(useInterpreter);
            int x;
            Assert.NotNull(del(out x));
            Assert.Equal(42, x);
        }
    }
}
