﻿// 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.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Updater
{
    internal abstract class SettingsUpdaterBase<TOption, TValue> : ISettingUpdater<TOption, TValue>
    {
        private readonly List<(TOption option, TValue value)> _queue = new();
        private readonly SemaphoreSlim _guard = new(1);
        private readonly IAsynchronousOperationListener _listener;
        protected readonly Workspace Workspace;
        protected readonly string EditorconfigPath;

        protected abstract SourceText? GetNewText(SourceText analyzerConfigDocument, IReadOnlyList<(TOption option, TValue value)> settingsToUpdate, CancellationToken token);

        protected SettingsUpdaterBase(Workspace workspace, string editorconfigPath)
        {
            Workspace = workspace;
            _listener = workspace.Services.GetRequiredService<IWorkspaceAsynchronousOperationListenerProvider>().GetListener();
            EditorconfigPath = editorconfigPath;
        }

        public void QueueUpdate(TOption setting, TValue value)
        {
            var token = _listener.BeginAsyncOperation(nameof(QueueUpdate));
            _ = QueueUpdateAsync().CompletesAsyncOperation(token);

            return;

            // local function
            async Task QueueUpdateAsync()
            {
                using (await _guard.DisposableWaitAsync().ConfigureAwait(false))
                {
                    _queue.Add((setting, value));
                }
            }
        }

        public async Task<SourceText?> GetChangedEditorConfigAsync(AnalyzerConfigDocument? analyzerConfigDocument, CancellationToken token)
        {
            if (analyzerConfigDocument is null)
                return null;

            var originalText = await analyzerConfigDocument.GetValueTextAsync(token).ConfigureAwait(false);
            using (await _guard.DisposableWaitAsync(token).ConfigureAwait(false))
            {
                var newText = GetNewText(originalText, _queue, token);
                if (newText is null || newText.Equals(originalText))
                {
                    _queue.Clear();
                    return null;
                }
                else
                {
                    _queue.Clear();
                    return newText;
                }
            }
        }

        public async Task<IReadOnlyList<TextChange>?> GetChangedEditorConfigAsync(CancellationToken token)
        {
            var solution = Workspace.CurrentSolution;
            var analyzerConfigDocument = solution.Projects
                .SelectMany(p => p.AnalyzerConfigDocuments)
                .FirstOrDefault(d => d.FilePath == EditorconfigPath);
            var newText = await GetChangedEditorConfigAsync(analyzerConfigDocument, token).ConfigureAwait(false);
            if (newText is null)
            {
                return null;
            }

            var originalText = await analyzerConfigDocument!.GetValueTextAsync(token).ConfigureAwait(false);
            return newText.GetTextChanges(originalText);
        }

        public async Task<SourceText?> GetChangedEditorConfigAsync(SourceText originalText, CancellationToken token)
        {
            using (await _guard.DisposableWaitAsync(token).ConfigureAwait(false))
            {
                var newText = GetNewText(originalText, _queue, token);
                if (newText is null || newText.Equals(originalText))
                {
                    _queue.Clear();
                    return null;
                }
                else
                {
                    _queue.Clear();
                    return newText;
                }
            }
        }

        public async Task<bool> HasAnyChangesAsync()
        {
            using (await _guard.DisposableWaitAsync().ConfigureAwait(false))
            {
                return _queue.Any();
            }
        }
    }
}
