using Lucene.Net.Attributes;
using NUnit.Framework;
using System.Collections.Generic;

namespace Lucene.Net.Support
{
    /*
     * Licensed to the Apache Software Foundation (ASF) under one or more
     * contributor license agreements.  See the NOTICE file distributed with
     * this work for additional information regarding copyright ownership.
     * The ASF licenses this file to You under the Apache License, Version 2.0
     * (the "License"); you may not use this file except in compliance with
     * the License.  You may obtain a copy of the License at
     *
     *     http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */

    [TestFixture]
    public class TestLinkedHashMap : TestHashMap
    {
        protected override HashMap<TKey, TValue> GetNewHashMap<TKey, TValue>()
        {
            return new LinkedHashMap<TKey, TValue>();
        }

        private IDictionary<FakeKey<int>, string> GetDefaultHashMap2()
        {
            var dict = GetNewHashMap<FakeKey<int>, string>();

            dict[new FakeKey<int>(2)] = "MyString";
            dict[new FakeKey<int>(0)] = "OtherString";
            dict[new FakeKey<int>(5)] = "NumberFive";
            dict[new FakeKey<int>(int.MaxValue)] = "Maximum";
            dict[null] = "NullValue";
            dict[new FakeKey<int>(int.MinValue)] = "Minimum";

            return dict;
        }

        internal class FakeKey<T>
        {
            private readonly T key;

            public FakeKey(T key)
            {
                this.key = key;
            }

            public override bool Equals(object obj)
            {
                // NOTE: This takes into consideration that value
                // types cannot be null.
                if (key == null && obj == null)
                {
                    return true;
                }

                if (!(obj is FakeKey<T>))
                {
                    return false;
                }

                return key.Equals(((FakeKey<T>)obj).key);
            }

            public override int GetHashCode()
            {
                // NOTE: This takes into consideration that value
                // types cannot be null.
                if (key == null)
                {
                    return 0; // Emulates Objects.hashcode(object) in Java
                }

                return key.GetHashCode();
            }

            public override string ToString()
            {
                // NOTE: This takes into consideration that value
                // types cannot be null.
                if (key == null)
                {
                    return "null"; // Emulates String.valueOf(object) in Java
                }

                return key.ToString();
            }
        }

        [Test, LuceneNetSpecific]
        public void TestInsertionOrderNullFirst()
        {
            var dict = GetNewHashMap<FakeKey<int>, string>();

            dict[null] = "NullValue";
            dict[new FakeKey<int>(2)] = "MyString";
            dict[new FakeKey<int>(0)] = "OtherString";
            dict[new FakeKey<int>(5)] = "NumberFive";
            dict[new FakeKey<int>(int.MaxValue)] = "Maximum";

            var expectedOrder = new List<KeyValuePair<FakeKey<int>, string>>
            {
                new KeyValuePair<FakeKey<int>, string>(null, "NullValue"),
                new KeyValuePair<FakeKey<int>, string>(new FakeKey<int>(2), "MyString"),
                new KeyValuePair<FakeKey<int>, string>(new FakeKey<int>(0), "OtherString"),
                new KeyValuePair<FakeKey<int>, string>(new FakeKey<int>(5), "NumberFive"),
                new KeyValuePair<FakeKey<int>, string>(new FakeKey<int>(int.MaxValue), "Maximum"),
            };

            AssertEnumerationOrder(expectedOrder, dict);
        }

        [Test, LuceneNetSpecific]
        public void TestInsertionOrderNullInMiddle()
        {
            var dict = GetNewHashMap<FakeKey<int>, string>();

            dict[new FakeKey<int>(2)] = "MyString";
            dict[new FakeKey<int>(0)] = "OtherString";
            dict[null] = "NullValue";
            dict[new FakeKey<int>(5)] = "NumberFive";
            dict[new FakeKey<int>(int.MaxValue)] = "Maximum";

            var expectedOrder = new List<KeyValuePair<FakeKey<int>, string>>
            {
                new KeyValuePair<FakeKey<int>, string>(new FakeKey<int>(2), "MyString"),
                new KeyValuePair<FakeKey<int>, string>(new FakeKey<int>(0), "OtherString"),
                new KeyValuePair<FakeKey<int>, string>(null, "NullValue"),
                new KeyValuePair<FakeKey<int>, string>(new FakeKey<int>(5), "NumberFive"),
                new KeyValuePair<FakeKey<int>, string>(new FakeKey<int>(int.MaxValue), "Maximum"),
            };

            AssertEnumerationOrder(expectedOrder, dict);
        }

        [Test, LuceneNetSpecific]
        public void TestInsertionOrderNullLast()
        {
            var dict = GetNewHashMap<FakeKey<int>, string>();

            dict[new FakeKey<int>(2)] = "MyString";
            dict[new FakeKey<int>(0)] = "OtherString";
            dict[new FakeKey<int>(5)] = "NumberFive";
            dict[new FakeKey<int>(int.MaxValue)] = "Maximum";
            dict[null] = "NullValue";

            var expectedOrder = new List<KeyValuePair<FakeKey<int>, string>>
            {
                new KeyValuePair<FakeKey<int>, string>(new FakeKey<int>(2), "MyString"),
                new KeyValuePair<FakeKey<int>, string>(new FakeKey<int>(0), "OtherString"),
                new KeyValuePair<FakeKey<int>, string>(new FakeKey<int>(5), "NumberFive"),
                new KeyValuePair<FakeKey<int>, string>(new FakeKey<int>(int.MaxValue), "Maximum"),
                new KeyValuePair<FakeKey<int>, string>(null, "NullValue"),
            };

            AssertEnumerationOrder(expectedOrder, dict);
        }

        [Test, LuceneNetSpecific]
        public void TestInsertionOrderNullAfterRemovingElements()
        {
            var dict = GetDefaultHashMap2();

            // Remove elements before the null value to make sure
            // we track the proper position of the null
            dict.Remove(new FakeKey<int>(5));
            dict.Remove(new KeyValuePair<FakeKey<int>, string>(new FakeKey<int>(0), "OtherString"));

            var expectedOrder = new List<KeyValuePair<FakeKey<int>, string>>
            {
                new KeyValuePair<FakeKey<int>, string>(new FakeKey<int>(2), "MyString"),
                new KeyValuePair<FakeKey<int>, string>(new FakeKey<int>(int.MaxValue), "Maximum"),
                new KeyValuePair<FakeKey<int>, string>(null, "NullValue"),
                new KeyValuePair<FakeKey<int>, string>(new FakeKey<int>(int.MinValue), "Minimum"),
            };

            AssertEnumerationOrder(expectedOrder, dict);
        }


        [Test, LuceneNetSpecific]
        public void TestInsertionOrderNullAfterRefillingBuckets()
        {
            var dict = GetDefaultHashMap2();

            // Remove elements before the null value to make sure
            // we track the proper position of the null
            dict.Remove(new FakeKey<int>(5));
            dict.Remove(new FakeKey<int>(0));

            // This has been verified that it puts the reused key before "new FakeKey<int>(int.MaxValue)"
            // in a standard Dictionary (putting it out of insertion order)
            dict.Add(new FakeKey<int>(5), "Testing");

            var expectedOrder = new List<KeyValuePair<FakeKey<int>, string>>
            {
                new KeyValuePair<FakeKey<int>, string>(new FakeKey<int>(2), "MyString"),
                new KeyValuePair<FakeKey<int>, string>(new FakeKey<int>(int.MaxValue), "Maximum"),
                new KeyValuePair<FakeKey<int>, string>(null, "NullValue"),
                new KeyValuePair<FakeKey<int>, string>(new FakeKey<int>(int.MinValue), "Minimum"),
                new KeyValuePair<FakeKey<int>, string>(new FakeKey<int>(5), "Testing"),
            };

            AssertEnumerationOrder(expectedOrder, dict);
        }

        private void AssertEnumerationOrder<TKey, TValue>(IList<KeyValuePair<TKey, TValue>> expectedOrder, IDictionary<TKey, TValue> actualOrder)
        {
            // check element order
            int expectedCount = expectedOrder.Count;
            var elementEnumerator = actualOrder.GetEnumerator();
            for (int i = 0; i < expectedCount; i++)
            {
                Assert.IsTrue(elementEnumerator.MoveNext());
                Assert.AreEqual(expectedOrder[i].Key, elementEnumerator.Current.Key);
                Assert.AreEqual(expectedOrder[i].Value, elementEnumerator.Current.Value);
            }

            Assert.IsFalse(elementEnumerator.MoveNext());
            Assert.IsFalse(elementEnumerator.MoveNext());

            // check key order
            var keyEnumerator = actualOrder.Keys.GetEnumerator();
            for (int i = 0; i < expectedCount; i++)
            {
                Assert.IsTrue(keyEnumerator.MoveNext());
                Assert.AreEqual(expectedOrder[i].Key, keyEnumerator.Current);
            }

            Assert.IsFalse(keyEnumerator.MoveNext());
            Assert.IsFalse(keyEnumerator.MoveNext());

            // check value order
            var valueEnumerator = actualOrder.Values.GetEnumerator();
            for (int i = 0; i < expectedCount; i++)
            {
                Assert.IsTrue(valueEnumerator.MoveNext());
                Assert.AreEqual(expectedOrder[i].Value, valueEnumerator.Current);
            }

            Assert.IsFalse(valueEnumerator.MoveNext());
            Assert.IsFalse(valueEnumerator.MoveNext());
        }


        [Test, LuceneNetSpecific]
        public void TestCopyTo()
        {
            var dict = GetDefaultHashMap2();

            KeyValuePair<FakeKey<int>, string>[] elements = new KeyValuePair<FakeKey<int>, string>[dict.Count];

            dict.CopyTo(elements, 0);

            // element insertion order
            var enumerator = elements.GetEnumerator();

            Assert.IsTrue(enumerator.MoveNext());
            Assert.AreEqual(new FakeKey<int>(2), ((KeyValuePair<FakeKey<int>, string>)enumerator.Current).Key);
            Assert.AreEqual("MyString", ((KeyValuePair<FakeKey<int>, string>)enumerator.Current).Value);

            Assert.IsTrue(enumerator.MoveNext());
            Assert.AreEqual(new FakeKey<int>(0), ((KeyValuePair<FakeKey<int>, string>)enumerator.Current).Key);
            Assert.AreEqual("OtherString", ((KeyValuePair<FakeKey<int>, string>)enumerator.Current).Value);

            Assert.IsTrue(enumerator.MoveNext());
            Assert.AreEqual(new FakeKey<int>(5), ((KeyValuePair<FakeKey<int>, string>)enumerator.Current).Key);
            Assert.AreEqual("NumberFive", ((KeyValuePair<FakeKey<int>, string>)enumerator.Current).Value);

            Assert.IsTrue(enumerator.MoveNext());
            Assert.AreEqual(new FakeKey<int>(int.MaxValue), ((KeyValuePair<FakeKey<int>, string>)enumerator.Current).Key);
            Assert.AreEqual("Maximum", ((KeyValuePair<FakeKey<int>, string>)enumerator.Current).Value);

            Assert.IsTrue(enumerator.MoveNext());
            Assert.AreEqual(null, ((KeyValuePair<FakeKey<int>, string>)enumerator.Current).Key);
            Assert.AreEqual("NullValue", ((KeyValuePair<FakeKey<int>, string>)enumerator.Current).Value);

            Assert.IsTrue(enumerator.MoveNext());
            Assert.AreEqual(new FakeKey<int>(int.MinValue), ((KeyValuePair<FakeKey<int>, string>)enumerator.Current).Key);
            Assert.AreEqual("Minimum", ((KeyValuePair<FakeKey<int>, string>)enumerator.Current).Value);

            Assert.IsFalse(enumerator.MoveNext());
            Assert.IsFalse(enumerator.MoveNext());
        }

        [Test, LuceneNetSpecific]
        public void TestCopyToWithOffset()
        {
            int offset = 5;
            var dict = GetDefaultHashMap2();

            KeyValuePair<FakeKey<int>, string>[] elements = new KeyValuePair<FakeKey<int>, string>[dict.Count + offset];

            dict.CopyTo(elements, offset);

            // element insertion order
            var enumerator = elements.GetEnumerator();

            for (int i = 0; i < offset; i++)
            {
                Assert.IsTrue(enumerator.MoveNext());
                Assert.AreEqual(null, ((KeyValuePair<FakeKey<int>, string>)enumerator.Current).Key);
                Assert.AreEqual(null, ((KeyValuePair<FakeKey<int>, string>)enumerator.Current).Value);
            }

            Assert.IsTrue(enumerator.MoveNext());
            Assert.AreEqual(new FakeKey<int>(2), ((KeyValuePair<FakeKey<int>, string>)enumerator.Current).Key);
            Assert.AreEqual("MyString", ((KeyValuePair<FakeKey<int>, string>)enumerator.Current).Value);

            Assert.IsTrue(enumerator.MoveNext());
            Assert.AreEqual(new FakeKey<int>(0), ((KeyValuePair<FakeKey<int>, string>)enumerator.Current).Key);
            Assert.AreEqual("OtherString", ((KeyValuePair<FakeKey<int>, string>)enumerator.Current).Value);

            Assert.IsTrue(enumerator.MoveNext());
            Assert.AreEqual(new FakeKey<int>(5), ((KeyValuePair<FakeKey<int>, string>)enumerator.Current).Key);
            Assert.AreEqual("NumberFive", ((KeyValuePair<FakeKey<int>, string>)enumerator.Current).Value);

            Assert.IsTrue(enumerator.MoveNext());
            Assert.AreEqual(new FakeKey<int>(int.MaxValue), ((KeyValuePair<FakeKey<int>, string>)enumerator.Current).Key);
            Assert.AreEqual("Maximum", ((KeyValuePair<FakeKey<int>, string>)enumerator.Current).Value);

            Assert.IsTrue(enumerator.MoveNext());
            Assert.AreEqual(null, ((KeyValuePair<FakeKey<int>, string>)enumerator.Current).Key);
            Assert.AreEqual("NullValue", ((KeyValuePair<FakeKey<int>, string>)enumerator.Current).Value);

            Assert.IsTrue(enumerator.MoveNext());
            Assert.AreEqual(new FakeKey<int>(int.MinValue), ((KeyValuePair<FakeKey<int>, string>)enumerator.Current).Key);
            Assert.AreEqual("Minimum", ((KeyValuePair<FakeKey<int>, string>)enumerator.Current).Value);

            Assert.IsFalse(enumerator.MoveNext());
            Assert.IsFalse(enumerator.MoveNext());
        }

        [Test, LuceneNetSpecific]
        public void TestContainsKeyValuePair()
        {
            var dict = GetDefaultHashMap2();

            var realTarget = new KeyValuePair<FakeKey<int>, string>(new FakeKey<int>(5), "NumberFive");
            Assert.IsTrue(dict.Contains(realTarget));

            var nullTarget = new KeyValuePair<FakeKey<int>, string>(null, "NullValue");
            Assert.IsTrue(dict.Contains(nullTarget));

            var invalidTarget = new KeyValuePair<FakeKey<int>, string>(new FakeKey<int>(543), "NullValue");
            Assert.IsFalse(dict.Contains(invalidTarget));

            dict.Remove(nullTarget);
            Assert.IsFalse(dict.Contains(nullTarget));
        }
    }
}
