﻿// 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 enable

using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Symbols;
using Microsoft.DiaSymReader;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.ExpressionEvaluator
{
    internal static class PdbHelpers
    {
        /// <remarks>
        /// Test helper.
        /// </remarks>
        internal static void GetAllScopes(this ISymUnmanagedMethod method, ArrayBuilder<ISymUnmanagedScope> builder)
        {
            var unused = ArrayBuilder<ISymUnmanagedScope>.GetInstance();
            GetAllScopes(method, builder, unused, offset: -1, isScopeEndInclusive: false);
            unused.Free();
        }

        internal static void GetAllScopes(
            this ISymUnmanagedMethod method,
            ArrayBuilder<ISymUnmanagedScope> allScopes,
            ArrayBuilder<ISymUnmanagedScope> containingScopes,
            int offset,
            bool isScopeEndInclusive)
        {
            GetAllScopes(method.GetRootScope(), allScopes, containingScopes, offset, isScopeEndInclusive);
        }

        private static void GetAllScopes(
            ISymUnmanagedScope root,
            ArrayBuilder<ISymUnmanagedScope> allScopes,
            ArrayBuilder<ISymUnmanagedScope> containingScopes,
            int offset,
            bool isScopeEndInclusive)
        {
            var stack = ArrayBuilder<ISymUnmanagedScope>.GetInstance();
            stack.Push(root);

            while (stack.Any())
            {
                var scope = stack.Pop();
                allScopes.Add(scope);
                if (offset >= 0 && IsInScope(scope, offset, isScopeEndInclusive))
                {
                    containingScopes.Add(scope);
                }

                foreach (var nested in scope.GetChildren())
                {
                    stack.Push(nested);
                }
            }

            stack.Free();
        }

        private static bool IsInScope(ISymUnmanagedScope scope, int offset, bool isEndInclusive)
        {
            int startOffset = scope.GetStartOffset();
            if (offset < startOffset)
            {
                return false;
            }

            int endOffset = scope.GetEndOffset();

            // In PDBs emitted by VB the end offset is inclusive, 
            // in PDBs emitted by C# the end offset is exclusive.
            return isEndInclusive ? offset <= endOffset : offset < endOffset;
        }

        /// <summary>
        /// Translates the value of a constant returned by <see cref="ISymUnmanagedConstant.GetValue(out object)"/> to a <see cref="ConstantValue"/>.
        /// </summary>
        public static ConstantValue GetSymConstantValue(ITypeSymbolInternal type, object symValue)
        {
            if (type.TypeKind == TypeKind.Enum)
            {
                type = ((INamedTypeSymbolInternal)type).EnumUnderlyingType;
            }

            return (type.SpecialType, symValue) switch
            {
                (SpecialType.System_Boolean, short shortVal) => ConstantValue.Create(shortVal != 0),
                (SpecialType.System_Byte, short shortVal) when unchecked((byte)shortVal) == shortVal => ConstantValue.Create((byte)shortVal),
                (SpecialType.System_SByte, short shortVal) when unchecked((sbyte)shortVal) == shortVal => ConstantValue.Create((sbyte)shortVal),
                (SpecialType.System_Int16, short shortVal) => ConstantValue.Create(shortVal),
                (SpecialType.System_Char, ushort ushortVal) => ConstantValue.Create((char)ushortVal),
                (SpecialType.System_UInt16, ushort ushortVal) => ConstantValue.Create(ushortVal),
                (SpecialType.System_Int32, int intVal) => ConstantValue.Create(intVal),
                (SpecialType.System_UInt32, uint uintVal) => ConstantValue.Create(uintVal),
                (SpecialType.System_Int64, long longVal) => ConstantValue.Create(longVal),
                (SpecialType.System_UInt64, ulong ulongVal) => ConstantValue.Create(ulongVal),
                (SpecialType.System_Single, float floatVal) => ConstantValue.Create(floatVal),
                (SpecialType.System_Double, double doubleVal) => ConstantValue.Create(doubleVal),
                (SpecialType.System_String, 0) => ConstantValue.Null,
                (SpecialType.System_String, null) => ConstantValue.Create(string.Empty),
                (SpecialType.System_String, string str) => ConstantValue.Create(str),
                (SpecialType.System_Object, 0) => ConstantValue.Null,
                (SpecialType.System_Decimal, decimal decimalValue) => ConstantValue.Create(decimalValue),
                (SpecialType.System_DateTime, double doubleVal) => ConstantValue.Create(DateTimeUtilities.ToDateTime(doubleVal)),
                (SpecialType.None, 0) when type.IsReferenceType => ConstantValue.Null,
                _ => ConstantValue.Bad,
            };
        }
    }
}

