// 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.Reflection;
using AssemblyHashAlgorithm = System.Configuration.Assemblies.AssemblyHashAlgorithm;

#nullable disable

namespace Microsoft.Build.BackEnd
{
    /// <summary>
    /// This class provides helper methods to adapt from <see cref="NodePacketValueFactory{T}"/> to
    /// <see cref="ObjectTranslator{T}"/>.
    /// </summary>
    internal static class TranslatorHelpers
    {
        /// <summary>
        /// Translates an object implementing <see cref="ITranslatable"/> which does not expose a
        /// public parameterless constructor.
        /// </summary>
        /// <typeparam name="T">The reference type.</typeparam>
        /// <param name="translator">The translator</param>
        /// <param name="instance">The value to be translated.</param>
        /// <param name="valueFactory">The factory method used to instantiate values of type T.</param>
        public static void Translate<T>(
            this ITranslator translator,
            ref T instance,
            NodePacketValueFactory<T> valueFactory) where T : ITranslatable
        {
            if (!translator.TranslateNullable(instance))
            {
                return;
            }
            if (translator.Mode == TranslationDirection.ReadFromStream)
            {
                instance = valueFactory(translator);
            }
            else
            {
                instance.Translate(translator);
            }
        }

        private static ObjectTranslator<T> AdaptFactory<T>(NodePacketValueFactory<T> valueFactory) where T : ITranslatable
        {
            void TranslateUsingValueFactory(ITranslator translator, ref T objectToTranslate)
            {
                translator.Translate(ref objectToTranslate, valueFactory);
            }

            return TranslateUsingValueFactory;
        }

        public static void Translate<T>(
            this ITranslator translator,
            ref List<T> list,
            NodePacketValueFactory<T> valueFactory) where T : class, ITranslatable
        {
            translator.Translate(ref list, AdaptFactory(valueFactory));
        }

        public static void Translate<T, L>(
            this ITranslator translator,
            ref IList<T> list,
            NodePacketValueFactory<T> valueFactory,
            NodePacketCollectionCreator<L> collectionFactory) where L : IList<T> where T : ITranslatable
        {
            translator.Translate(ref list, AdaptFactory(valueFactory), collectionFactory);
        }

        public static void TranslateArray<T>(
            this ITranslator translator,
            ref T[] array,
            NodePacketValueFactory<T> valueFactory) where T : class, ITranslatable
        {
            translator.TranslateArray(ref array, AdaptFactory(valueFactory));
        }

        public static void TranslateDictionary<T>(
            this ITranslator translator,
            ref Dictionary<string, T> dictionary,
            IEqualityComparer<string> comparer,
            NodePacketValueFactory<T> valueFactory) where T : class, ITranslatable
        {
            translator.TranslateDictionary(ref dictionary, comparer, AdaptFactory(valueFactory));
        }

        public static void TranslateDictionary<D, T>(
            this ITranslator translator,
            ref D dictionary,
            NodePacketValueFactory<T> valueFactory)
            where D : IDictionary<string, T>, new()
            where T : class, ITranslatable
        {
            translator.TranslateDictionary(ref dictionary, AdaptFactory(valueFactory));
        }

        public static void TranslateDictionary<D, T>(
            this ITranslator translator,
            ref D dictionary,
            NodePacketValueFactory<T> valueFactory,
            NodePacketCollectionCreator<D> collectionCreator)
            where D : IDictionary<string, T>
            where T : class, ITranslatable
        {
            translator.TranslateDictionary(ref dictionary, AdaptFactory(valueFactory), collectionCreator);
        }

        public static void TranslateHashSet<T>(
            this ITranslator translator,
            ref HashSet<T> hashSet,
            NodePacketValueFactory<T> valueFactory,
            NodePacketCollectionCreator<HashSet<T>> collectionFactory) where T : class, ITranslatable
        {
            if (!translator.TranslateNullable(hashSet))
            {
                return;
            }

            int count = default;
            if (translator.Mode == TranslationDirection.WriteToStream)
            {
                count = hashSet.Count;
            }
            translator.Translate(ref count);

            if (translator.Mode == TranslationDirection.ReadFromStream)
            {
                hashSet = collectionFactory(count);
                for (int i = 0; i < count; i++)
                {
                    T value = default;
                    translator.Translate(ref value, valueFactory);
                    hashSet.Add(value);
                }
            }

            if (translator.Mode == TranslationDirection.WriteToStream)
            {
                foreach (T item in hashSet)
                {
                    T value = item;
                    translator.Translate(ref value, valueFactory);
                }
            }
        }

        public static void Translate(this ITranslator translator, ref CultureInfo cultureInfo)
        {
            if (!translator.TranslateNullable(cultureInfo))
            {
                return;
            }

            int lcid = default;

            if (translator.Mode == TranslationDirection.WriteToStream)
            {
                lcid = cultureInfo.LCID;
            }

            translator.Translate(ref lcid);

            if (translator.Mode == TranslationDirection.ReadFromStream)
            {
                cultureInfo = new CultureInfo(lcid);
            }
        }

        public static void Translate(this ITranslator translator, ref Version version)
        {
            if (!translator.TranslateNullable(version))
            {
                return;
            }

            int major = 0;
            int minor = 0;
            int build = 0;
            int revision = 0;

            if (translator.Mode == TranslationDirection.WriteToStream)
            {
                major = version.Major;
                minor = version.Minor;
                build = version.Build;
                revision = version.Revision;
            }

            translator.Translate(ref major);
            translator.Translate(ref minor);
            translator.Translate(ref build);
            translator.Translate(ref revision);

            if (translator.Mode == TranslationDirection.ReadFromStream)
            {
                if (build < 0)
                {
                    version = new Version(major, minor);
                }
                else if (revision < 0)
                {
                    version = new Version(major, minor, build);
                }
                else
                {
                    version = new Version(major, minor, build, revision);
                }
            }
        }

        public static void Translate(this ITranslator translator, ref AssemblyName assemblyName)
        {
            if (!translator.TranslateNullable(assemblyName))
            {
                return;
            }

            string name = null;
            Version version = null;
            AssemblyNameFlags flags = default;
            ProcessorArchitecture processorArchitecture = default;
            CultureInfo cultureInfo = null;
            AssemblyHashAlgorithm hashAlgorithm = default;
            AssemblyVersionCompatibility versionCompatibility = default;
            string codeBase = null;

            byte[] publicKey = null;
            byte[] publicKeyToken = null;

            if (translator.Mode == TranslationDirection.WriteToStream)
            {
                name = assemblyName.Name;
                version = assemblyName.Version;
                flags = assemblyName.Flags;
                processorArchitecture = assemblyName.ProcessorArchitecture;
                cultureInfo = assemblyName.CultureInfo;
                hashAlgorithm = assemblyName.HashAlgorithm;
                versionCompatibility = assemblyName.VersionCompatibility;
                codeBase = assemblyName.CodeBase;

                publicKey = assemblyName.GetPublicKey(); // TODO: no need to serialize, public key is not used anywhere in context of RAR, only public key token
                publicKeyToken = assemblyName.GetPublicKeyToken();
            }

            translator.Translate(ref name);
            translator.Translate(ref version);
            translator.TranslateEnum(ref flags, (int)flags);
            translator.TranslateEnum(ref processorArchitecture, (int)processorArchitecture);
            translator.Translate(ref cultureInfo);
            translator.TranslateEnum(ref hashAlgorithm, (int)hashAlgorithm);
            translator.TranslateEnum(ref versionCompatibility, (int)versionCompatibility);
            translator.Translate(ref codeBase);

            translator.Translate(ref publicKey);
            translator.Translate(ref publicKeyToken);

            if (translator.Mode == TranslationDirection.ReadFromStream)
            {
                assemblyName = new AssemblyName
                {
                    Name = name,
                    Version = version,
                    Flags = flags,
                    ProcessorArchitecture = processorArchitecture,
                    CultureInfo = cultureInfo,
                    HashAlgorithm = hashAlgorithm,
                    VersionCompatibility = versionCompatibility,
                    CodeBase = codeBase,
                };

                assemblyName.SetPublicKey(publicKey);
                assemblyName.SetPublicKeyToken(publicKeyToken);
            }
        }
    }
}
