﻿// 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.

// NOTE: This code is derived from an implementation originally in dotnet/runtime:
// https://github.com/dotnet/runtime/blob/v5.0.2/src/libraries/System.Collections.Immutable/tests/ImmutableTestBase.nonnetstandard.cs
//
// See the commentary in https://github.com/dotnet/roslyn/pull/50156 for notes on incorporating changes made to the
// reference implementation.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Xunit;

namespace Microsoft.CodeAnalysis.UnitTests.Collections
{
    public abstract partial class ImmutablesTestBase
    {
        /// <summary>
        /// Tests the EqualsStructurally public method and the IStructuralEquatable.Equals method.
        /// </summary>
        /// <typeparam name="TCollection">The type of tested collection.</typeparam>
        /// <typeparam name="TElement">The type of element stored in the collection.</typeparam>
        /// <param name="objectUnderTest">An instance of the collection to test, which must have at least two elements.</param>
        /// <param name="additionalItem">A unique item that does not already exist in <paramref name="objectUnderTest" />.</param>
        /// <param name="equalsStructurally">A delegate that invokes the EqualsStructurally method.</param>
        protected static void StructuralEqualityHelper<TCollection, TElement>(TCollection objectUnderTest, TElement additionalItem, Func<TCollection, IEnumerable<TElement>?, bool> equalsStructurally)
            where TCollection : class, IEnumerable<TElement>
        {
            if (objectUnderTest is null)
                throw new ArgumentNullException(nameof(objectUnderTest));
            if (objectUnderTest.Count() < 2)
                throw new ArgumentException("Collection must contain at least two elements.", nameof(objectUnderTest));
            if (equalsStructurally is null)
                throw new ArgumentNullException(nameof(equalsStructurally));

            var structuralEquatableUnderTest = objectUnderTest as IStructuralEquatable;
            var enumerableUnderTest = (IEnumerable<TElement>)objectUnderTest;

            var equivalentSequence = objectUnderTest.ToList();
            var shorterSequence = equivalentSequence.Take(equivalentSequence.Count() - 1);
            var longerSequence = equivalentSequence.Concat(new[] { additionalItem });
            var differentSequence = shorterSequence.Concat(new[] { additionalItem });
            var nonUniqueSubsetSequenceOfSameLength = shorterSequence.Concat(shorterSequence.Take(1));

            var testValues = new IEnumerable<TElement>?[] {
                objectUnderTest,
                null,
                Enumerable.Empty<TElement>(),
                equivalentSequence,
                longerSequence,
                shorterSequence,
                nonUniqueSubsetSequenceOfSameLength,
            };

            foreach (var value in testValues)
            {
                bool expectedResult = value != null && Enumerable.SequenceEqual(objectUnderTest, value);

                if (structuralEquatableUnderTest != null)
                {
                    Assert.Equal(expectedResult, structuralEquatableUnderTest.Equals(value, null!));

                    if (value != null)
                    {
                        Assert.Equal(
                            expectedResult,
                            structuralEquatableUnderTest.Equals(new NonGenericEnumerableWrapper(value), null!));
                    }
                }

                Assert.Equal(expectedResult, equalsStructurally(objectUnderTest, value));
            }
        }

        private class NonGenericEnumerableWrapper : IEnumerable
        {
            private readonly IEnumerable _enumerable;

            internal NonGenericEnumerableWrapper(IEnumerable enumerable)
            {
                _enumerable = enumerable ?? throw new ArgumentNullException(nameof(enumerable));
            }

            public IEnumerator GetEnumerator()
            {
                return _enumerable.GetEnumerator();
            }
        }
    }
}
