﻿// 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.Immutable;
using System.Diagnostics;
using Microsoft.CodeAnalysis.EmbeddedLanguages.Common;

namespace Microsoft.CodeAnalysis.Features.EmbeddedLanguages.Json
{
    using JsonNodeOrToken = EmbeddedSyntaxNodeOrToken<JsonKind, JsonNode>;
    using JsonToken = EmbeddedSyntaxToken<JsonKind>;
    using JsonSeparatedList = EmbeddedSeparatedSyntaxNodeList<JsonKind, JsonNode, JsonValueNode>;

    internal sealed class JsonCompilationUnit : JsonNode
    {
        public JsonCompilationUnit(ImmutableArray<JsonValueNode> sequence, JsonToken endOfFileToken)
            : base(JsonKind.CompilationUnit)
        {
            Debug.Assert(sequence != null);
            Debug.Assert(endOfFileToken.Kind == JsonKind.EndOfFile);
            Sequence = sequence;
            EndOfFileToken = endOfFileToken;
        }

        /// <summary>
        /// For error recovery purposes, we support a sequence of nodes at the top level (even
        /// though only a single node is actually allowed).
        /// </summary>
        public ImmutableArray<JsonValueNode> Sequence { get; }
        public JsonToken EndOfFileToken { get; }

        internal override int ChildCount => Sequence.Length + 1;

        internal override JsonNodeOrToken ChildAt(int index)
        {
            if (index == Sequence.Length)
                return EndOfFileToken;

            return Sequence[index];
        }

        public override void Accept(IJsonNodeVisitor visitor)
            => visitor.Visit(this);
    }

    /// <summary>
    /// Root of all value nodes.
    /// </summary>
    internal abstract class JsonValueNode : JsonNode
    {
        protected JsonValueNode(JsonKind kind)
            : base(kind)
        {
        }
    }

    /// <summary>
    /// Represents a chunk of text that we did not understand as anything special.  i.e. it wasn't a keyword, number, or
    /// literal.  One common case of this is an unquoted property name (which json.net accepts).
    /// </summary>
    internal sealed class JsonTextNode : JsonValueNode
    {
        public JsonTextNode(JsonToken textToken)
            : base(JsonKind.Text)
        {
            Debug.Assert(textToken.Kind == JsonKind.TextToken);
            TextToken = textToken;
        }

        public JsonToken TextToken { get; }

        internal override int ChildCount => 1;

        internal override JsonNodeOrToken ChildAt(int index)
            => index switch
            {
                0 => TextToken,
                _ => throw new InvalidOperationException(),
            };

        public override void Accept(IJsonNodeVisitor visitor)
            => visitor.Visit(this);
    }

    internal sealed class JsonObjectNode : JsonValueNode
    {
        public JsonObjectNode(
            JsonToken openBraceToken,
            JsonSeparatedList sequence,
            JsonToken closeBraceToken)
            : base(JsonKind.Object)
        {
            Debug.Assert(openBraceToken.Kind == JsonKind.OpenBraceToken);
            Debug.Assert(closeBraceToken.Kind == JsonKind.CloseBraceToken);

            OpenBraceToken = openBraceToken;
            Sequence = sequence;
            CloseBraceToken = closeBraceToken;
        }

        public JsonToken OpenBraceToken { get; }
        public JsonSeparatedList Sequence { get; }
        public JsonToken CloseBraceToken { get; }

        internal override int ChildCount => 2 + Sequence.NodesAndTokens.Length;

        internal override JsonNodeOrToken ChildAt(int index)
        {
            if (index == 0)
                return OpenBraceToken;

            if (index == Sequence.NodesAndTokens.Length + 1)
                return CloseBraceToken;

            return Sequence.NodesAndTokens[index - 1];
        }

        public override void Accept(IJsonNodeVisitor visitor)
            => visitor.Visit(this);
    }

    internal sealed class JsonArrayNode : JsonValueNode
    {
        public JsonArrayNode(
            JsonToken openBracketToken,
            ImmutableArray<JsonValueNode> sequence,
            JsonToken closeBracketToken)
            : base(JsonKind.Array)
        {
            Debug.Assert(openBracketToken.Kind == JsonKind.OpenBracketToken);
            Debug.Assert(sequence != null);
            Debug.Assert(closeBracketToken.Kind == JsonKind.CloseBracketToken);

            OpenBracketToken = openBracketToken;
            Sequence = sequence;
            CloseBracketToken = closeBracketToken;
        }

        public JsonToken OpenBracketToken { get; }
        public ImmutableArray<JsonValueNode> Sequence { get; }
        public JsonToken CloseBracketToken { get; }

        internal override int ChildCount => 2 + Sequence.Length;

        internal override JsonNodeOrToken ChildAt(int index)
        {
            if (index == 0)
                return OpenBracketToken;

            if (index == Sequence.Length + 1)
                return CloseBracketToken;

            return Sequence[index - 1];
        }

        public override void Accept(IJsonNodeVisitor visitor)
            => visitor.Visit(this);
    }

    internal sealed class JsonNegativeLiteralNode : JsonValueNode
    {
        public JsonNegativeLiteralNode(JsonToken minusToken, JsonToken literalToken)
            : base(JsonKind.NegativeLiteral)
        {
            Debug.Assert(minusToken.Kind == JsonKind.MinusToken);
            MinusToken = minusToken;
            LiteralToken = literalToken;
        }

        public JsonToken MinusToken { get; }
        public JsonToken LiteralToken { get; }

        internal override int ChildCount => 2;

        internal override JsonNodeOrToken ChildAt(int index)
            => index switch
            {
                0 => MinusToken,
                1 => LiteralToken,
                _ => throw new InvalidOperationException(),
            };

        public override void Accept(IJsonNodeVisitor visitor)
            => visitor.Visit(this);
    }

    internal sealed class JsonLiteralNode(JsonToken literalToken) : JsonValueNode(JsonKind.Literal)
    {
        public JsonToken LiteralToken { get; } = literalToken;

        internal override int ChildCount => 1;

        internal override JsonNodeOrToken ChildAt(int index)
            => index switch
            {
                0 => LiteralToken,
                _ => throw new InvalidOperationException(),
            };

        public override void Accept(IJsonNodeVisitor visitor)
            => visitor.Visit(this);
    }

    /// <summary>
    /// See the note in <see cref="JsonParser"/> for why commas are stored as values for convenience.
    /// </summary>
    internal sealed class JsonCommaValueNode : JsonValueNode
    {
        public JsonCommaValueNode(JsonToken commaToken)
            : base(JsonKind.CommaValue)
        {
            Debug.Assert(commaToken.Kind == JsonKind.CommaToken);
            CommaToken = commaToken;
        }

        public JsonToken CommaToken { get; }

        internal override int ChildCount => 1;

        internal override JsonNodeOrToken ChildAt(int index)
            => index switch
            {
                0 => CommaToken,
                _ => throw new InvalidOperationException(),
            };

        public override void Accept(IJsonNodeVisitor visitor)
            => visitor.Visit(this);
    }

    internal sealed class JsonPropertyNode : JsonValueNode
    {
        public JsonPropertyNode(JsonToken nameToken, JsonToken colonToken, JsonValueNode value)
            : base(JsonKind.Property)
        {
            // Note: the name is allowed by json.net to just be a text token, not a string.  e.g. `goo: 0` as opposed to
            // `"goo": 0`.  Strict json does not allow this.
            Debug.Assert(nameToken.Kind == JsonKind.StringToken || nameToken.Kind == JsonKind.TextToken);
            Debug.Assert(colonToken.Kind == JsonKind.ColonToken);
            Debug.Assert(value != null);
            NameToken = nameToken;
            ColonToken = colonToken;
            Value = value;
        }

        public JsonToken NameToken { get; }
        public JsonToken ColonToken { get; }
        public JsonValueNode Value { get; }

        internal override int ChildCount => 3;

        internal override JsonNodeOrToken ChildAt(int index)
            => index switch
            {
                0 => NameToken,
                1 => ColonToken,
                2 => Value,
                _ => throw new InvalidOperationException(),
            };

        public override void Accept(IJsonNodeVisitor visitor)
            => visitor.Visit(this);
    }

    /// <summary>
    /// Json.net construction.  It allows things like <c>new Date(1, 2, 3)</c>.  This is not allowed in strict mode.  
    /// </summary>
    internal sealed class JsonConstructorNode : JsonValueNode
    {
        public JsonConstructorNode(
            JsonToken newKeyword,
            JsonToken nameToken,
            JsonToken openParenToken,
            ImmutableArray<JsonValueNode> sequence,
            JsonToken closeParenToken)
            : base(JsonKind.Constructor)
        {
            Debug.Assert(newKeyword.Kind == JsonKind.NewKeyword);
            Debug.Assert(nameToken.Kind == JsonKind.TextToken);
            Debug.Assert(openParenToken.Kind == JsonKind.OpenParenToken);
            Debug.Assert(sequence != null);
            Debug.Assert(closeParenToken.Kind == JsonKind.CloseParenToken);
            NewKeyword = newKeyword;
            NameToken = nameToken;
            OpenParenToken = openParenToken;
            Sequence = sequence;
            CloseParenToken = closeParenToken;
        }

        public JsonToken NewKeyword { get; }
        public JsonToken NameToken { get; }
        public JsonToken OpenParenToken { get; }
        public ImmutableArray<JsonValueNode> Sequence { get; }
        public JsonToken CloseParenToken { get; }

        internal override int ChildCount => Sequence.Length + 4;

        internal override JsonNodeOrToken ChildAt(int index)
        {
            if (index == 0)
                return NewKeyword;

            if (index == 1)
                return NameToken;

            if (index == 2)
                return OpenParenToken;

            if (index == Sequence.Length + 3)
                return CloseParenToken;

            return Sequence[index - 3];
        }

        public override void Accept(IJsonNodeVisitor visitor)
            => visitor.Visit(this);
    }
}
