// 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 Microsoft.CodeAnalysis.ExpressionEvaluator;
using Microsoft.VisualStudio.Debugger.Clr;
using Microsoft.VisualStudio.Debugger.Evaluation;
using Microsoft.VisualStudio.Debugger.Evaluation.ClrCompilation;
using Microsoft.VisualStudio.Debugger.Metadata;
using System;
using System.Diagnostics;
using Xunit;
using Type = Microsoft.VisualStudio.Debugger.Metadata.Type;

namespace Microsoft.CodeAnalysis.CSharp.ExpressionEvaluator.UnitTests
{
    public class FunctionPointerTests : CSharpResultProviderTestBase
    {
        [Fact]
        public void Root()
        {
            var source =
@"unsafe class C
{
    internal C(long p)
    {
        this.pfn = (int*)p;
    }
    int* pfn;
}";
            var assembly = GetUnsafeAssembly(source);
            unsafe
            {
                int i = 0x1234;
                long ptr = (long)&i;
                var type = assembly.GetType("C");
                var value = GetFunctionPointerField(CreateDkmClrValue(type.Instantiate(ptr)), "pfn");
                var evalResult = FormatResult("pfn", value);
                Verify(evalResult,
                    EvalResult("pfn", PointerToString(new IntPtr(ptr)), "System.Object*", "pfn", DkmEvaluationResultFlags.None, DkmEvaluationResultCategory.Other));
            }
        }

        [Fact]
        public void Member()
        {
            var source =
@"unsafe class C
{
    internal C(long p)
    {
        this.pfn = (int*)p;
    }
    int* pfn;
}";
            var assembly = GetUnsafeAssembly(source);
            const long ptr = 0x0;
            DkmClrValue getMemberValue(DkmClrValue v, string m) => (m == "pfn") ? GetFunctionPointerField(v, m) : null;
            var runtime = new DkmClrRuntimeInstance(ReflectionUtilities.GetMscorlibAndSystemCore(assembly), getMemberValue: getMemberValue);
            using (runtime.Load())
            {
                var type = runtime.GetType("C");
                var value = type.Instantiate(ptr);
                var evalResult = FormatResult("o", value);
                Verify(evalResult,
                    EvalResult("o", "{C}", "C", "o", DkmEvaluationResultFlags.Expandable, DkmEvaluationResultCategory.Other));
                var children = GetChildren(evalResult);
                Verify(children,
                    EvalResult("pfn", PointerToString(new IntPtr(ptr)), "int*", "o.pfn", DkmEvaluationResultFlags.None, DkmEvaluationResultCategory.Other));
            }
        }

        private DkmClrValue GetFunctionPointerField(DkmClrValue value, string fieldName)
        {
            var valueType = value.Type.GetLmrType();
            var fieldInfo = valueType.GetField(fieldName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
            var fieldValue = fieldInfo.GetValue(value.RawValue);
            return CreateDkmClrValue(DkmClrValue.UnboxPointer(fieldValue), new DkmClrType(FunctionPointerType.Instance));
        }

        // Function pointer type has IsPointer == true and GetElementType() == null.
        private sealed class FunctionPointerType : TypeImpl
        {
            internal static readonly FunctionPointerType Instance = new FunctionPointerType();

            private FunctionPointerType() : base(typeof(object).MakePointerType())
            {
                Debug.Assert(this.IsPointer);
            }

            public override Type GetElementType()
            {
                return null;
            }

            public override bool IsFunctionPointer()
            {
                return true;
            }
        }
    }
}
