// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

#nullable disable

using System;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis.ExpressionEvaluator;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.VisualStudio.Debugger.Clr;
using Microsoft.VisualStudio.Debugger.Evaluation;
using Roslyn.Test.Utilities;
using Xunit;

namespace Microsoft.CodeAnalysis.CSharp.ExpressionEvaluator.UnitTests
{
    public class DynamicTests : CSharpResultProviderTestBase
    {
        [Fact]
        public void Simple()
        {
            var value = CreateDkmClrValue(new object());
            var rootExpr = "d";
            var evalResult = FormatResult(rootExpr, rootExpr, value, declaredType: new DkmClrType((TypeImpl)typeof(object)), declaredTypeInfo: MakeCustomTypeInfo(true));
            Verify(evalResult,
                EvalResult(rootExpr, "{object}", "dynamic {object}", rootExpr));
        }

        [Fact]
        public void Member()
        {
            var source = @"
class C
{
    dynamic F;
    dynamic P { get; set; }
}";
            var assembly = GetAssembly(source);
            var type = assembly.GetType("C");
            var value = CreateDkmClrValue(Activator.CreateInstance(type));
            var rootExpr = "new C()";
            var evalResult = FormatResult(rootExpr, value);
            Verify(evalResult,
                EvalResult(rootExpr, "{C}", "C", rootExpr, DkmEvaluationResultFlags.Expandable));
            var children = GetChildren(evalResult);
            Verify(children,
                EvalResult("F", "null", "dynamic {object}", "(new C()).F", DkmEvaluationResultFlags.CanFavorite),
                EvalResult("P", "null", "dynamic {object}", "(new C()).P", DkmEvaluationResultFlags.CanFavorite));
        }

        [Fact]
        public void Member_ConstructedType()
        {
            var source = @"
class C<T, U>
{
    T Simple;
    U[] Array;
    C<U, T> Constructed;
    C<C<C<object, T>, dynamic[]>, U[]>[] Complex;
}";
            var assembly = GetAssembly(source);
            var typeC = assembly.GetType("C`2");
            var typeC_Constructed1 = typeC.MakeGenericType(typeof(object), typeof(object)); // C<object, dynamic>
            var typeC_Constructed2 = typeC.MakeGenericType(typeof(object), typeC_Constructed1); // C<dynamic, C<object, dynamic>> (i.e. T = dynamic, U = C<object, dynamic>)
            var value = CreateDkmClrValue(Activator.CreateInstance(typeC_Constructed2));
            var rootExpr = "c";
            var evalResult = FormatResult(rootExpr, rootExpr, value, new DkmClrType((TypeImpl)typeC_Constructed2), MakeCustomTypeInfo(false, true, false, false, true));
            Verify(evalResult,
                EvalResult(rootExpr, "{C<object, C<object, object>>}", "C<dynamic, C<object, dynamic>> {C<object, C<object, object>>}", rootExpr, DkmEvaluationResultFlags.Expandable));
            var children = GetChildren(evalResult);
            Verify(children,
                EvalResult("Array", "null", "C<object, dynamic>[] {C<object, object>[]}", "c.Array", DkmEvaluationResultFlags.CanFavorite),
                EvalResult("Complex", "null", "C<C<C<object, dynamic>, dynamic[]>, C<object, dynamic>[]>[] {C<C<C<object, object>, object[]>, C<object, object>[]>[]}", "c.Complex", DkmEvaluationResultFlags.CanFavorite),
                EvalResult("Constructed", "null", "C<C<object, dynamic>, dynamic> {C<C<object, object>, object>}", "c.Constructed", DkmEvaluationResultFlags.CanFavorite),
                EvalResult("Simple", "null", "dynamic {object}", "c.Simple", DkmEvaluationResultFlags.CanFavorite));
        }

        [Fact]
        public void Member_NestedType()
        {
            var source = @"
class Outer<T>
{
    class Inner<U>
    {
        T Simple;
        U[] Array;
        Outer<U>.Inner<T> Constructed;
    }
}";
            var assembly = GetAssembly(source);
            var typeInner = assembly.GetType("Outer`1+Inner`1");
            var typeInner_Constructed = typeInner.MakeGenericType(typeof(object), typeof(object)); // Outer<dynamic>.Inner<object>
            var value = CreateDkmClrValue(Activator.CreateInstance(typeInner_Constructed));
            var rootExpr = "i";
            var evalResult = FormatResult(rootExpr, rootExpr, value, new DkmClrType((TypeImpl)typeInner_Constructed), MakeCustomTypeInfo(false, true, false));
            Verify(evalResult,
                EvalResult(rootExpr, "{Outer<object>.Inner<object>}", "Outer<dynamic>.Inner<object> {Outer<object>.Inner<object>}", rootExpr, DkmEvaluationResultFlags.Expandable));
            var children = GetChildren(evalResult);
            Verify(children,
                EvalResult("Array", "null", "object[]", "i.Array", DkmEvaluationResultFlags.CanFavorite),
                EvalResult("Constructed", "null", "Outer<object>.Inner<dynamic> {Outer<object>.Inner<object>}", "i.Constructed", DkmEvaluationResultFlags.CanFavorite),
                EvalResult("Simple", "null", "dynamic {object}", "i.Simple", DkmEvaluationResultFlags.CanFavorite));
        }

        [Fact]
        public void Member_ConstructedTypeMember()
        {
            var source = @"
class C<T>
    where T : new()
{
    T Simple = new T();
    T[] Array = new[] { new T() };
    D<T, object, dynamic> Constructed = new D<T, object, dynamic>();
}

class D<T, U, V>
{
    T TT;
    U UU;
    V VV;
}";
            var assembly = GetAssembly(source);
            var typeD = assembly.GetType("D`3");
            var typeD_Constructed = typeD.MakeGenericType(typeof(object), typeof(object), typeof(int)); // D<object, dynamic, int>
            var typeC = assembly.GetType("C`1");
            var typeC_Constructed = typeC.MakeGenericType(typeD_Constructed); // C<D<object, dynamic, int>>
            var value = CreateDkmClrValue(Activator.CreateInstance(typeC_Constructed));
            var rootExpr = "c";
            var evalResult = FormatResult(rootExpr, rootExpr, value, new DkmClrType((TypeImpl)typeC_Constructed), MakeCustomTypeInfo(false, false, false, true, false));
            Verify(evalResult,
                EvalResult(rootExpr, "{C<D<object, object, int>>}", "C<D<object, dynamic, int>> {C<D<object, object, int>>}", rootExpr, DkmEvaluationResultFlags.Expandable));
            var children = GetChildren(evalResult);
            Verify(children,
                EvalResult("Array", "{D<object, object, int>[1]}", "D<object, dynamic, int>[] {D<object, object, int>[]}", "c.Array", DkmEvaluationResultFlags.Expandable | DkmEvaluationResultFlags.CanFavorite),
                EvalResult("Constructed", "{D<D<object, object, int>, object, object>}", "D<D<object, dynamic, int>, object, dynamic> {D<D<object, object, int>, object, object>}", "c.Constructed", DkmEvaluationResultFlags.Expandable | DkmEvaluationResultFlags.CanFavorite),
                EvalResult("Simple", "{D<object, object, int>}", "D<object, dynamic, int> {D<object, object, int>}", "c.Simple", DkmEvaluationResultFlags.Expandable | DkmEvaluationResultFlags.CanFavorite));

            Verify(GetChildren(children[0]),
                EvalResult("[0]", "{D<object, object, int>}", "D<object, dynamic, int> {D<object, object, int>}", "c.Array[0]", DkmEvaluationResultFlags.Expandable));

            Verify(GetChildren(children[1]),
                EvalResult("TT", "null", "D<object, dynamic, int> {D<object, object, int>}", "c.Constructed.TT", DkmEvaluationResultFlags.CanFavorite),
                EvalResult("UU", "null", "object", "c.Constructed.UU", DkmEvaluationResultFlags.CanFavorite),
                EvalResult("VV", "null", "dynamic {object}", "c.Constructed.VV", DkmEvaluationResultFlags.CanFavorite));

            Verify(GetChildren(children[2]),
                EvalResult("TT", "null", "object", "c.Simple.TT", DkmEvaluationResultFlags.CanFavorite),
                EvalResult("UU", "null", "dynamic {object}", "c.Simple.UU", DkmEvaluationResultFlags.CanFavorite),
                EvalResult("VV", "0", "int", "c.Simple.VV", DkmEvaluationResultFlags.CanFavorite));
        }

        [Fact]
        public void Member_ExplicitInterfaceImplementation()
        {
            var source = @"
interface I<V, W>
{
    V P { get; set; }
    W Q { get; set; }
}

class C<T, U> : I<long, T>, I<bool, U>
{
    long I<long, T>.P { get; set; }
    T I<long, T>.Q { get; set; }
    bool I<bool, U>.P { get; set; }
    U I<bool, U>.Q { get; set; }
}";
            var assembly = GetAssembly(source);
            var typeC = assembly.GetType("C`2");
            var typeC_Constructed = typeC.MakeGenericType(typeof(object), typeof(object)); // C<dynamic, object>
            var value = CreateDkmClrValue(Activator.CreateInstance(typeC_Constructed));
            var rootExpr = "c";
            var evalResult = FormatResult(rootExpr, rootExpr, value, new DkmClrType((TypeImpl)typeC_Constructed), MakeCustomTypeInfo(false, true, false));
            Verify(evalResult,
                EvalResult(rootExpr, "{C<object, object>}", "C<dynamic, object> {C<object, object>}", rootExpr, DkmEvaluationResultFlags.Expandable));
            var children = GetChildren(evalResult);
            Verify(children,
                EvalResult("I<bool, object>.P", "false", "bool", "((I<bool, object>)c).P", DkmEvaluationResultFlags.Boolean),
                EvalResult("I<bool, object>.Q", "null", "object", "((I<bool, object>)c).Q"),
                EvalResult("I<long, dynamic>.P", "0", "long", "((I<long, dynamic>)c).P"),
                EvalResult("I<long, dynamic>.Q", "null", "dynamic {object}", "((I<long, dynamic>)c).Q"));
        }

        [Fact]
        public void Member_BaseType()
        {
            var source = @"
class Base<T, U, V, W>
{
    public T P;
    public U Q;
    public V R;
    public W S;
}

class Derived<T, U> : Base<T, U, object, dynamic>
{
    new public T[] P;
    new public U[] Q;
    new public dynamic[] R;
    new public object[] S;
}";
            var assembly = GetAssembly(source);
            var typeDerived = assembly.GetType("Derived`2");
            var typeDerived_Constructed = typeDerived.MakeGenericType(typeof(object), typeof(object)); // Derived<dynamic, object>
            var value = CreateDkmClrValue(Activator.CreateInstance(typeDerived_Constructed));
            var rootExpr = "d";
            var evalResult = FormatResult(rootExpr, rootExpr, value, new DkmClrType((TypeImpl)typeDerived_Constructed), MakeCustomTypeInfo(false, true, false));
            Verify(evalResult,
                EvalResult(rootExpr, "{Derived<object, object>}", "Derived<dynamic, object> {Derived<object, object>}", rootExpr, DkmEvaluationResultFlags.Expandable));

            // CONSIDER: It would be nice to substitute "dynamic" where appropriate.
            var children = GetChildren(evalResult);
            Verify(children,
                EvalResult("P (Base<object, object, object, object>)", "null", "object", "((Base<object, object, object, object>)d).P"),
                EvalResult("P", "null", "dynamic[] {object[]}", "d.P", DkmEvaluationResultFlags.CanFavorite),
                EvalResult("Q (Base<object, object, object, object>)", "null", "object", "((Base<object, object, object, object>)d).Q"),
                EvalResult("Q", "null", "object[]", "d.Q", DkmEvaluationResultFlags.CanFavorite),
                EvalResult("R (Base<object, object, object, object>)", "null", "object", "((Base<object, object, object, object>)d).R"),
                EvalResult("R", "null", "dynamic[] {object[]}", "d.R", DkmEvaluationResultFlags.CanFavorite),
                EvalResult("S (Base<object, object, object, object>)", "null", "object", "((Base<object, object, object, object>)d).S"),
                EvalResult("S", "null", "object[]", "d.S", DkmEvaluationResultFlags.CanFavorite));
        }

        [Fact]
        public void ArrayElement()
        {
            var value = CreateDkmClrValue(new object[1]);
            var rootExpr = "d";
            var evalResult = FormatResult(rootExpr, rootExpr, value, declaredType: new DkmClrType((TypeImpl)typeof(object[])), declaredTypeInfo: MakeCustomTypeInfo(false, true));
            Verify(evalResult,
                EvalResult(rootExpr, "{object[1]}", "dynamic[] {object[]}", rootExpr, DkmEvaluationResultFlags.Expandable));
            var children = GetChildren(evalResult);
            Verify(children,
                EvalResult("[0]", "null", "dynamic {object}", "d[0]"));
        }

        [Fact]
        public void TypeVariables()
        {
            var intrinsicSource =
@".class private abstract sealed beforefieldinit specialname '<>c__TypeVariables'<T,U,V>
{
  .method public hidebysig specialname rtspecialname instance void .ctor() { ret }
}";
            ImmutableArray<byte> assemblyBytes;
            ImmutableArray<byte> pdbBytes;
            CommonTestBase.EmitILToArray(intrinsicSource, appendDefaultHeader: true, includePdb: false, assemblyBytes: out assemblyBytes, pdbBytes: out pdbBytes);
            var assembly = ReflectionUtilities.Load(assemblyBytes);
            var reflectionType = assembly.GetType(ExpressionCompilerConstants.TypeVariablesClassName).MakeGenericType(new[] { typeof(object), typeof(object), typeof(object[]) });
            var value = CreateDkmClrValue(value: null, type: reflectionType, valueFlags: DkmClrValueFlags.Synthetic);
            var evalResult = FormatResult("typevars", "typevars", value, new DkmClrType((TypeImpl)reflectionType), MakeCustomTypeInfo(false, true, false, false, true));
            Verify(evalResult,
                EvalResult("Type variables", "", "", null, DkmEvaluationResultFlags.Expandable | DkmEvaluationResultFlags.ReadOnly, DkmEvaluationResultCategory.Data));
            var children = GetChildren(evalResult);
            Verify(children,
                EvalResult("T", "dynamic", "dynamic", null, DkmEvaluationResultFlags.ReadOnly, DkmEvaluationResultCategory.Data),
                EvalResult("U", "object", "object", null, DkmEvaluationResultFlags.ReadOnly, DkmEvaluationResultCategory.Data),
                EvalResult("V", "dynamic[]", "dynamic[]", null, DkmEvaluationResultFlags.ReadOnly, DkmEvaluationResultCategory.Data));
        }

        [Fact(Skip = "13554")]
        [WorkItem("https://github.com/dotnet/roslyn/issues/13554")]
        public void DynamicBaseTypeArgument()
        {
            var source =
@"class A<T>
{
#pragma warning disable 0169
    internal T F;
#pragma warning restore 0169
}
class B : A<dynamic>
{
    B() { F = 1; }
}";
            var runtime = new DkmClrRuntimeInstance(ReflectionUtilities.GetMscorlib(GetAssembly(source)));
            using (runtime.Load())
            {
                var type = runtime.GetType("B");
                var value = type.Instantiate();
                var evalResult = FormatResult("o", value);
                var children = GetChildren(evalResult);
                Verify(children,
                    EvalResult("F", "1", "dynamic {int}", "o.F"));
            }
        }
    }
}
