// 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;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using Xunit;

namespace Microsoft.Extensions.Internal
{
    public class ClosedGenericMatcherTest
    {
        // queryType, interfaceType, expectedResult
        public static TheoryData<Type, Type, Type> ExtractGenericInterfaceDataSet
        {
            get
            {
                return new TheoryData<Type, Type, Type>
                {
                    // Closed generic types that match given open generic type.
                    {
                        typeof(IEnumerable<BaseClass>),
                        typeof(IEnumerable<>),
                        typeof(IEnumerable<BaseClass>)
                    },
                    {
                        typeof(IReadOnlyList<int>),
                        typeof(IReadOnlyList<>),
                        typeof(IReadOnlyList<int>)
                    },
                    {
                        typeof(KeyValuePair<string, object>),
                        typeof(KeyValuePair<,>),
                        typeof(KeyValuePair<string, object>)
                    },
                    // Closed generic interfaces that implement sub-interface of given open generic type.
                    {
                        typeof(ICollection<BaseClass>),
                        typeof(IEnumerable<>),
                        typeof(IEnumerable<BaseClass>)
                    },
                    {
                        typeof(IReadOnlyList<int>),
                        typeof(IEnumerable<>),
                        typeof(IEnumerable<int>)
                    },
                    {
                        typeof(IDictionary<string, object>),
                        typeof(IEnumerable<>),
                        typeof(IEnumerable<KeyValuePair<string, object>>)
                    },
                    // Class that implements closed generic based on given open generic interface.
                    {
                        typeof(BaseClass),
                        typeof(IDictionary<,>),
                        typeof(IDictionary<string, object>)
                    },
                    {
                        typeof(BaseClass),
                        typeof(IEquatable<>),
                        typeof(IEquatable<BaseClass>)
                    },
                    {
                        typeof(BaseClass),
                        typeof(ICollection<>),
                        typeof(ICollection<KeyValuePair<string, object>>)
                    },
                    // Derived class that implements closed generic based on given open generic interface.
                    {
                        typeof(DerivedClass),
                        typeof(IDictionary<,>),
                        typeof(IDictionary<string, object>)
                    },
                    {
                        typeof(DerivedClass),
                        typeof(IEquatable<>),
                        typeof(IEquatable<BaseClass>)
                    },
                    {
                        typeof(DerivedClass),
                        typeof(ICollection<>),
                        typeof(ICollection<KeyValuePair<string, object>>)
                    },
                    // Derived class that also implements another interface.
                    {
                        typeof(DerivedClassWithComparable),
                        typeof(IDictionary<,>),
                        typeof(IDictionary<string, object>)
                    },
                    {
                        typeof(DerivedClassWithComparable),
                        typeof(IEquatable<>),
                        typeof(IEquatable<BaseClass>)
                    },
                    {
                        typeof(DerivedClassWithComparable),
                        typeof(ICollection<>),
                        typeof(ICollection<KeyValuePair<string, object>>)
                    },
                    {
                        typeof(DerivedClassWithComparable),
                        typeof(IComparable<>),
                        typeof(IComparable<DerivedClassWithComparable>)
                    },
                    // Derived class using system implementation.
                    {
                        typeof(DerivedClassFromSystemImplementation),
                        typeof(ICollection<>),
                        typeof(ICollection<BaseClass>)
                    },
                    {
                        typeof(DerivedClassFromSystemImplementation),
                        typeof(IReadOnlyList<>),
                        typeof(IReadOnlyList<BaseClass>)
                    },
                    {
                        typeof(DerivedClassFromSystemImplementation),
                        typeof(IEnumerable<>),
                        typeof(IEnumerable<BaseClass>)
                    },
                    // Not given an open generic type.
                    {
                        typeof(IEnumerable<BaseClass>),
                        typeof(IEnumerable<BaseClass>),
                        null
                    },
                    {
                        typeof(IEnumerable<BaseClass>),
                        typeof(IEnumerable),
                        null
                    },
                    {
                        typeof(IReadOnlyList<int>),
                        typeof(BaseClass),
                        null
                    },
                    {
                        typeof(KeyValuePair<,>),
                        typeof(KeyValuePair<string, object>),
                        null
                    },
                    // Not a match.
                    {
                        typeof(IEnumerable<BaseClass>),
                        typeof(IReadOnlyList<>),
                        null
                    },
                    {
                        typeof(IList<int>),
                        typeof(IReadOnlyList<>),
                        null
                    },
                    {
                        typeof(IDictionary<string, object>),
                        typeof(KeyValuePair<,>),
                        null
                    },
                };
            }
        }

        [Theory]
        [MemberData(nameof(ExtractGenericInterfaceDataSet))]
        public void ExtractGenericInterface_ReturnsExpectedType(
            Type queryType,
            Type interfaceType,
            Type expectedResult)
        {
            // Arrange & Act
            var result = ClosedGenericMatcher.ExtractGenericInterface(queryType, interfaceType);

            // Assert
            Assert.Equal(expectedResult, result);
        }

        // IEnumerable<int> is preferred because it is defined on the more-derived type.
        [Fact]
        public void ExtractGenericInterface_MultipleDefinitionsInherited()
        {
            // Arrange
            var type = typeof(TwoIEnumerableImplementationsInherited);

            // Act
            var result = ClosedGenericMatcher.ExtractGenericInterface(type, typeof(IEnumerable<>));

            // Sort
            Assert.Equal(typeof(IEnumerable<int>), result);
        }

        // IEnumerable<int> is preferred because we sort by Ordinal on the full name.
        [Fact]
        public void ExtractGenericInterface_MultipleDefinitionsOnSameType()
        {
            // Arrange
            var type = typeof(TwoIEnumerableImplementationsOnSameClass);

            // Act
            var result = ClosedGenericMatcher.ExtractGenericInterface(type, typeof(IEnumerable<>));

            // Sort
            Assert.Equal(typeof(IEnumerable<int>), result);
        }

        private class TwoIEnumerableImplementationsOnSameClass : IEnumerable<string>, IEnumerable<int>
        {
            IEnumerator<int> IEnumerable<int>.GetEnumerator()
            {
                throw new NotImplementedException();
            }

            IEnumerator IEnumerable.GetEnumerator()
            {
                throw new NotImplementedException();
            }

            IEnumerator<string> IEnumerable<string>.GetEnumerator()
            {
                throw new NotImplementedException();
            }
        }

        private class TwoIEnumerableImplementationsInherited : List<int>, IEnumerable<string>
        {
            IEnumerator<string> IEnumerable<string>.GetEnumerator()
            {
                throw new NotImplementedException();
            }

            IEnumerator IEnumerable.GetEnumerator()
            {
                throw new NotImplementedException();
            }
        }

        private class BaseClass : IDictionary<string, object>, IEquatable<BaseClass>
        {
            object IDictionary<string, object>.this[string key]
            {
                get
                {
                    throw new NotImplementedException();
                }

                set
                {
                    throw new NotImplementedException();
                }
            }

            int ICollection<KeyValuePair<string, object>>.Count
            {
                get
                {
                    throw new NotImplementedException();
                }
            }

            bool ICollection<KeyValuePair<string, object>>.IsReadOnly
            {
                get
                {
                    throw new NotImplementedException();
                }
            }

            ICollection<string> IDictionary<string, object>.Keys
            {
                get
                {
                    throw new NotImplementedException();
                }
            }

            ICollection<object> IDictionary<string, object>.Values
            {
                get
                {
                    throw new NotImplementedException();
                }
            }

            public bool Equals(BaseClass other)
            {
                throw new NotImplementedException();
            }

            void ICollection<KeyValuePair<string, object>>.Add(KeyValuePair<string, object> item)
            {
                throw new NotImplementedException();
            }

            void IDictionary<string, object>.Add(string key, object value)
            {
                throw new NotImplementedException();
            }

            void ICollection<KeyValuePair<string, object>>.Clear()
            {
                throw new NotImplementedException();
            }

            bool ICollection<KeyValuePair<string, object>>.Contains(KeyValuePair<string, object> item)
            {
                throw new NotImplementedException();
            }

            bool IDictionary<string, object>.ContainsKey(string key)
            {
                throw new NotImplementedException();
            }

            void ICollection<KeyValuePair<string, object>>.CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
            {
                throw new NotImplementedException();
            }

            IEnumerator IEnumerable.GetEnumerator()
            {
                throw new NotImplementedException();
            }

            IEnumerator<KeyValuePair<string, object>> IEnumerable<KeyValuePair<string, object>>.GetEnumerator()
            {
                throw new NotImplementedException();
            }

            bool ICollection<KeyValuePair<string, object>>.Remove(KeyValuePair<string, object> item)
            {
                throw new NotImplementedException();
            }

            bool IDictionary<string, object>.Remove(string key)
            {
                throw new NotImplementedException();
            }

            bool IDictionary<string, object>.TryGetValue(string key, out object value)
            {
                throw new NotImplementedException();
            }
        }

        private class DerivedClass : BaseClass
        {
        }

        private class DerivedClassWithComparable : DerivedClass, IComparable<DerivedClassWithComparable>
        {
            public int CompareTo(DerivedClassWithComparable other)
            {
                throw new NotImplementedException();
            }
        }

        private class DerivedClassFromSystemImplementation : Collection<BaseClass>
        {
        }
    }
}