// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.Formats.Nrbf;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using PresentationCore.Tests.TestUtilities;

namespace PresentationCore.Tests.BinaryFormat;
#pragma warning disable CS0618
public class HashtableTests
{
    [Fact]
    public void HashTable_GetObjectData()
    {
        Hashtable hashtable = new()
        {
            { "This", "That" }
        };

        // The converter isn't used for this scenario and can be a no-op.
#pragma warning disable SYSLIB0050 // Type or member is obsolete
        SerializationInfo info = new(typeof(Hashtable), FormatterConverterStub.Instance);
#pragma warning restore SYSLIB0050
#pragma warning disable SYSLIB0051 // Type or member is obsolete
        hashtable.GetObjectData(info, default);
#pragma warning restore SYSLIB0051
        info.MemberCount.Should().Be(7);

        var enumerator = info.GetEnumerator();

        enumerator.MoveNext().Should().BeTrue();
        enumerator.Current.Name.Should().Be("LoadFactor");
        enumerator.Current.Value.Should().Be(0.72f);

        enumerator.MoveNext().Should().BeTrue();
        enumerator.Current.Name.Should().Be("Version");
        enumerator.Current.Value.Should().Be(1);

        enumerator.MoveNext().Should().BeTrue();
        enumerator.Current.Name.Should().Be("Comparer");
        enumerator.Current.Value.Should().BeNull();

        enumerator.MoveNext().Should().BeTrue();
        enumerator.Current.Name.Should().Be("HashCodeProvider");
        enumerator.Current.Value.Should().BeNull();

        enumerator.MoveNext().Should().BeTrue();
        enumerator.Current.Name.Should().Be("HashSize");
        enumerator.Current.Value.Should().Be(3);

        enumerator.MoveNext().Should().BeTrue();
        enumerator.Current.Name.Should().Be("Keys");
        enumerator.Current.Value.Should().BeEquivalentTo(new object[] { "This" });

        enumerator.MoveNext().Should().BeTrue();
        enumerator.Current.Name.Should().Be("Values");
        enumerator.Current.Value.Should().BeEquivalentTo(new object[] { "That" });
    }

    [Fact]
    public void HashTable_CustomComparer_DoesNotRead()
    {
        Hashtable hashtable = new(new CustomHashCodeProvider(), StringComparer.OrdinalIgnoreCase)
        {
            { "This", "That" }
        };

        SerializationRecord format = hashtable.SerializeAndParse();
        format.TryGetPrimitiveHashtable(out object? deserialized).Should().BeFalse();
        deserialized.Should().BeNull();
    }


    [Serializable]
    public class CustomHashCodeProvider : IHashCodeProvider
    {
        public int GetHashCode(object obj) => HashCode.Combine(obj);
    }

    [Fact]
    public void BinaryFormatWriter_WriteCustomComparerfails()
    {
        Hashtable hashtable = new(new CustomHashCodeProvider(), StringComparer.OrdinalIgnoreCase)
        {
            { "This", "That" }
        };

        using MemoryStream stream = new();
        BinaryFormatWriter.TryWriteHashtable(stream, hashtable).Should().BeFalse();
        stream.Position.Should().Be(0);
    }
#pragma warning restore CS0618
    [Theory]
    [MemberData(nameof(Hashtables_TestData))]
    public void BinaryFormatWriter_WriteHashtables(Hashtable hashtable)
    {
        using MemoryStream stream = new();
        BinaryFormatWriter.WritePrimitiveHashtable(stream, hashtable);
        stream.Position = 0;

        using var formatterScope = new BinaryFormatterScope(enable: true);
#pragma warning disable SYSLIB0011 // Type or member is obsolete
        BinaryFormatter formatter = new();
        Hashtable deserialized = (Hashtable)formatter.Deserialize(stream);
#pragma warning restore SYSLIB0011

        deserialized.Count.Should().Be(hashtable.Count);
        foreach (object? key in hashtable.Keys)
        {
            deserialized[key].Should().Be(hashtable[key]);
        }
    }

    [Theory]
    [MemberData(nameof(Hashtables_UnsupportedTestData))]
    public void BinaryFormatWriter_WriteUnsupportedHashtables(Hashtable hashtable)
    {
        using MemoryStream stream = new();
        Action action = () => BinaryFormatWriter.WritePrimitiveHashtable(stream, hashtable);
        action.Should().Throw<ArgumentException>();
    }

    [Theory]
    [MemberData(nameof(Hashtables_TestData))]
    public void BinaryFormattedObjectExtensions_TryGetPrimitiveHashtable(Hashtable hashtable)
    {
        SerializationRecord format = hashtable.SerializeAndParse();
        format.TryGetPrimitiveHashtable(out object? deserialized).Should().BeTrue();

        ((Hashtable)deserialized!).Count.Should().Be(hashtable.Count);
        foreach (object? key in hashtable.Keys)
        {
            ((Hashtable)deserialized)[key].Should().Be(hashtable[key]);
        }
    }

    [Theory]
    [MemberData(nameof(Hashtables_TestData))]
    public void RoundTripHashtables(Hashtable hashtable)
    {
        using MemoryStream stream = new();
        BinaryFormatWriter.WritePrimitiveHashtable(stream, hashtable);
        stream.Position = 0;

        SerializationRecord format = NrbfDecoder.Decode(stream);
        format.TryGetPrimitiveHashtable(out object? deserialized).Should().BeTrue();
        ((Hashtable)deserialized!).Count.Should().Be(hashtable.Count);
        foreach (object? key in hashtable.Keys)
        {
            ((Hashtable)deserialized)[key].Should().Be(hashtable[key]);
        }
    }

    public static TheoryData<Hashtable> Hashtables_TestData => new()
    {
        new Hashtable(),
        new Hashtable()
        {
            { "This", "That" }
        },
        new Hashtable()
        {
            { "Meaning", 42 }
        },
        new Hashtable()
        {
            { 42, 42 }
        },
        new Hashtable()
        {
            { 42, 42 },
            { 43, 42 }
        },
        new Hashtable()
        {
            { "Hastings", new DateTime(1066, 10, 14) }
        },
        new Hashtable()
        {
            { "Decimal", decimal.MaxValue }
        },
        new Hashtable()
        {
            { "This", "That" },
            { "TheOther", "This" },
            { "That", "This" }
        },
        new Hashtable()
        {
            { "Yowza", null },
            { "Youza", null },
            { "Meeza", null }
        },
        new Hashtable()
        {
            { "Yowza", null },
            { "Youza", "Binks" },
            { "Meeza", null }
        },
        new Hashtable()
        {
            { "Yowza", "Binks" },
            { "Youza", "Binks" },
            { "Meeza", null }
        },
        new Hashtable()
        {
            { decimal.MinValue, decimal.MaxValue },
            { float.MinValue, float.MaxValue },
            { DateTime.MinValue, DateTime.MaxValue },
            { TimeSpan.MinValue, TimeSpan.MaxValue }
        },
        // Stress the string interning
        MakeRepeatedHashtable(50, "Ditto"),
        MakeRepeatedHashtable(100, "..."),
        // Cross over into ObjectNullMultiple
        MakeRepeatedHashtable(255, null),
        MakeRepeatedHashtable(256, null),
        MakeRepeatedHashtable(257, null)
    };

    public static TheoryData<Hashtable> Hashtables_UnsupportedTestData => new()
    {
        new Hashtable()
        {
            { new object(), new object() }
        },
        new Hashtable()
        {
            { "Foo", new object() }
        },
        new Hashtable()
        {
            { "Foo", new System.Drawing.Point() }
        },
        new Hashtable()
        {
            { "Foo", new PointF() }
        },
        new Hashtable()
        {
            { "Foo", (nint)42 }
        },
    };

    private static Hashtable MakeRepeatedHashtable(int countOfEntries, object? value)
    {
        Hashtable result = new(countOfEntries);
        for (int i = 1; i <= countOfEntries; i++)
        {
            result.Add($"Entry{i}", value);
        }

        return result;
    }

// #pragma warning disable SYSLIB0050 // Type or member is obsolete
//     private sealed class FormatterConverterStub : IFormatterConverter
//     {
//         public static IFormatterConverter Instance { get; } = new FormatterConverterStub();
// #pragma warning restore SYSLIB0050

//         public object Convert(object value, Type type) => throw new NotImplementedException();
//         public object Convert(object value, TypeCode typeCode) => throw new NotImplementedException();
//         public bool ToBoolean(object value) => throw new NotImplementedException();
//         public byte ToByte(object value) => throw new NotImplementedException();
//         public char ToChar(object value) => throw new NotImplementedException();
//         public DateTime ToDateTime(object value) => throw new NotImplementedException();
//         public decimal ToDecimal(object value) => throw new NotImplementedException();
//         public double ToDouble(object value) => throw new NotImplementedException();
//         public short ToInt16(object value) => throw new NotImplementedException();
//         public int ToInt32(object value) => throw new NotImplementedException();
//         public long ToInt64(object value) => throw new NotImplementedException();
//         public sbyte ToSByte(object value) => throw new NotImplementedException();
//         public float ToSingle(object value) => throw new NotImplementedException();
//         public string? ToString(object value) => throw new NotImplementedException();
//         public ushort ToUInt16(object value) => throw new NotImplementedException();
//         public uint ToUInt32(object value) => throw new NotImplementedException();
//         public ulong ToUInt64(object value) => throw new NotImplementedException();
//     }
}
