﻿// 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.Collections.Generic;
using System.Threading;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Text;

namespace Microsoft.CodeAnalysis.Highlighting
{
    internal abstract class AbstractKeywordHighlighter<TNode> : AbstractKeywordHighlighter where TNode : SyntaxNode
    {
        protected sealed override bool IsHighlightableNode(SyntaxNode node) => node is TNode;

        protected sealed override void AddHighlightsForNode(SyntaxNode node, List<TextSpan> highlights, CancellationToken cancellationToken)
            => AddHighlights((TNode)node, highlights, cancellationToken);

        protected abstract void AddHighlights(TNode node, List<TextSpan> highlights, CancellationToken cancellationToken);
    }

    internal abstract class AbstractKeywordHighlighter : IHighlighter
    {
        private static readonly ObjectPool<List<TextSpan>> s_textSpanListPool = new(() => new List<TextSpan>());
        private static readonly ObjectPool<List<SyntaxToken>> s_tokenListPool = new(() => new List<SyntaxToken>());

        protected abstract bool IsHighlightableNode(SyntaxNode node);

        public void AddHighlights(
            SyntaxNode root, int position, List<TextSpan> highlights, CancellationToken cancellationToken)
        {
            using (s_textSpanListPool.GetPooledObject(out var tempHighlights))
            using (s_tokenListPool.GetPooledObject(out var touchingTokens))
            {
                AddTouchingTokens(root, position, touchingTokens);

                foreach (var token in touchingTokens)
                {
                    for (var parent = token.Parent; parent != null; parent = parent.Parent)
                    {
                        if (IsHighlightableNode(parent))
                        {
                            tempHighlights.Clear();
                            AddHighlightsForNode(parent, tempHighlights, cancellationToken);

                            if (AnyIntersects(position, tempHighlights))
                            {
                                highlights.AddRange(tempHighlights);
                                return;
                            }
                        }
                    }
                }
            }
        }

        private static bool AnyIntersects(int position, List<TextSpan> highlights)
        {
            foreach (var highlight in highlights)
            {
                if (highlight.IntersectsWith(position))
                {
                    return true;
                }
            }

            return false;
        }

        protected abstract void AddHighlightsForNode(SyntaxNode node, List<TextSpan> highlights, CancellationToken cancellationToken);

        protected static TextSpan EmptySpan(int position)
            => new(position, 0);

        internal static void AddTouchingTokens(SyntaxNode root, int position, List<SyntaxToken> tokens)
        {
            AddTouchingTokens(root, position, tokens, findInsideTrivia: true);
            AddTouchingTokens(root, position, tokens, findInsideTrivia: false);
        }

        private static void AddTouchingTokens(SyntaxNode root, int position, List<SyntaxToken> tokens, bool findInsideTrivia)
        {
            var token = root.FindToken(position, findInsideTrivia);
            if (!tokens.Contains(token))
                tokens.Add(token);

            if (position == 0)
                return;

            var previous = root.FindToken(position - 1, findInsideTrivia);
            if (previous.Span.End == position && !tokens.Contains(previous))
                tokens.Add(previous);
        }
    }
}
