﻿// 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.Linq;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.SignatureHelp;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis.Shared.Extensions;

namespace Microsoft.CodeAnalysis.CSharp.SignatureHelp
{
    internal static class SignatureHelpUtilities
    {
        private static readonly Func<BaseArgumentListSyntax, SyntaxToken> s_getBaseArgumentListOpenToken = list => list.GetOpenToken();
        private static readonly Func<TypeArgumentListSyntax, SyntaxToken> s_getTypeArgumentListOpenToken = list => list.LessThanToken;
        private static readonly Func<InitializerExpressionSyntax, SyntaxToken> s_getInitializerExpressionOpenToken = e => e.OpenBraceToken;
        private static readonly Func<AttributeArgumentListSyntax, SyntaxToken> s_getAttributeArgumentListOpenToken = list => list.OpenParenToken;

        private static readonly Func<BaseArgumentListSyntax, SyntaxToken> s_getBaseArgumentListCloseToken = list => list.GetCloseToken();
        private static readonly Func<TypeArgumentListSyntax, SyntaxToken> s_getTypeArgumentListCloseToken = list => list.GreaterThanToken;
        private static readonly Func<InitializerExpressionSyntax, SyntaxToken> s_getInitializerExpressionCloseToken = e => e.CloseBraceToken;
        private static readonly Func<AttributeArgumentListSyntax, SyntaxToken> s_getAttributeArgumentListCloseToken = list => list.CloseParenToken;

        private static readonly Func<BaseArgumentListSyntax, SyntaxNodeOrTokenList> s_getBaseArgumentListArgumentsWithSeparators =
            list => list.Arguments.GetWithSeparators();
        private static readonly Func<TypeArgumentListSyntax, SyntaxNodeOrTokenList> s_getTypeArgumentListArgumentsWithSeparators =
            list => list.Arguments.GetWithSeparators();
        private static readonly Func<InitializerExpressionSyntax, SyntaxNodeOrTokenList> s_getInitializerExpressionArgumentsWithSeparators =
            e => e.Expressions.GetWithSeparators();
        private static readonly Func<AttributeArgumentListSyntax, SyntaxNodeOrTokenList> s_getAttributeArgumentListArgumentsWithSeparators =
            list => list.Arguments.GetWithSeparators();

        private static readonly Func<BaseArgumentListSyntax, IEnumerable<string?>> s_getBaseArgumentListNames =
            list => list.Arguments.Select(argument => argument.NameColon?.Name.Identifier.ValueText);
        private static readonly Func<TypeArgumentListSyntax, IEnumerable<string?>> s_getTypeArgumentListNames =
            list => list.Arguments.Select(a => (string?)null);
        private static readonly Func<InitializerExpressionSyntax, IEnumerable<string?>> s_getInitializerExpressionNames =
            e => e.Expressions.Select(a => (string?)null);
        private static readonly Func<AttributeArgumentListSyntax, IEnumerable<string?>> s_getAttributeArgumentListNames =
            list => list.Arguments.Select(
                argument => argument.NameColon?.Name.Identifier.ValueText ?? argument.NameEquals?.Name.Identifier.ValueText);

        public static SignatureHelpState? GetSignatureHelpState(BaseArgumentListSyntax argumentList, int position)
        {
            return CommonSignatureHelpUtilities.GetSignatureHelpState(
                argumentList, position,
                s_getBaseArgumentListOpenToken,
                s_getBaseArgumentListCloseToken,
                s_getBaseArgumentListArgumentsWithSeparators,
                s_getBaseArgumentListNames);
        }

        internal static SignatureHelpState? GetSignatureHelpState(TypeArgumentListSyntax argumentList, int position)
        {
            return CommonSignatureHelpUtilities.GetSignatureHelpState(
                argumentList, position,
                s_getTypeArgumentListOpenToken,
                s_getTypeArgumentListCloseToken,
                s_getTypeArgumentListArgumentsWithSeparators,
                s_getTypeArgumentListNames);
        }

        internal static SignatureHelpState? GetSignatureHelpState(InitializerExpressionSyntax argumentList, int position)
        {
            return CommonSignatureHelpUtilities.GetSignatureHelpState(
                argumentList, position,
                s_getInitializerExpressionOpenToken,
                s_getInitializerExpressionCloseToken,
                s_getInitializerExpressionArgumentsWithSeparators,
                s_getInitializerExpressionNames);
        }

        internal static SignatureHelpState? GetSignatureHelpState(AttributeArgumentListSyntax argumentList, int position)
        {
            return CommonSignatureHelpUtilities.GetSignatureHelpState(
                argumentList, position,
                s_getAttributeArgumentListOpenToken,
                s_getAttributeArgumentListCloseToken,
                s_getAttributeArgumentListArgumentsWithSeparators,
                s_getAttributeArgumentListNames);
        }

        internal static TextSpan GetSignatureHelpSpan(BaseArgumentListSyntax argumentList)
            => CommonSignatureHelpUtilities.GetSignatureHelpSpan(argumentList, s_getBaseArgumentListCloseToken);

        internal static TextSpan GetSignatureHelpSpan(TypeArgumentListSyntax argumentList)
            => CommonSignatureHelpUtilities.GetSignatureHelpSpan(argumentList, s_getTypeArgumentListCloseToken);

        internal static TextSpan GetSignatureHelpSpan(InitializerExpressionSyntax initializer)
            => CommonSignatureHelpUtilities.GetSignatureHelpSpan(initializer, initializer.SpanStart, s_getInitializerExpressionCloseToken);

        internal static TextSpan GetSignatureHelpSpan(AttributeArgumentListSyntax argumentList)
            => CommonSignatureHelpUtilities.GetSignatureHelpSpan(argumentList, s_getAttributeArgumentListCloseToken);

        internal static bool IsTriggerParenOrComma<TSyntaxNode>(SyntaxToken token, Func<char, bool> isTriggerCharacter) where TSyntaxNode : SyntaxNode
        {
            // Don't dismiss if the user types ( to start a parenthesized expression or tuple
            // Note that the tuple initially parses as a parenthesized expression 
            if (token.IsKind(SyntaxKind.OpenParenToken) &&
                token.Parent is ParenthesizedExpressionSyntax parenExpr)
            {
                var parenthesizedExpr = parenExpr.WalkUpParentheses();
                if (parenthesizedExpr.Parent is ArgumentSyntax)
                {
                    var parent = parenthesizedExpr.Parent;
                    var grandParent = parent.Parent;
                    if (grandParent is ArgumentListSyntax && grandParent.Parent is TSyntaxNode)
                    {
                        // Argument to TSyntaxNode's argument list
                        return true;
                    }
                    else
                    {
                        // Argument to a tuple in TSyntaxNode's argument list
                        return grandParent is TupleExpressionSyntax && parenthesizedExpr.GetAncestor<TSyntaxNode>() != null;
                    }
                }
                else
                {
                    // Not an argument
                    return false;
                }
            }

            // Don't dismiss if the user types ',' to add a member to a tuple
            if (token.IsKind(SyntaxKind.CommaToken) && token.Parent is TupleExpressionSyntax && token.GetAncestor<TSyntaxNode>() != null)
            {
                return true;
            }

            return !token.IsKind(SyntaxKind.None) &&
                token.ValueText.Length == 1 &&
                isTriggerCharacter(token.ValueText[0]) &&
                token.Parent is ArgumentListSyntax &&
                token.Parent.Parent is TSyntaxNode;
        }
    }
}
