﻿// 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.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.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Reflection;
using System.Text;
using Microsoft.CodeAnalysis.Collections;
using Xunit;

namespace Microsoft.CodeAnalysis.UnitTests.Collections
{
    public abstract partial class ImmutablesTestBase
    {
        /// <summary>
        /// Gets the number of operations to perform in randomized tests.
        /// </summary>
        protected static int RandomOperationsCount
        {
            get { return 100; }
        }

        internal static void AssertAreSame<T>(T expected, T actual)
        {
            if (typeof(T).GetTypeInfo().IsValueType)
            {
                Assert.Equal(expected, actual); //, message, formattingArgs);
            }
            else
            {
                Assert.Same((object?)expected, (object?)actual); //, message, formattingArgs);
            }
        }

        internal static void CollectionAssertAreEquivalent<T>(ICollection<T> expected, ICollection<T> actual)
        {
            Assert.Equal(expected.Count, actual.Count);
            foreach (var value in expected)
            {
                Assert.Contains(value, actual);
            }
        }

        protected static bool IsSame<T>(IImmutableList<T> first, IImmutableList<T> second)
        {
            if (first is ImmutableSegmentedList<T> firstSegmented
                && second is ImmutableSegmentedList<T> secondSegmented)
            {
                return firstSegmented == secondSegmented;
            }
            else if (first.GetType() != second.GetType())
            {
                // If the instances do not have the same type, they cannot be the same
                return false;
            }
            else if (first.GetType().IsValueType)
            {
                throw new NotSupportedException($"Unable to compare '{first.GetType()}' for identity.");
            }

            return first == second;
        }

        protected static bool IsSame<T>(IImmutableSet<T> first, IImmutableSet<T> second)
        {
            if (first is ImmutableSegmentedHashSet<T> firstSegmented
                && second is ImmutableSegmentedHashSet<T> secondSegmented)
            {
                return firstSegmented == secondSegmented;
            }
            else if (first.GetType() != second.GetType())
            {
                // If the instances do not have the same type, they cannot be the same
                return false;
            }
            else if (first.GetType().IsValueType)
            {
                throw new NotSupportedException($"Unable to compare '{first.GetType()}' for identity.");
            }

            return first == second;
        }

        protected static bool IsSame<TKey, TValue>(IImmutableDictionary<TKey, TValue> first, IImmutableDictionary<TKey, TValue> second)
            where TKey : notnull
        {
            if (first is ImmutableSegmentedDictionary<TKey, TValue> firstSegmented
                && second is ImmutableSegmentedDictionary<TKey, TValue> secondSegmented)
            {
                return firstSegmented == secondSegmented;
            }
            else if (first.GetType() != second.GetType())
            {
                // If the instances do not have the same type, they cannot be the same
                return false;
            }
            else if (first.GetType().IsValueType)
            {
                throw new NotSupportedException($"Unable to compare '{first.GetType()}' for identity.");
            }

            return first == second;
        }

        protected static string ToString(System.Collections.IEnumerable sequence)
        {
            var sb = new StringBuilder();
            sb.Append('{');
            int count = 0;
            foreach (object item in sequence)
            {
                if (count > 0)
                {
                    sb.Append(',');
                }

                if (count == 10)
                {
                    sb.Append("...");
                    break;
                }

                sb.Append(item);
                count++;
            }

            sb.Append('}');
            return sb.ToString();
        }

        protected static object ToStringDeferred(System.Collections.IEnumerable sequence)
        {
            return new DeferredToString(() => ToString(sequence));
        }

        protected static void ManuallyEnumerateTest<T>(IList<T> expectedResults, IEnumerator<T> enumerator)
        {
            T[] manualArray = new T[expectedResults.Count];
            int i = 0;

            Assert.Equal(default(T), enumerator.Current);

            while (enumerator.MoveNext())
            {
                manualArray[i++] = enumerator.Current;
            }

            Assert.False(enumerator.MoveNext());
            Assert.Equal(default(T), enumerator.Current);
            Assert.False(enumerator.MoveNext());
            Assert.Equal(default(T), enumerator.Current);

            Assert.Equal(expectedResults.Count, i); //, "Enumeration did not produce enough elements.");
            Assert.Equal<T>(expectedResults, manualArray);
        }

        /// <summary>
        /// Generates an array of unique values.
        /// </summary>
        /// <param name="length">The desired length of the array.</param>
        /// <returns>An array of doubles.</returns>
        protected static double[] GenerateDummyFillData(int length = 1000)
        {
            Assert.InRange(length, 0, int.MaxValue);

            int seed = unchecked((int)DateTime.Now.Ticks);

            Debug.WriteLine("Random seed {0}", seed);

            var random = new Random(seed);
            var inputs = new double[length];
            var ensureUniqueness = new HashSet<double>();
            for (int i = 0; i < inputs.Length; i++)
            {
                double input;
                do
                {
                    input = random.NextDouble();
                }
                while (!ensureUniqueness.Add(input));
                inputs[i] = input;
            }

            Assert.NotNull(inputs);
            Assert.Equal(length, inputs.Length);

            return inputs;
        }

        private class DeferredToString
        {
            private readonly Func<string> _generator;

            internal DeferredToString(Func<string> generator)
            {
                Debug.Assert(generator != null);
                _generator = generator;
            }

            public override string ToString()
            {
                return _generator();
            }
        }
    }
}
