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

using System;

namespace Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax;

// Simplified version of Roslyn's SyntaxNodeCache
internal sealed class SyntaxTokenCache
{
    private const int CacheSizeBits = 16;
    private const int CacheSize = 1 << CacheSizeBits;
    private const int CacheMask = CacheSize - 1;
    public static readonly SyntaxTokenCache Instance = new();
    private static readonly Entry[] s_cache = new Entry[CacheSize];

    internal SyntaxTokenCache() { }

    private readonly struct Entry
    {
        public int Hash { get; }
        public SyntaxToken? Token { get; }

        internal Entry(int hash, SyntaxToken token)
        {
            Hash = hash;
            Token = token;
        }
    }

    public bool CanBeCached(SyntaxKind kind, params RazorDiagnostic[] diagnostics)
    {
        if (diagnostics.Length == 0)
        {
            switch (kind)
            {
                case SyntaxKind.CharacterLiteral:
                case SyntaxKind.Dot:
                case SyntaxKind.Identifier:
                case SyntaxKind.IntegerLiteral:
                case SyntaxKind.Keyword:
                case SyntaxKind.NewLine:
                case SyntaxKind.RazorCommentStar:
                case SyntaxKind.RazorCommentTransition:
                case SyntaxKind.StringLiteral:
                case SyntaxKind.Transition:
                case SyntaxKind.Whitespace:
                case SyntaxKind.EndOfFile:
                    return true;
            }
        }

        return false;
    }

    public SyntaxToken GetCachedToken(SyntaxKind kind, string content)
    {
        var hash = (kind, content).GetHashCode();

        // Allow the upper 16 bits to contribute to the index
        var indexableHash = hash ^ (hash >> 16);

        var idx = indexableHash & CacheMask;
        var e = s_cache[idx];

        if (e.Hash == hash && e.Token != null && e.Token.Kind == kind && e.Token.Content == content)
        {
            return e.Token;
        }

        var token = new SyntaxToken(kind, content, Array.Empty<RazorDiagnostic>());
        s_cache[idx] = new Entry(hash, token);

        return token;
    }
}
