// 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.Generic;
using System.Configuration.Assemblies;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using Microsoft.Build.BackEnd;
using Microsoft.Build.Exceptions;
using Microsoft.Build.Framework;
using Microsoft.Build.Framework.BuildException;
using Shouldly;
using Xunit;

#nullable disable

namespace Microsoft.Build.UnitTests.BackEnd
{
    /// <summary>
    /// Tests for the NodePacketTranslators
    /// </summary>
    public class BinaryTranslator_Tests
    {
        static BinaryTranslator_Tests()
        {
            SerializationContractInitializer.Initialize();
        }

        /// <summary>
        /// Tests the SerializationMode property
        /// </summary>
        [Fact]
        public void TestSerializationMode()
        {
            MemoryStream stream = new MemoryStream();
            using ITranslator readTranslator = BinaryTranslator.GetReadTranslator(stream, InterningBinaryReader.PoolingBuffer);
            Assert.Equal(TranslationDirection.ReadFromStream, readTranslator.Mode);

            using ITranslator writeTranslator = BinaryTranslator.GetWriteTranslator(stream);
            Assert.Equal(TranslationDirection.WriteToStream, writeTranslator.Mode);
        }

        /// <summary>
        /// Tests serializing bools.
        /// </summary>
        [Fact]
        public void TestSerializeBool()
        {
            HelperTestSimpleType(false, true);
            HelperTestSimpleType(true, false);
        }

        /// <summary>
        /// Tests serializing bytes.
        /// </summary>
        [Fact]
        public void TestSerializeByte()
        {
            byte val = 0x55;
            HelperTestSimpleType((byte)0, val);
            HelperTestSimpleType(val, (byte)0);
        }

        /// <summary>
        /// Tests serializing shorts.
        /// </summary>
        [Fact]
        public void TestSerializeShort()
        {
            short val = 0x55AA;
            HelperTestSimpleType((short)0, val);
            HelperTestSimpleType(val, (short)0);
        }

        /// <summary>
        /// Tests serializing longs.
        /// </summary>
        [Fact]
        public void TestSerializeLong()
        {
            long val = 0x55AABBCCDDEE;
            HelperTestSimpleType((long)0, val);
            HelperTestSimpleType(val, (long)0);
        }

        /// <summary>
        /// Tests serializing doubles.
        /// </summary>
        [Fact]
        public void TestSerializeDouble()
        {
            double val = 3.1416;
            HelperTestSimpleType((double)0, val);
            HelperTestSimpleType(val, (double)0);
        }

        /// <summary>
        /// Tests serializing TimeSpan.
        /// </summary>
        [Fact]
        public void TestSerializeTimeSpan()
        {
            TimeSpan val = TimeSpan.FromMilliseconds(123);
            HelperTestSimpleType(TimeSpan.Zero, val);
            HelperTestSimpleType(val, TimeSpan.Zero);
        }

        /// <summary>
        /// Tests serializing ints.
        /// </summary>
        [Fact]
        public void TestSerializeInt()
        {
            int val = 0x55AA55AA;
            HelperTestSimpleType((int)0, val);
            HelperTestSimpleType(val, (int)0);
        }

        /// <summary>
        /// Tests serializing strings.
        /// </summary>
        [Fact]
        public void TestSerializeString()
        {
            HelperTestSimpleType("foo", null);
            HelperTestSimpleType("", null);
            HelperTestSimpleType(null, null);
        }

        /// <summary>
        /// Tests serializing string arrays.
        /// </summary>
        [Fact]
        public void TestSerializeStringArray()
        {
            HelperTestArray(Array.Empty<string>(), StringComparer.Ordinal);
            HelperTestArray(new string[] { "foo", "bar" }, StringComparer.Ordinal);
            HelperTestArray(null, StringComparer.Ordinal);
        }

        /// <summary>
        /// Tests serializing string arrays.
        /// </summary>
        [Fact]
        public void TestSerializeStringList()
        {
            HelperTestList(new List<string>(), StringComparer.Ordinal);
            List<string> twoItems = new List<string>(2);
            twoItems.Add("foo");
            twoItems.Add("bar");
            HelperTestList(twoItems, StringComparer.Ordinal);
            HelperTestList(null, StringComparer.Ordinal);
        }

        /// <summary>
        /// Tests serializing DateTimes.
        /// </summary>
        [Fact]
        public void TestSerializeDateTime()
        {
            HelperTestSimpleType(new DateTime(), DateTime.Now);
            HelperTestSimpleType(DateTime.Now, new DateTime());
        }

        /// <summary>
        /// Tests serializing enums.
        /// </summary>
        [Fact]
        public void TestSerializeEnum()
        {
            TranslationDirection value = TranslationDirection.ReadFromStream;
            TranslationHelpers.GetWriteTranslator().TranslateEnum(ref value, (int)value);

            TranslationDirection deserializedValue = TranslationDirection.WriteToStream;
            TranslationHelpers.GetReadTranslator().TranslateEnum(ref deserializedValue, (int)deserializedValue);

            Assert.Equal(value, deserializedValue);
        }

        [Fact]
        public void TestSerializeException()
        {
            Exception value = new ArgumentNullException("The argument was null");
            TranslationHelpers.GetWriteTranslator().TranslateException(ref value);

            Exception deserializedValue = null;
            TranslationHelpers.GetReadTranslator().TranslateException(ref deserializedValue);

            Assert.True(TranslationHelpers.CompareExceptions(value, deserializedValue, out string diffReason), diffReason);
        }

        [Fact]
        public void TestSerializeException_NestedWithStack()
        {
            Exception value = null;
            try
            {
                // Intentionally throw a nested exception with a stack trace.
                value = value.InnerException;
            }
            catch (Exception e)
            {
                value = new ArgumentNullException("The argument was null", e);
            }

            TranslationHelpers.GetWriteTranslator().TranslateException(ref value);

            Exception deserializedValue = null;
            TranslationHelpers.GetReadTranslator().TranslateException(ref deserializedValue);

            Assert.True(TranslationHelpers.CompareExceptions(value, deserializedValue, out string diffReason), diffReason);
        }

        [Fact]
        public void TestSerializeBuildException_NestedWithStack()
        {
            Exception value = null;
            try
            {
                throw new InvalidProjectFileException("sample message");
            }
            catch (Exception e)
            {
                try
                {
                    throw new ArgumentNullException("The argument was null", e);
                }
                catch (Exception exception)
                {
                    value = new InternalErrorException("Another message", exception);
                }
            }

            Assert.NotNull(value);
            TranslationHelpers.GetWriteTranslator().TranslateException(ref value);

            Exception deserializedValue = null;
            TranslationHelpers.GetReadTranslator().TranslateException(ref deserializedValue);

            Assert.True(TranslationHelpers.CompareExceptions(value, deserializedValue, out string diffReason), diffReason);
        }

        public static IEnumerable<object[]> GetBuildExceptionsAsTestData()
            => AppDomain
                .CurrentDomain
                .GetAssemblies()
                // TaskHost is copying code files - so has a copy of types with identical names.
                .Where(a => !a.FullName!.StartsWith("MSBuildTaskHost", StringComparison.CurrentCultureIgnoreCase))
                .SelectMany(s => s.GetTypes())
                .Where(BuildExceptionSerializationHelper.IsSupportedExceptionType)
                .Select(t => new object[] { t });

        [Theory]
        [MemberData(nameof(GetBuildExceptionsAsTestData))]
        public void TestSerializationOfBuildExceptions(Type exceptionType)
        {
            Exception e = (Exception)Activator.CreateInstance(exceptionType, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.CreateInstance | BindingFlags.Instance, null, new object[]{"msg", new GenericBuildTransferredException() }, System.Globalization.CultureInfo.CurrentCulture);
            Exception remote;
            try
            {
                throw e;
            }
            catch (Exception exception)
            {
                remote = exception;
            }

            Assert.NotNull(remote);
            TranslationHelpers.GetWriteTranslator().TranslateException(ref remote);

            Exception deserializedValue = null;
            TranslationHelpers.GetReadTranslator().TranslateException(ref deserializedValue);

            Assert.True(TranslationHelpers.CompareExceptions(remote, deserializedValue, out string diffReason, true), $"Exception type {exceptionType.FullName} not properly de/serialized: {diffReason}");
        }

        [Fact]
        public void TestInvalidProjectFileException_NestedWithStack()
        {
            Exception value = null;
            try
            {
                throw new InvalidProjectFileException("sample message", new InternalErrorException("Another message"));
            }
            catch (Exception e)
            {
                value = e;
            }

            TranslationHelpers.GetWriteTranslator().TranslateException(ref value);

            Exception deserializedValue = null;
            TranslationHelpers.GetReadTranslator().TranslateException(ref deserializedValue);

            Assert.True(TranslationHelpers.CompareExceptions(value, deserializedValue, out string diffReason, true), diffReason);
        }

        /// <summary>
        /// Tests serializing an object with a default constructor.
        /// </summary>
        [Fact]
        public void TestSerializeINodePacketSerializable()
        {
            DerivedClass value = new DerivedClass(1, 2);
            TranslationHelpers.GetWriteTranslator().Translate(ref value);

            DerivedClass deserializedValue = null;
            TranslationHelpers.GetReadTranslator().Translate(ref deserializedValue);

            Assert.Equal(value.BaseValue, deserializedValue.BaseValue);
            Assert.Equal(value.DerivedValue, deserializedValue.DerivedValue);
        }

        /// <summary>
        /// Tests serializing an object with a default constructor passed as null.
        /// </summary>
        [Fact]
        public void TestSerializeINodePacketSerializableNull()
        {
            DerivedClass value = null;
            TranslationHelpers.GetWriteTranslator().Translate(ref value);

            DerivedClass deserializedValue = null;
            TranslationHelpers.GetReadTranslator().Translate(ref deserializedValue);

            Assert.Equal(value, deserializedValue);
        }

        /// <summary>
        /// Tests serializing an object requiring a factory to construct.
        /// </summary>
        [Fact]
        public void TestSerializeWithFactory()
        {
            BaseClass value = new BaseClass(1);
            TranslationHelpers.GetWriteTranslator().Translate(ref value, BaseClass.FactoryForDeserialization);

            BaseClass deserializedValue = null;
            TranslationHelpers.GetReadTranslator().Translate(ref deserializedValue, BaseClass.FactoryForDeserialization);

            Assert.Equal(value.BaseValue, deserializedValue.BaseValue);
        }

        /// <summary>
        /// Tests serializing an object requiring a factory to construct, passing null for the value.
        /// </summary>
        [Fact]
        public void TestSerializeWithFactoryNull()
        {
            BaseClass value = null;
            TranslationHelpers.GetWriteTranslator().Translate(ref value, BaseClass.FactoryForDeserialization);

            BaseClass deserializedValue = null;
            TranslationHelpers.GetReadTranslator().Translate(ref deserializedValue, BaseClass.FactoryForDeserialization);

            Assert.Equal(value, deserializedValue);
        }

        /// <summary>
        /// Tests serializing an array of objects with default constructors.
        /// </summary>
        [Fact]
        public void TestSerializeArray()
        {
            DerivedClass[] value = new DerivedClass[] { new DerivedClass(1, 2), new DerivedClass(3, 4) };
            TranslationHelpers.GetWriteTranslator().TranslateArray(ref value);

            DerivedClass[] deserializedValue = null;
            TranslationHelpers.GetReadTranslator().TranslateArray(ref deserializedValue);

            Assert.True(TranslationHelpers.CompareCollections(value, deserializedValue, DerivedClass.Comparer));
        }

        /// <summary>
        /// Tests serializing an array of objects with default constructors, passing null for the array.
        /// </summary>
        [Fact]
        public void TestSerializeArrayNull()
        {
            DerivedClass[] value = null;
            TranslationHelpers.GetWriteTranslator().TranslateArray(ref value);

            DerivedClass[] deserializedValue = null;
            TranslationHelpers.GetReadTranslator().TranslateArray(ref deserializedValue);

            Assert.True(TranslationHelpers.CompareCollections(value, deserializedValue, DerivedClass.Comparer));
        }

        /// <summary>
        /// Tests serializing an array of objects requiring factories to construct.
        /// </summary>
        [Fact]
        public void TestSerializeArrayWithFactory()
        {
            BaseClass[] value = new BaseClass[] { new BaseClass(1), new BaseClass(2) };
            TranslationHelpers.GetWriteTranslator().TranslateArray(ref value, BaseClass.FactoryForDeserialization);

            BaseClass[] deserializedValue = null;
            TranslationHelpers.GetReadTranslator().TranslateArray(ref deserializedValue, BaseClass.FactoryForDeserialization);

            Assert.True(TranslationHelpers.CompareCollections(value, deserializedValue, BaseClass.Comparer));
        }

        /// <summary>
        /// Tests serializing an array of objects requiring factories to construct, passing null for the array.
        /// </summary>
        [Fact]
        public void TestSerializeArrayWithFactoryNull()
        {
            BaseClass[] value = null;
            TranslationHelpers.GetWriteTranslator().TranslateArray(ref value, BaseClass.FactoryForDeserialization);

            BaseClass[] deserializedValue = null;
            TranslationHelpers.GetReadTranslator().TranslateArray(ref deserializedValue, BaseClass.FactoryForDeserialization);

            Assert.True(TranslationHelpers.CompareCollections(value, deserializedValue, BaseClass.Comparer));
        }

        /// <summary>
        /// Tests serializing a dictionary of { string, string }
        /// </summary>
        [Fact]
        public void TestSerializeDictionaryStringString()
        {
            Dictionary<string, string> value = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
            value["foo"] = "bar";
            value["alpha"] = "omega";

            TranslationHelpers.GetWriteTranslator().TranslateDictionary(ref value, StringComparer.OrdinalIgnoreCase);

            Dictionary<string, string> deserializedValue = null;
            TranslationHelpers.GetReadTranslator().TranslateDictionary(ref deserializedValue, StringComparer.OrdinalIgnoreCase);

            Assert.Equal(value.Count, deserializedValue.Count);
            Assert.Equal(value["foo"], deserializedValue["foo"]);
            Assert.Equal(value["alpha"], deserializedValue["alpha"]);
            Assert.Equal(value["FOO"], deserializedValue["FOO"]);
        }

        /// <summary>
        /// Tests serializing a dictionary of { string, string }, passing null.
        /// </summary>
        [Fact]
        public void TestSerializeDictionaryStringStringNull()
        {
            Dictionary<string, string> value = null;

            TranslationHelpers.GetWriteTranslator().TranslateDictionary(ref value, StringComparer.OrdinalIgnoreCase);

            Dictionary<string, string> deserializedValue = null;
            TranslationHelpers.GetReadTranslator().TranslateDictionary(ref deserializedValue, StringComparer.OrdinalIgnoreCase);

            Assert.Equal(value, deserializedValue);
        }

        /// <summary>
        /// Tests serializing a dictionary of { string, T } where T requires a factory to construct and the dictionary
        /// requires a KeyComparer initializer.
        /// </summary>
        [Fact]
        public void TestSerializeDictionaryStringT()
        {
            Dictionary<string, BaseClass> value = new Dictionary<string, BaseClass>(StringComparer.OrdinalIgnoreCase);
            value["foo"] = new BaseClass(1);
            value["alpha"] = new BaseClass(2);

            TranslationHelpers.GetWriteTranslator().TranslateDictionary(ref value, StringComparer.OrdinalIgnoreCase, BaseClass.FactoryForDeserialization);

            Dictionary<string, BaseClass> deserializedValue = null;
            TranslationHelpers.GetReadTranslator().TranslateDictionary(ref deserializedValue, StringComparer.OrdinalIgnoreCase, BaseClass.FactoryForDeserialization);

            Assert.Equal(value.Count, deserializedValue.Count);
            Assert.Equal(0, BaseClass.Comparer.Compare(value["foo"], deserializedValue["foo"]));
            Assert.Equal(0, BaseClass.Comparer.Compare(value["alpha"], deserializedValue["alpha"]));
            Assert.Equal(0, BaseClass.Comparer.Compare(value["FOO"], deserializedValue["FOO"]));
        }

        /// <summary>
        /// Tests serializing a dictionary of { string, T } where T requires a factory to construct and the dictionary
        /// requires a KeyComparer initializer, passing null for the dictionary.
        /// </summary>
        [Fact]
        public void TestSerializeDictionaryStringTNull()
        {
            Dictionary<string, BaseClass> value = null;

            TranslationHelpers.GetWriteTranslator().TranslateDictionary(ref value, StringComparer.OrdinalIgnoreCase, BaseClass.FactoryForDeserialization);

            Dictionary<string, BaseClass> deserializedValue = null;
            TranslationHelpers.GetReadTranslator().TranslateDictionary(ref deserializedValue, StringComparer.OrdinalIgnoreCase, BaseClass.FactoryForDeserialization);

            Assert.Equal(value, deserializedValue);
        }

        /// <summary>
        /// Tests serializing a dictionary of { string, T } where T requires a factory to construct and the dictionary
        /// has a default constructor.
        /// </summary>
        [Fact]
        public void TestSerializeDictionaryStringTNoComparer()
        {
            Dictionary<string, BaseClass> value = new Dictionary<string, BaseClass>();
            value["foo"] = new BaseClass(1);
            value["alpha"] = new BaseClass(2);

            TranslationHelpers.GetWriteTranslator().TranslateDictionary<Dictionary<string, BaseClass>, BaseClass>(ref value, BaseClass.FactoryForDeserialization);

            Dictionary<string, BaseClass> deserializedValue = null;
            TranslationHelpers.GetReadTranslator().TranslateDictionary<Dictionary<string, BaseClass>, BaseClass>(ref deserializedValue, BaseClass.FactoryForDeserialization);

            Assert.Equal(value.Count, deserializedValue.Count);
            Assert.Equal(0, BaseClass.Comparer.Compare(value["foo"], deserializedValue["foo"]));
            Assert.Equal(0, BaseClass.Comparer.Compare(value["alpha"], deserializedValue["alpha"]));
            Assert.False(deserializedValue.ContainsKey("FOO"));
        }

        /// <summary>
        /// Tests serializing a dictionary of { string, T } where T requires a factory to construct and the dictionary
        /// has a default constructor, passing null for the dictionary.
        /// </summary>
        [Fact]
        public void TestSerializeDictionaryStringTNoComparerNull()
        {
            Dictionary<string, BaseClass> value = null;

            TranslationHelpers.GetWriteTranslator().TranslateDictionary<Dictionary<string, BaseClass>, BaseClass>(ref value, BaseClass.FactoryForDeserialization);

            Dictionary<string, BaseClass> deserializedValue = null;
            TranslationHelpers.GetReadTranslator().TranslateDictionary<Dictionary<string, BaseClass>, BaseClass>(ref deserializedValue, BaseClass.FactoryForDeserialization);

            Assert.Equal(value, deserializedValue);
        }

        [Theory]
        [InlineData("en")]
        [InlineData("en-US")]
        [InlineData("en-CA")]
        [InlineData("zh-HK")]
        [InlineData("sr-Cyrl-CS")]
        public void CultureInfo(string name)
        {
            CultureInfo value = new CultureInfo(name);
            TranslationHelpers.GetWriteTranslator().Translate(ref value);

            CultureInfo deserializedValue = null;
            TranslationHelpers.GetReadTranslator().Translate(ref deserializedValue);

            deserializedValue.ShouldBe(value);
        }

        [Fact]
        public void CultureInfoAsNull()
        {
            CultureInfo value = null;
            TranslationHelpers.GetWriteTranslator().Translate(ref value);

            CultureInfo deserializedValue = null;
            TranslationHelpers.GetReadTranslator().Translate(ref deserializedValue);

            deserializedValue.ShouldBeNull();
        }

        [Theory]
        [InlineData("1.2")]
        [InlineData("1.2.3")]
        [InlineData("1.2.3.4")]
        public void Version(string version)
        {
            Version value = new Version(version);
            TranslationHelpers.GetWriteTranslator().Translate(ref value);

            Version deserializedValue = null;
            TranslationHelpers.GetReadTranslator().Translate(ref deserializedValue);

            deserializedValue.ShouldBe(value);
        }

        [Fact]
        public void VersionAsNull()
        {
            Version value = null;
            TranslationHelpers.GetWriteTranslator().Translate(ref value);

            Version deserializedValue = null;
            TranslationHelpers.GetReadTranslator().Translate(ref deserializedValue);

            deserializedValue.ShouldBeNull();
        }

        [Fact]
        public void HashSetOfT()
        {
            HashSet<BaseClass> values = new()
            {
                new BaseClass(1),
                new BaseClass(2),
                null
            };
            TranslationHelpers.GetWriteTranslator().TranslateHashSet(ref values, BaseClass.FactoryForDeserialization, capacity => new());

            HashSet<BaseClass> deserializedValues = null;
            TranslationHelpers.GetReadTranslator().TranslateHashSet(ref deserializedValues, BaseClass.FactoryForDeserialization, capacity => new());

            deserializedValues.ShouldBe(values, ignoreOrder: true);
        }

        [Fact]
        public void HashSetOfTAsNull()
        {
            HashSet<BaseClass> value = null;
            TranslationHelpers.GetWriteTranslator().TranslateHashSet(ref value, BaseClass.FactoryForDeserialization, capacity => new());

            HashSet<BaseClass> deserializedValue = null;
            TranslationHelpers.GetReadTranslator().TranslateHashSet(ref deserializedValue, BaseClass.FactoryForDeserialization, capacity => new());

            deserializedValue.ShouldBeNull();
        }

        [Fact]
        public void AssemblyNameAsNull()
        {
            AssemblyName value = null;
            TranslationHelpers.GetWriteTranslator().Translate(ref value);

            AssemblyName deserializedValue = null;
            TranslationHelpers.GetReadTranslator().Translate(ref deserializedValue);

            deserializedValue.ShouldBeNull();
        }

        [Fact]
        public void AssemblyNameWithAllFields()
        {
            AssemblyName value = new()
            {
                Name = "a",
                Version = new Version(1, 2, 3),
                Flags = AssemblyNameFlags.PublicKey,
                ProcessorArchitecture = ProcessorArchitecture.X86,
                CultureInfo = new CultureInfo("zh-HK"),
                HashAlgorithm = System.Configuration.Assemblies.AssemblyHashAlgorithm.SHA256,
                VersionCompatibility = AssemblyVersionCompatibility.SameMachine,
                CodeBase = "C:\\src",
                ContentType = AssemblyContentType.WindowsRuntime,
                CultureName = "zh-HK",
            };
            value.SetPublicKey(new byte[] { 3, 2, 1 });
            value.SetPublicKeyToken(new byte[] { 8, 7, 6, 5, 4, 3, 2, 1 });

            TranslationHelpers.GetWriteTranslator().Translate(ref value);

            AssemblyName deserializedValue = null;
            TranslationHelpers.GetReadTranslator().Translate(ref deserializedValue);

            HelperAssertAssemblyNameEqual(value, deserializedValue);
        }

        [Fact]
        public void AssemblyNameWithMinimalFields()
        {
            AssemblyName value = new();

            TranslationHelpers.GetWriteTranslator().Translate(ref value);

            AssemblyName deserializedValue = null;
            TranslationHelpers.GetReadTranslator().Translate(ref deserializedValue);

            HelperAssertAssemblyNameEqual(value, deserializedValue);
        }

        /// <summary>
        /// Assert two AssemblyName objects values are same.
        /// Ignoring KeyPair, ContentType, CultureName as those are not serialized
        /// </summary>
        private static void HelperAssertAssemblyNameEqual(AssemblyName expected, AssemblyName actual)
        {
            actual.Name.ShouldBe(expected.Name);
            actual.Version.ShouldBe(expected.Version);
            actual.Flags.ShouldBe(expected.Flags);
            actual.ProcessorArchitecture.ShouldBe(expected.ProcessorArchitecture);
            actual.CultureInfo.ShouldBe(expected.CultureInfo);
            actual.HashAlgorithm.ShouldBe(expected.HashAlgorithm);
            actual.VersionCompatibility.ShouldBe(expected.VersionCompatibility);
            actual.CodeBase.ShouldBe(expected.CodeBase);

            actual.GetPublicKey().ShouldBe(expected.GetPublicKey());
            actual.GetPublicKeyToken().ShouldBe(expected.GetPublicKeyToken());
        }

        /// <summary>
        /// Helper for bool serialization.
        /// </summary>
        private void HelperTestSimpleType(bool initialValue, bool deserializedInitialValue)
        {
            bool value = initialValue;
            TranslationHelpers.GetWriteTranslator().Translate(ref value);

            bool deserializedValue = deserializedInitialValue;
            TranslationHelpers.GetReadTranslator().Translate(ref deserializedValue);

            Assert.Equal(value, deserializedValue);
        }

        /// <summary>
        /// Helper for long serialization.
        /// </summary>
        private void HelperTestSimpleType(long initialValue, long deserializedInitialValue)
        {
            long value = initialValue;
            TranslationHelpers.GetWriteTranslator().Translate(ref value);

            long deserializedValue = deserializedInitialValue;
            TranslationHelpers.GetReadTranslator().Translate(ref deserializedValue);

            Assert.Equal(value, deserializedValue);
        }

        /// <summary>
        /// Helper for double serialization.
        /// </summary>
        private void HelperTestSimpleType(double initialValue, double deserializedInitialValue)
        {
            double value = initialValue;
            TranslationHelpers.GetWriteTranslator().Translate(ref value);

            double deserializedValue = deserializedInitialValue;
            TranslationHelpers.GetReadTranslator().Translate(ref deserializedValue);

            Assert.Equal(value, deserializedValue);
        }

        /// <summary>
        /// Helper for TimeSpan serialization.
        /// </summary>
        private void HelperTestSimpleType(TimeSpan initialValue, TimeSpan deserializedInitialValue)
        {
            TimeSpan value = initialValue;
            TranslationHelpers.GetWriteTranslator().Translate(ref value);

            TimeSpan deserializedValue = deserializedInitialValue;
            TranslationHelpers.GetReadTranslator().Translate(ref deserializedValue);

            Assert.Equal(value, deserializedValue);
        }

        /// <summary>
        /// Helper for byte serialization.
        /// </summary>
        private void HelperTestSimpleType(byte initialValue, byte deserializedInitialValue)
        {
            byte value = initialValue;
            TranslationHelpers.GetWriteTranslator().Translate(ref value);

            byte deserializedValue = deserializedInitialValue;
            TranslationHelpers.GetReadTranslator().Translate(ref deserializedValue);

            Assert.Equal(value, deserializedValue);
        }

        /// <summary>
        /// Helper for short serialization.
        /// </summary>
        private void HelperTestSimpleType(short initialValue, short deserializedInitialValue)
        {
            short value = initialValue;
            TranslationHelpers.GetWriteTranslator().Translate(ref value);

            short deserializedValue = deserializedInitialValue;
            TranslationHelpers.GetReadTranslator().Translate(ref deserializedValue);

            Assert.Equal(value, deserializedValue);
        }

        /// <summary>
        /// Helper for int serialization.
        /// </summary>
        private void HelperTestSimpleType(int initialValue, int deserializedInitialValue)
        {
            int value = initialValue;
            TranslationHelpers.GetWriteTranslator().Translate(ref value);

            int deserializedValue = deserializedInitialValue;
            TranslationHelpers.GetReadTranslator().Translate(ref deserializedValue);

            Assert.Equal(value, deserializedValue);
        }

        /// <summary>
        /// Helper for string serialization.
        /// </summary>
        private void HelperTestSimpleType(string initialValue, string deserializedInitialValue)
        {
            string value = initialValue;
            TranslationHelpers.GetWriteTranslator().Translate(ref value);

            string deserializedValue = deserializedInitialValue;
            TranslationHelpers.GetReadTranslator().Translate(ref deserializedValue);

            Assert.Equal(value, deserializedValue);
        }

        /// <summary>
        /// Helper for DateTime serialization.
        /// </summary>
        private void HelperTestSimpleType(DateTime initialValue, DateTime deserializedInitialValue)
        {
            DateTime value = initialValue;
            TranslationHelpers.GetWriteTranslator().Translate(ref value);

            DateTime deserializedValue = deserializedInitialValue;
            TranslationHelpers.GetReadTranslator().Translate(ref deserializedValue);

            Assert.Equal(value, deserializedValue);
        }

        /// <summary>
        /// Helper for array serialization.
        /// </summary>
        private void HelperTestArray(string[] initialValue, IComparer<string> comparer)
        {
            string[] value = initialValue;
            TranslationHelpers.GetWriteTranslator().Translate(ref value);

            string[] deserializedValue = null;
            TranslationHelpers.GetReadTranslator().Translate(ref deserializedValue);

            Assert.True(TranslationHelpers.CompareCollections(value, deserializedValue, comparer));
        }

        /// <summary>
        /// Helper for list serialization.
        /// </summary>
        private void HelperTestList(List<string> initialValue, IComparer<string> comparer)
        {
            List<string> value = initialValue;
            TranslationHelpers.GetWriteTranslator().Translate(ref value);

            List<string> deserializedValue = null;
            TranslationHelpers.GetReadTranslator().Translate(ref deserializedValue);

            Assert.True(TranslationHelpers.CompareCollections(value, deserializedValue, comparer));
        }

        /// <summary>
        /// Base class for testing
        /// </summary>
        private class BaseClass : ITranslatable
        {
            /// <summary>
            /// A field.
            /// </summary>
            private int _baseValue;

            /// <summary>
            /// Constructor with value.
            /// </summary>
            public BaseClass(int val)
            {
                _baseValue = val;
            }

            /// <summary>
            /// Constructor
            /// </summary>
            protected BaseClass()
            {
            }

            protected bool Equals(BaseClass other)
            {
                return _baseValue == other._baseValue;
            }

            public override bool Equals(object obj)
            {
                if (ReferenceEquals(null, obj))
                {
                    return false;
                }

                if (ReferenceEquals(this, obj))
                {
                    return true;
                }

                if (obj.GetType() != this.GetType())
                {
                    return false;
                }

                return Equals((BaseClass)obj);
            }

            public override int GetHashCode()
            {
                return _baseValue;
            }

            /// <summary>
            /// Gets a comparer.
            /// </summary>
            public static IComparer<BaseClass> Comparer
            {
                get { return new BaseClassComparer(); }
            }

            /// <summary>
            /// Gets the value.
            /// </summary>
            public int BaseValue
            {
                get { return _baseValue; }
            }

            #region INodePacketTranslatable Members

            /// <summary>
            /// Factory for serialization.
            /// </summary>
            public static BaseClass FactoryForDeserialization(ITranslator translator)
            {
                BaseClass packet = new BaseClass();
                packet.Translate(translator);
                return packet;
            }

            /// <summary>
            /// Serializes the class.
            /// </summary>
            public virtual void Translate(ITranslator translator)
            {
                translator.Translate(ref _baseValue);
            }

            #endregion

            /// <summary>
            /// Comparer for BaseClass.
            /// </summary>
            private sealed class BaseClassComparer : IComparer<BaseClass>
            {
                /// <summary>
                /// Constructor.
                /// </summary>
                public BaseClassComparer()
                {
                }

                #region IComparer<BaseClass> Members

                /// <summary>
                /// Compare two BaseClass objects.
                /// </summary>
                public int Compare(BaseClass x, BaseClass y)
                {
                    if (x._baseValue == y._baseValue)
                    {
                        return 0;
                    }

                    return -1;
                }
                #endregion
            }
        }

        /// <summary>
        /// Derived class for testing.
        /// </summary>
        private sealed class DerivedClass : BaseClass
        {
            /// <summary>
            /// A field.
            /// </summary>
            private int _derivedValue;

            /// <summary>
            /// Default constructor.
            /// </summary>
            public DerivedClass()
            {
            }

            /// <summary>
            /// Constructor taking two values.
            /// </summary>
            public DerivedClass(int derivedValue, int baseValue)
                : base(baseValue)
            {
                _derivedValue = derivedValue;
            }

            /// <summary>
            /// Gets a comparer.
            /// </summary>
            public static new IComparer<DerivedClass> Comparer
            {
                get { return new DerivedClassComparer(); }
            }

            /// <summary>
            /// Returns the value.
            /// </summary>
            public int DerivedValue
            {
                get { return _derivedValue; }
            }

            #region INodePacketTranslatable Members

            /// <summary>
            /// Serializes the class.
            /// </summary>
            public override void Translate(ITranslator translator)
            {
                base.Translate(translator);
                translator.Translate(ref _derivedValue);
            }

            #endregion

            /// <summary>
            /// Comparer for DerivedClass.
            /// </summary>
            private sealed class DerivedClassComparer : IComparer<DerivedClass>
            {
                /// <summary>
                /// Constructor
                /// </summary>
                public DerivedClassComparer()
                {
                }

                #region IComparer<DerivedClass> Members

                /// <summary>
                /// Compares two DerivedClass objects.
                /// </summary>
                public int Compare(DerivedClass x, DerivedClass y)
                {
                    if (x._derivedValue == y._derivedValue)
                    {
                        return BaseClass.Comparer.Compare(x, y);
                    }

                    return -1;
                }
                #endregion
            }
        }
    }
}
