// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Text;
using System.Threading;

namespace Microsoft.AspNetCore.Razor.Language.Syntax
{
    [DebuggerDisplay("{GetDebuggerDisplay(), nq}")]
    internal abstract partial class SyntaxNode
    {
        public SyntaxNode(GreenNode green, SyntaxNode parent, int position)
        {
            Green = green;
            Parent = parent;
            Position = position;
        }

        internal GreenNode Green { get; }

        public SyntaxNode Parent { get; }

        public int Position { get; }

        public int EndPosition => Position + FullWidth;

        public SyntaxKind Kind => Green.Kind;

        public int Width => Green.Width;

        public int FullWidth => Green.FullWidth;

        public int SpanStart => Position + Green.GetLeadingTriviaWidth();

        public TextSpan FullSpan => new TextSpan(Position, Green.FullWidth);

        public TextSpan Span
        {
            get
            {
                // Start with the full span.
                var start = Position;
                var width = Green.FullWidth;

                // adjust for preceding trivia (avoid calling this twice, do not call Green.Width)
                var precedingWidth = Green.GetLeadingTriviaWidth();
                start += precedingWidth;
                width -= precedingWidth;

                // adjust for following trivia width
                width -= Green.GetTrailingTriviaWidth();

                Debug.Assert(width >= 0);
                return new TextSpan(start, width);
            }
        }

        internal int SlotCount => Green.SlotCount;

        public bool IsList => Green.IsList;

        public bool IsMissing => Green.IsMissing;

        public bool IsToken => Green.IsToken;

        public bool IsTrivia => Green.IsTrivia;

        public bool HasLeadingTrivia
        {
            get
            {
                return GetLeadingTrivia().Count > 0;
            }
        }

        public bool HasTrailingTrivia
        {
            get
            {
                return GetTrailingTrivia().Count > 0;
            }
        }

        public bool ContainsDiagnostics => Green.ContainsDiagnostics;

        public bool ContainsAnnotations => Green.ContainsAnnotations;

        internal string SerializedValue => SyntaxSerializer.Serialize(this);

        public abstract TResult Accept<TResult>(SyntaxVisitor<TResult> visitor);

        public abstract void Accept(SyntaxVisitor visitor);

        internal abstract SyntaxNode GetNodeSlot(int index);

        internal abstract SyntaxNode GetCachedSlot(int index);

        internal SyntaxNode GetRed(ref SyntaxNode field, int slot)
        {
            var result = field;

            if (result == null)
            {
                var green = Green.GetSlot(slot);
                if (green != null)
                {
                    Interlocked.CompareExchange(ref field, green.CreateRed(this, GetChildPosition(slot)), null);
                    result = field;
                }
            }

            return result;
        }

        // Special case of above function where slot = 0, does not need GetChildPosition
        internal SyntaxNode GetRedAtZero(ref SyntaxNode field)
        {
            var result = field;

            if (result == null)
            {
                var green = Green.GetSlot(0);
                if (green != null)
                {
                    Interlocked.CompareExchange(ref field, green.CreateRed(this, Position), null);
                    result = field;
                }
            }

            return result;
        }

        protected T GetRed<T>(ref T field, int slot) where T : SyntaxNode
        {
            var result = field;

            if (result == null)
            {
                var green = Green.GetSlot(slot);
                if (green != null)
                {
                    Interlocked.CompareExchange(ref field, (T)green.CreateRed(this, this.GetChildPosition(slot)), null);
                    result = field;
                }
            }

            return result;
        }

        // special case of above function where slot = 0, does not need GetChildPosition
        protected T GetRedAtZero<T>(ref T field) where T : SyntaxNode
        {
            var result = field;

            if (result == null)
            {
                var green = Green.GetSlot(0);
                if (green != null)
                {
                    Interlocked.CompareExchange(ref field, (T)green.CreateRed(this, Position), null);
                    result = field;
                }
            }

            return result;
        }

        internal SyntaxNode GetRedElement(ref SyntaxNode element, int slot)
        {
            Debug.Assert(IsList);

            var result = element;

            if (result == null)
            {
                var green = Green.GetSlot(slot);
                // passing list's parent
                Interlocked.CompareExchange(ref element, green.CreateRed(Parent, GetChildPosition(slot)), null);
                result = element;
            }

            return result;
        }

        internal virtual int GetChildPosition(int index)
        {
            var offset = 0;
            var green = Green;
            while (index > 0)
            {
                index--;
                var prevSibling = GetCachedSlot(index);
                if (prevSibling != null)
                {
                    return prevSibling.EndPosition + offset;
                }
                var greenChild = green.GetSlot(index);
                if (greenChild != null)
                {
                    offset += greenChild.FullWidth;
                }
            }

            return Position + offset;
        }

        public virtual SyntaxTriviaList GetLeadingTrivia()
        {
            var firstToken = GetFirstToken();
            return firstToken != null ? firstToken.GetLeadingTrivia() : default(SyntaxTriviaList);
        }

        public virtual SyntaxTriviaList GetTrailingTrivia()
        {
            var lastToken = GetLastToken();
            return lastToken != null ? lastToken.GetTrailingTrivia() : default(SyntaxTriviaList);
        }

        internal SyntaxToken GetFirstToken()
        {
            return ((SyntaxToken)GetFirstTerminal());
        }

        internal SyntaxList<SyntaxToken> GetTokens()
        {
            var tokens = SyntaxListBuilder<SyntaxToken>.Create();

            AddTokens(this, tokens);

            return tokens;

            static void AddTokens(SyntaxNode current, SyntaxListBuilder<SyntaxToken> tokens)
            {
                if (current.SlotCount == 0 && current is SyntaxToken token)
                {
                    // Token
                    tokens.Add(token);
                    return;
                }

                for (var i = 0; i < current.SlotCount; i++)
                {
                    var child = current.GetNodeSlot(i);

                    if (child != null)
                    {
                        AddTokens(child, tokens);
                    }
                }
            }
        }

        internal SyntaxToken GetLastToken()
        {
            return ((SyntaxToken)GetLastTerminal());
        }

        public SyntaxNode GetFirstTerminal()
        {
            var node = this;

            do
            {
                var foundChild = false;
                for (int i = 0, n = node.SlotCount; i < n; i++)
                {
                    var child = node.GetNodeSlot(i);
                    if (child != null)
                    {
                        node = child;
                        foundChild = true;
                        break;
                    }
                }

                if (!foundChild)
                {
                    return null;
                }
            }
            while (node.SlotCount != 0);

            return node == this ? this : node;
        }

        public SyntaxNode GetLastTerminal()
        {
            var node = this;

            do
            {
                SyntaxNode lastChild = null;
                for (var i = node.SlotCount - 1; i >= 0; i--)
                {
                    var child = node.GetNodeSlot(i);
                    if (child != null && child.FullWidth > 0)
                    {
                        lastChild = child;
                        break;
                    }
                }
                node = lastChild;
            } while (node?.SlotCount > 0);

            return node;
        }

        /// <summary>
        /// The list of child nodes of this node, where each element is a SyntaxNode instance.
        /// </summary>
        public ChildSyntaxList ChildNodes()
        {
            return new ChildSyntaxList(this);
        }

        /// <summary>
        /// Gets a list of ancestor nodes
        /// </summary>
        public IEnumerable<SyntaxNode> Ancestors()
        {
            return Parent?
                .AncestorsAndSelf() ??
                Array.Empty<SyntaxNode>();
        }

        /// <summary>
        /// Gets a list of ancestor nodes (including this node)
        /// </summary>
        public IEnumerable<SyntaxNode> AncestorsAndSelf()
        {
            for (var node = this; node != null; node = node.Parent)
            {
                yield return node;
            }
        }

        /// <summary>
        /// Gets the first node of type TNode that matches the predicate.
        /// </summary>
        public TNode FirstAncestorOrSelf<TNode>(Func<TNode, bool> predicate = null)
            where TNode : SyntaxNode
        {
            for (var node = this; node != null; node = node.Parent)
            {
                if (node is TNode tnode && (predicate == null || predicate(tnode)))
                {
                    return tnode;
                }
            }

            return default;
        }

        /// <summary>
        /// Gets a list of descendant nodes in prefix document order.
        /// </summary>
        /// <param name="descendIntoChildren">An optional function that determines if the search descends into the argument node's children.</param>
        public IEnumerable<SyntaxNode> DescendantNodes(Func<SyntaxNode, bool> descendIntoChildren = null)
        {
            return DescendantNodesImpl(FullSpan, descendIntoChildren, includeSelf: false);
        }

        /// <summary>
        /// Gets a list of descendant nodes (including this node) in prefix document order.
        /// </summary>
        /// <param name="descendIntoChildren">An optional function that determines if the search descends into the argument node's children.</param>
        public IEnumerable<SyntaxNode> DescendantNodesAndSelf(Func<SyntaxNode, bool> descendIntoChildren = null)
        {
            return DescendantNodesImpl(FullSpan, descendIntoChildren, includeSelf: true);
        }

        protected internal SyntaxNode ReplaceCore<TNode>(
            IEnumerable<TNode> nodes = null,
            Func<TNode, TNode, SyntaxNode> computeReplacementNode = null)
            where TNode : SyntaxNode
        {
            return SyntaxReplacer.Replace(this, nodes, computeReplacementNode);
        }

        protected internal SyntaxNode ReplaceNodeInListCore(SyntaxNode originalNode, IEnumerable<SyntaxNode> replacementNodes)
        {
            return SyntaxReplacer.ReplaceNodeInList(this, originalNode, replacementNodes);
        }

        protected internal SyntaxNode InsertNodesInListCore(SyntaxNode nodeInList, IEnumerable<SyntaxNode> nodesToInsert, bool insertBefore)
        {
            return SyntaxReplacer.InsertNodeInList(this, nodeInList, nodesToInsert, insertBefore);
        }

        public RazorDiagnostic[] GetDiagnostics()
        {
            return Green.GetDiagnostics();
        }

        public SyntaxAnnotation[] GetAnnotations()
        {
            return Green.GetAnnotations();
        }

        public bool IsEquivalentTo(SyntaxNode other)
        {
            if (this == other)
            {
                return true;
            }

            if (other == null)
            {
                return false;
            }

            return Green.IsEquivalentTo(other.Green);
        }

        public override string ToString()
        {
            var builder = new StringBuilder();
            builder.Append(Green.ToString());
            builder.AppendFormat(CultureInfo.InvariantCulture, " at {0}::{1}", Position, FullWidth);

            return builder.ToString();
        }

        public virtual string ToFullString()
        {
            return Green.ToFullString();
        }

        protected virtual string GetDebuggerDisplay()
        {
            if (IsToken)
            {
                return string.Format(CultureInfo.InvariantCulture, "{0};[{1}]", Kind, ToFullString());
            }

            return string.Format(CultureInfo.InvariantCulture, "{0} [{1}..{2})", Kind, Position, EndPosition);
        }
    }
}
