﻿// 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;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.SignatureHelp;
using Microsoft.VisualStudio.Language.Intellisense;
using Microsoft.VisualStudio.Text;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.SignatureHelp.Presentation
{
    internal class Signature : ISignature
    {
        private const int MaxParamColumnCount = 100;

        private readonly SignatureHelpItem _signatureHelpItem;

        public Signature(ITrackingSpan applicableToSpan, SignatureHelpItem signatureHelpItem, int selectedParameterIndex)
        {
            if (selectedParameterIndex < -1 || selectedParameterIndex >= signatureHelpItem.Parameters.Length)
            {
                throw new ArgumentOutOfRangeException(nameof(selectedParameterIndex));
            }

            this.ApplicableToSpan = applicableToSpan;
            _signatureHelpItem = signatureHelpItem;
            _parameterIndex = selectedParameterIndex;
        }

        private bool _isInitialized;
        private void EnsureInitialized()
        {
            if (!_isInitialized)
            {
                _isInitialized = true;
                Initialize();
            }
        }

        private Signature InitializedThis
        {
            get
            {
                EnsureInitialized();
                return this;
            }
        }

        private IList<TaggedText> _displayParts;
        internal IList<TaggedText> DisplayParts => InitializedThis._displayParts;

        public ITrackingSpan ApplicableToSpan { get; }

        private string _content;
        public string Content => InitializedThis._content;

        private readonly int _parameterIndex = -1;
        public IParameter CurrentParameter
        {
            get
            {
                EnsureInitialized();
                return _parameterIndex >= 0 && _parameters != null ? _parameters[_parameterIndex] : null;
            }
        }

        /// <remarks>
        /// The documentation is included in <see cref="Content"/> so that it will be classified.
        /// </remarks>
        public string Documentation => null;

        private ReadOnlyCollection<IParameter> _parameters;
        public ReadOnlyCollection<IParameter> Parameters => InitializedThis._parameters;

        private string _prettyPrintedContent;
        public string PrettyPrintedContent => InitializedThis._prettyPrintedContent;

        // This event is required by the ISignature interface but it's not actually used
        // (once created the CurrentParameter property cannot change)
        public event EventHandler<CurrentParameterChangedEventArgs> CurrentParameterChanged
        {
            add
            {
            }
            remove
            {
            }
        }

        private IList<TaggedText> _prettyPrintedDisplayParts;
        internal IList<TaggedText> PrettyPrintedDisplayParts => InitializedThis._prettyPrintedDisplayParts;

        private void Initialize()
        {
            var content = new StringBuilder();
            var prettyPrintedContent = new StringBuilder();

            var parts = new List<TaggedText>();
            var prettyPrintedParts = new List<TaggedText>();

            var parameters = new List<IParameter>();

            var signaturePrefixParts = _signatureHelpItem.PrefixDisplayParts;
            var signaturePrefixContent = _signatureHelpItem.PrefixDisplayParts.GetFullText();

            AddRange(signaturePrefixParts, parts, prettyPrintedParts);
            Append(signaturePrefixContent, content, prettyPrintedContent);

            var separatorParts = _signatureHelpItem.SeparatorDisplayParts;
            var separatorContent = separatorParts.GetFullText();

            var newLinePart = new TaggedText(TextTags.LineBreak, "\r\n");
            var newLineContent = newLinePart.ToString();
            var spacerPart = new TaggedText(TextTags.Space, new string(' ', signaturePrefixContent.Length));
            var spacerContent = spacerPart.ToString();

            var paramColumnCount = 0;

            for (var i = 0; i < _signatureHelpItem.Parameters.Length; i++)
            {
                var sigHelpParameter = _signatureHelpItem.Parameters[i];

                var parameterPrefixParts = sigHelpParameter.PrefixDisplayParts;
                var parameterPrefixContext = sigHelpParameter.PrefixDisplayParts.GetFullText();

                var parameterParts = AddOptionalBrackets(
                    sigHelpParameter.IsOptional, sigHelpParameter.DisplayParts);
                var parameterContent = parameterParts.GetFullText();

                var parameterSuffixParts = sigHelpParameter.SuffixDisplayParts;
                var parameterSuffixContext = sigHelpParameter.SuffixDisplayParts.GetFullText();

                paramColumnCount += separatorContent.Length + parameterPrefixContext.Length + parameterContent.Length + parameterSuffixContext.Length;

                if (i > 0)
                {
                    AddRange(separatorParts, parts, prettyPrintedParts);
                    Append(separatorContent, content, prettyPrintedContent);

                    if (paramColumnCount > MaxParamColumnCount)
                    {
                        prettyPrintedParts.Add(newLinePart);
                        prettyPrintedParts.Add(spacerPart);
                        prettyPrintedContent.Append(newLineContent);
                        prettyPrintedContent.Append(spacerContent);

                        paramColumnCount = 0;
                    }
                }

                AddRange(parameterPrefixParts, parts, prettyPrintedParts);
                Append(parameterPrefixContext, content, prettyPrintedContent);

                parameters.Add(new Parameter(this, sigHelpParameter, parameterContent, content.Length, prettyPrintedContent.Length));

                AddRange(parameterParts, parts, prettyPrintedParts);
                Append(parameterContent, content, prettyPrintedContent);

                AddRange(parameterSuffixParts, parts, prettyPrintedParts);
                Append(parameterSuffixContext, content, prettyPrintedContent);
            }

            AddRange(_signatureHelpItem.SuffixDisplayParts, parts, prettyPrintedParts);
            Append(_signatureHelpItem.SuffixDisplayParts.GetFullText(), content, prettyPrintedContent);

            if (_parameterIndex >= 0)
            {
                var sigHelpParameter = _signatureHelpItem.Parameters[_parameterIndex];

                AddRange(sigHelpParameter.SelectedDisplayParts, parts, prettyPrintedParts);
                Append(sigHelpParameter.SelectedDisplayParts.GetFullText(), content, prettyPrintedContent);
            }

            AddRange(_signatureHelpItem.DescriptionParts, parts, prettyPrintedParts);
            Append(_signatureHelpItem.DescriptionParts.GetFullText(), content, prettyPrintedContent);

            var documentation = _signatureHelpItem.DocumentationFactory(CancellationToken.None).ToList();
            if (documentation.Count > 0)
            {
                AddRange(new[] { newLinePart }, parts, prettyPrintedParts);
                Append(newLineContent, content, prettyPrintedContent);

                AddRange(documentation, parts, prettyPrintedParts);
                Append(documentation.GetFullText(), content, prettyPrintedContent);
            }

            _content = content.ToString();
            _prettyPrintedContent = prettyPrintedContent.ToString();
            _displayParts = parts.ToImmutableArrayOrEmpty();
            _prettyPrintedDisplayParts = prettyPrintedParts.ToImmutableArrayOrEmpty();
            _parameters = parameters.ToReadOnlyCollection();
        }

        private static void AddRange(IList<TaggedText> values, List<TaggedText> parts, List<TaggedText> prettyPrintedParts)
        {
            parts.AddRange(values);
            prettyPrintedParts.AddRange(values);
        }

        private static void Append(string text, StringBuilder content, StringBuilder prettyPrintedContent)
        {
            content.Append(text);
            prettyPrintedContent.Append(text);
        }

        private static IList<TaggedText> AddOptionalBrackets(bool isOptional, IList<TaggedText> list)
        {
            if (isOptional)
            {
                var result = new List<TaggedText>();
                result.Add(new TaggedText(TextTags.Punctuation, "["));
                result.AddRange(list);
                result.Add(new TaggedText(TextTags.Punctuation, "]"));
                return result;
            }

            return list;
        }
    }
}
