// 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.

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Formatting.Rules;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Shared;
using Microsoft.CodeAnalysis.Shared.Collections;
using Microsoft.CodeAnalysis.Shared.Utilities;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.Formatting
{
    internal abstract class AbstractSyntaxFormatting : ISyntaxFormatting
    {
        private static readonly Func<TextSpan, bool> s_notEmpty = s => !s.IsEmpty;

        protected AbstractSyntaxFormatting()
        {
        }

        public abstract SyntaxFormattingOptions DefaultOptions { get; }
        public abstract SyntaxFormattingOptions GetFormattingOptions(IOptionsReader options, SyntaxFormattingOptions? fallbackOptions);

        public abstract ImmutableArray<AbstractFormattingRule> GetDefaultFormattingRules();

        protected abstract IFormattingResult CreateAggregatedFormattingResult(SyntaxNode node, IList<AbstractFormattingResult> results, TextSpanIntervalTree? formattingSpans = null);

        protected abstract AbstractFormattingResult Format(SyntaxNode node, SyntaxFormattingOptions options, IEnumerable<AbstractFormattingRule> rules, SyntaxToken startToken, SyntaxToken endToken, CancellationToken cancellationToken);

        public IFormattingResult GetFormattingResult(SyntaxNode node, IEnumerable<TextSpan>? spans, SyntaxFormattingOptions options, IEnumerable<AbstractFormattingRule>? rules, CancellationToken cancellationToken)
        {
            IReadOnlyList<TextSpan> spansToFormat;

            if (spans == null)
            {
                spansToFormat = node.FullSpan.IsEmpty
                    ? SpecializedCollections.EmptyReadOnlyList<TextSpan>()
                    : SpecializedCollections.SingletonReadOnlyList(node.FullSpan);
            }
            else
            {
                spansToFormat = new NormalizedTextSpanCollection(spans.Where(s_notEmpty));
            }

            if (spansToFormat.Count == 0)
            {
                return CreateAggregatedFormattingResult(node, SpecializedCollections.EmptyList<AbstractFormattingResult>());
            }

            rules ??= GetDefaultFormattingRules();

            List<AbstractFormattingResult>? results = null;
            foreach (var (startToken, endToken) in node.ConvertToTokenPairs(spansToFormat))
            {
                if (node.IsInvalidTokenRange(startToken, endToken))
                {
                    continue;
                }

                results ??= new List<AbstractFormattingResult>();
                results.Add(Format(node, options, rules, startToken, endToken, cancellationToken));
            }

            // quick simple case check
            if (results == null)
            {
                return CreateAggregatedFormattingResult(node, SpecializedCollections.EmptyList<AbstractFormattingResult>());
            }

            if (results.Count == 1)
            {
                return results[0];
            }

            // more expensive case
            return CreateAggregatedFormattingResult(node, results);
        }
    }
}
