// 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.ComponentModel.Composition;
using System.Threading;
using Microsoft.CodeAnalysis.Editor;
using Microsoft.CodeAnalysis.Editor.Implementation.SmartIndent;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.CodeAnalysis.Editor.Shared.Options;
using Microsoft.CodeAnalysis.ExternalAccess.FSharp.Editor;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Indentation;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Utilities;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.ExternalAccess.FSharp.Internal.Editor
{
    [Export(typeof(ISmartIndentProvider))]
    [ContentType(ContentTypeNames.FSharpContentType)]
    internal sealed class FSharpSmartIndentProvider : ISmartIndentProvider
    {
#pragma warning disable CS0618 // Type or member is obsolete
        private readonly IFSharpSynchronousIndentationService? _legacyService;
#pragma warning restore
        private readonly IFSharpIndentationService? _service;

        private readonly IGlobalOptionService _globalOptions;

        [ImportingConstructor]
        [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
        public FSharpSmartIndentProvider(
            [Import(AllowDefault = true)] IFSharpSynchronousIndentationService? legacyService,
            [Import(AllowDefault = true)] IFSharpIndentationService? service,
            IGlobalOptionService globalOptions)
        {
            Contract.ThrowIfTrue(service == null && legacyService == null);

            _legacyService = legacyService;
            _service = service;
            _globalOptions = globalOptions;
        }

        public ISmartIndent? CreateSmartIndent(ITextView textView)
            => _globalOptions.GetOption(SmartIndenterOptionsStorage.SmartIndenter) ? new SmartIndent(textView, this) : null;

        private sealed class SmartIndent : ISmartIndent
        {
            private readonly ITextView _textView;
            private readonly FSharpSmartIndentProvider _provider;

            public SmartIndent(ITextView textView, FSharpSmartIndentProvider provider)
            {
                _textView = textView;
                _provider = provider;
            }

            public void Dispose()
            {
            }

            public int? GetDesiredIndentation(ITextSnapshotLine line)
                => GetDesiredIndentation(line, CancellationToken.None);

            private int? GetDesiredIndentation(ITextSnapshotLine line, CancellationToken cancellationToken)
            {
                using (Logger.LogBlock(FunctionId.SmartIndentation_Start, cancellationToken))
                {
                    var document = line.Snapshot.GetOpenDocumentInCurrentContextWithChanges();

                    // all F# documents should have a file path
                    if (document?.FilePath == null)
                        return null;

                    FSharpIndentationResult? result;
                    if (_provider._service != null)
                    {
                        var text = document.GetTextSynchronously(cancellationToken);

                        var indentStyle = _provider._globalOptions.GetOption(IndentationOptionsStorage.SmartIndent, document.Project.Language);

                        var fsharpOptions = new FSharpIndentationOptions(
                            TabSize: _textView.Options.GetOptionValue(DefaultOptions.TabSizeOptionId),
                            IndentStyle: (FormattingOptions.IndentStyle)indentStyle);

#pragma warning disable 0618 // Compat with existing EA api
                        result = _provider._service.GetDesiredIndentation(document.Project.LanguageServices, text, document.Id, document.FilePath, line.LineNumber, fsharpOptions);
#pragma warning restore
                    }
                    else
                    {
                        Contract.ThrowIfNull(_provider._legacyService);
                        result = _provider._legacyService.GetDesiredIndentation(document, line.LineNumber, cancellationToken);
                    }

                    return result.HasValue ? new IndentationResult(result.Value.BasePosition, result.Value.Offset).GetIndentation(_textView, line) : null;
                }
            }
        }
    }
}
