// 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.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using Microsoft.CodeAnalysis.CSharp.Symbols;

namespace Microsoft.CodeAnalysis.CSharp
{
    internal enum TupleBinaryOperatorInfoKind
    {
        Single,
        NullNull,
        Multiple
    }

    /// <summary>
    /// A tree of binary operators for tuple comparisons.
    ///
    /// For <c>(a, (b, c)) == (d, (e, f))</c> we'll hold a Multiple with two elements.
    /// The first element is a Single (describing the binary operator and conversions that are involved in <c>a == d</c>).
    /// The second element is a Multiple containing two Singles (one for the <c>b == e</c> comparison and the other for <c>c == f</c>).
    /// </summary>
    internal abstract class TupleBinaryOperatorInfo
    {
        internal abstract TupleBinaryOperatorInfoKind InfoKind { get; }
        internal readonly TypeSymbol? LeftConvertedTypeOpt;
        internal readonly TypeSymbol? RightConvertedTypeOpt;
#if DEBUG
        internal abstract TreeDumperNode DumpCore();
        internal string Dump() => TreeDumper.DumpCompact(DumpCore());
#endif

        private TupleBinaryOperatorInfo(TypeSymbol? leftConvertedTypeOpt, TypeSymbol? rightConvertedTypeOpt)
        {
            LeftConvertedTypeOpt = leftConvertedTypeOpt;
            RightConvertedTypeOpt = rightConvertedTypeOpt;
        }

        /// <summary>
        /// Holds the information for an element-wise comparison (like <c>a == b</c> as part of <c>(a, ...) == (b, ...)</c>)
        /// </summary>
        internal class Single : TupleBinaryOperatorInfo
        {
            internal readonly BinaryOperatorKind Kind;
            internal readonly MethodSymbol? MethodSymbolOpt; // User-defined comparison operator, if applicable
            internal readonly TypeSymbol? ConstrainedToTypeOpt;

            internal readonly BoundValuePlaceholder? ConversionForBoolPlaceholder;
            internal readonly BoundExpression? ConversionForBool; // If a conversion to bool exists, then no operator needed. If an operator is needed, this holds the conversion for input to that operator.

            internal readonly UnaryOperatorSignature BoolOperator; // Information for op_true or op_false

            internal Single(
                TypeSymbol? leftConvertedTypeOpt,
                TypeSymbol? rightConvertedTypeOpt,
                BinaryOperatorKind kind,
                MethodSymbol? methodSymbolOpt,
                TypeSymbol? constrainedToTypeOpt,
                BoundValuePlaceholder? conversionForBoolPlaceholder,
                BoundExpression? conversionForBool,
                UnaryOperatorSignature boolOperator) : base(leftConvertedTypeOpt, rightConvertedTypeOpt)
            {
                Kind = kind;
                MethodSymbolOpt = methodSymbolOpt;
                ConstrainedToTypeOpt = constrainedToTypeOpt;
                ConversionForBoolPlaceholder = conversionForBoolPlaceholder;
                ConversionForBool = conversionForBool;
                BoolOperator = boolOperator;

                Debug.Assert(Kind.IsUserDefined() == (MethodSymbolOpt is { }));
            }

            internal override TupleBinaryOperatorInfoKind InfoKind
                => TupleBinaryOperatorInfoKind.Single;

            public override string ToString()
                => $"binaryOperatorKind: {Kind}";

#if DEBUG
            internal override TreeDumperNode DumpCore()
            {
                var sub = new List<TreeDumperNode>();
                if (MethodSymbolOpt is { })
                {
                    sub.Add(new TreeDumperNode("methodSymbolOpt", MethodSymbolOpt.ToDisplayString(), null));
                }
                sub.Add(new TreeDumperNode("leftConversion", LeftConvertedTypeOpt?.ToDisplayString(), null));
                sub.Add(new TreeDumperNode("rightConversion", RightConvertedTypeOpt?.ToDisplayString(), null));

                return new TreeDumperNode("nested", Kind, sub);
            }
#endif
        }

        /// <summary>
        /// Holds the information for a tuple comparison, either at the top-level (like <c>(a, b) == ...</c>) or nested (like <c>(..., (a, b)) == (..., ...)</c>).
        /// </summary>
        internal class Multiple : TupleBinaryOperatorInfo
        {
            internal readonly ImmutableArray<TupleBinaryOperatorInfo> Operators;

            internal static readonly Multiple ErrorInstance =
                new Multiple(operators: ImmutableArray<TupleBinaryOperatorInfo>.Empty, leftConvertedTypeOpt: null, rightConvertedTypeOpt: null);

            internal Multiple(ImmutableArray<TupleBinaryOperatorInfo> operators, TypeSymbol? leftConvertedTypeOpt, TypeSymbol? rightConvertedTypeOpt)
                : base(leftConvertedTypeOpt, rightConvertedTypeOpt)
            {
                Debug.Assert(leftConvertedTypeOpt is null || leftConvertedTypeOpt.StrippedType().IsTupleType);
                Debug.Assert(rightConvertedTypeOpt is null || rightConvertedTypeOpt.StrippedType().IsTupleType);
                Debug.Assert(!operators.IsDefault);
                Debug.Assert(operators.IsEmpty || operators.Length > 1); // an empty array is used for error cases, otherwise tuples must have cardinality > 1

                Operators = operators;
            }

            internal override TupleBinaryOperatorInfoKind InfoKind
                => TupleBinaryOperatorInfoKind.Multiple;

#if DEBUG
            internal override TreeDumperNode DumpCore()
            {
                var sub = new List<TreeDumperNode>();
                sub.Add(new TreeDumperNode($"nestedOperators[{Operators.Length}]", null,
                    Operators.SelectAsArray(c => c.DumpCore())));

                return new TreeDumperNode("nested", null, sub);
            }
#endif
        }

        /// <summary>
        /// Represents an element-wise null/null comparison.
        /// For instance, <c>(null, ...) == (null, ...)</c>.
        /// </summary>
        internal class NullNull : TupleBinaryOperatorInfo
        {
            internal readonly BinaryOperatorKind Kind;

            internal NullNull(BinaryOperatorKind kind)
                : base(leftConvertedTypeOpt: null, rightConvertedTypeOpt: null)
            {
                Kind = kind;
            }

            internal override TupleBinaryOperatorInfoKind InfoKind
                => TupleBinaryOperatorInfoKind.NullNull;

#if DEBUG
            internal override TreeDumperNode DumpCore()
            {
                return new TreeDumperNode("nullnull", value: Kind, children: null);
            }
#endif
        }
    }
}
