﻿// 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.ComponentModel.Composition;
using System.Linq;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Interactive;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;

namespace Microsoft.CodeAnalysis.Editor.CSharp.Interactive
{
    [Export(typeof(ISendToInteractiveSubmissionProvider))]
    internal sealed class CSharpSendToInteractiveSubmissionProvider
        : AbstractSendToInteractiveSubmissionProvider
    {
        [ImportingConstructor]
        [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
        public CSharpSendToInteractiveSubmissionProvider()
        {
        }

        protected override bool CanParseSubmission(string code)
        {
            var options = CSharpInteractiveEvaluatorLanguageInfoProvider.Instance.ParseOptions;
            var tree = SyntaxFactory.ParseSyntaxTree(SourceText.From(code, encoding: null, SourceHashAlgorithms.Default), options);
            return tree.HasCompilationUnitRoot &&
                !tree.GetDiagnostics().Any(diagnostic => diagnostic.Severity == DiagnosticSeverity.Error);
        }

        protected override IEnumerable<TextSpan> GetExecutableSyntaxTreeNodeSelection(TextSpan selectionSpan, SyntaxNode root)
        {
            var expandedNode = GetSyntaxNodeForSubmission(selectionSpan, root);
            return expandedNode != null
                ? new TextSpan[] { expandedNode.Span }
                : Array.Empty<TextSpan>();
        }

        /// <summary>
        /// Finds a <see cref="SyntaxNode"/> that should be submitted to REPL.
        /// </summary>
        /// <param name="selectionSpan">Selection that user has originally made.</param>
        /// <param name="root">Root of the syntax tree.</param>
        private static SyntaxNode? GetSyntaxNodeForSubmission(TextSpan selectionSpan, SyntaxNode root)
        {
            GetSelectedTokens(selectionSpan, root, out var startToken, out var endToken);

            // Ensure that the first token comes before the last token.
            // Otherwise selection did not contain any tokens.
            if (startToken != endToken && startToken.Span.End > endToken.SpanStart)
                return null;

            if (startToken == endToken)
            {
                return GetSyntaxNodeForSubmission(startToken.GetRequiredParent());
            }

            var startNode = GetSyntaxNodeForSubmission(startToken.GetRequiredParent());
            var endNode = GetSyntaxNodeForSubmission(endToken.GetRequiredParent());

            // If there is no SyntaxNode worth sending to the REPL return null.
            if (startNode == null || endNode == null)
            {
                return null;
            }

            // If one of the nodes is an ancestor of another node return that node.
            if (startNode.Span.Contains(endNode.Span))
            {
                return startNode;
            }
            else if (endNode.Span.Contains(startNode.Span))
            {
                return endNode;
            }

            // Selection spans multiple statements.
            // In this case find common parent and find a span of statements within that parent.
            return GetSyntaxNodeForSubmission(startNode.GetCommonRoot(endNode));
        }

        /// <summary>
        /// Finds a <see cref="SyntaxNode"/> that should be submitted to REPL.
        /// </summary>
        /// <param name="node">The currently selected node.</param>
        private static SyntaxNode? GetSyntaxNodeForSubmission(SyntaxNode node)
        {
            SyntaxNode? candidate = node.GetAncestorOrThis<StatementSyntax>();
            if (candidate != null)
            {
                return candidate;
            }

            candidate = node.GetAncestorsOrThis<SyntaxNode>()
                .Where(IsSubmissionNode).FirstOrDefault();
            if (candidate != null)
            {
                return candidate;
            }

            return null;
        }

        /// <summary>Returns <c>true</c> if <c>node</c> could be treated as a REPL submission.</summary>
        private static bool IsSubmissionNode(SyntaxNode node)
        {
            var kind = node.Kind();
            return SyntaxFacts.IsTypeDeclaration(kind)
                || SyntaxFacts.IsGlobalMemberDeclaration(kind)
                || node.IsKind(SyntaxKind.UsingDirective);
        }

        private static void GetSelectedTokens(
            TextSpan selectionSpan,
            SyntaxNode root,
            out SyntaxToken startToken,
            out SyntaxToken endToken)
        {
            endToken = root.FindTokenOnLeftOfPosition(selectionSpan.End);
            startToken = selectionSpan.Length == 0
                ? endToken
                : root.FindTokenOnRightOfPosition(selectionSpan.Start);
        }
    }
}
