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

using System;
#if !TASKHOST
using System.Collections.Frozen;
using System.Collections.Immutable;
#endif
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 ObjectTranslatorWithValueFactory<T> AdaptFactory<T>(NodePacketValueFactory<T> valueFactory) where T : ITranslatable
        {
            static void TranslateUsingValueFactory(ITranslator translator, NodePacketValueFactory<T> valueFactory, 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), 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), 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), 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), valueFactory);
        }

        public static void InternDictionary(
            this ITranslator translator,
            ref Dictionary<string, string> dictionary,
            IEqualityComparer<string> comparer)
        {
            IDictionary<string, string> localDict = dictionary;
            translator.TranslateDictionary(
                ref localDict,
                (ITranslator translator, ref string key) => translator.Intern(ref key),
                (ITranslator translator, ref string val) => translator.Intern(ref val),
                capacity => new Dictionary<string, string>(capacity, comparer));
            dictionary = (Dictionary<string, string>)localDict;
        }

        public static void InternDictionary<T>(
            this ITranslator translator,
            ref Dictionary<string, T> dictionary,
            IEqualityComparer<string> stringComparer,
            NodePacketValueFactory<T> valueFactory)
            where T : ITranslatable
        {
            IDictionary<string, T> localDict = dictionary;
            translator.TranslateDictionary(
                ref localDict,
                (ITranslator translator, ref string key) => translator.Intern(ref key),
                AdaptFactory(valueFactory),
                valueFactory,
                capacity => new Dictionary<string, T>(capacity, stringComparer));
            dictionary = (Dictionary<string, T>)localDict;
        }

        public static void InternPathDictionary(
            this ITranslator translator,
            ref Dictionary<string, string> dictionary,
            IEqualityComparer<string> comparer)
        {
            IDictionary<string, string> localDict = dictionary;

            // For now, assume only the value contains path-like strings (e.g. TaskItem metadata).
            translator.TranslateDictionary(
                ref localDict,
                (ITranslator translator, ref string key) => translator.Intern(ref key),
                (ITranslator translator, ref string val) => translator.InternPath(ref val),
                capacity => new Dictionary<string, string>(capacity, comparer));
            dictionary = (Dictionary<string, string>)localDict;
        }

        public static void InternPathDictionary<T>(
            this ITranslator translator,
            ref Dictionary<string, T> dictionary,
            IEqualityComparer<string> stringComparer,
            NodePacketValueFactory<T> valueFactory)
            where T : ITranslatable
        {
            IDictionary<string, T> localDict = dictionary;
            translator.TranslateDictionary(
                ref localDict,
                (ITranslator translator, ref string key) => translator.InternPath(ref key),
                AdaptFactory(valueFactory),
                valueFactory,
                capacity => new Dictionary<string, T>(capacity, stringComparer));
            dictionary = (Dictionary<string, T>)localDict;
        }

        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), 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), valueFactory, collectionCreator);
        }

#if !TASKHOST
        public static void TranslateDictionary(
            this ITranslator translator,
            ref FrozenDictionary<string, string> dictionary,
            IEqualityComparer<string> comparer)
        {
            IDictionary<string, string> localDict = dictionary;
            translator.TranslateDictionary(ref localDict, capacity => new Dictionary<string, string>(capacity, comparer));

            if (translator.Mode == TranslationDirection.ReadFromStream)
            {
                dictionary = localDict?.ToFrozenDictionary(comparer);
            }
        }

        public static void TranslateDictionary(
            this ITranslator translator,
            ref IReadOnlyDictionary<string, string> dictionary,
            IEqualityComparer<string> comparer)
        {
            // Defensive copy since immutable dictionaries are expected to be overwritten.
            IReadOnlyDictionary<string, string> localDict = dictionary;

            if (!translator.TranslateNullable(localDict))
            {
                return;
            }

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

                foreach (KeyValuePair<string, string> kvp in localDict)
                {
                    string key = kvp.Key;
                    string value = kvp.Value;

                    translator.Translate(ref key);
                    translator.Translate(ref value);
                }
            }
            else
            {
                int count = default;
                translator.Translate(ref count);

                ImmutableDictionary<string, string>.Builder builder = ImmutableDictionary.Create<string, string>(comparer).ToBuilder();

                for (int i = 0; i < count; i++)
                {
                    string key = null;
                    string value = null;

                    translator.Translate(ref key);
                    translator.Translate(ref value);

                    builder[key] = value;
                }

                dictionary = builder.ToImmutable();
            }
        }

        public static void TranslateDictionary(
            this ITranslator translator,
            ref ImmutableDictionary<string, string> dictionary,
            IEqualityComparer<string> comparer)
        {
            // Defensive copy since immutable dictionaries are expected to be overwritten.
            IReadOnlyDictionary<string, string> localDict = dictionary;

            TranslateDictionary(translator, ref localDict, comparer);

            if (translator.Mode == TranslationDirection.ReadFromStream)
            {
                dictionary = (ImmutableDictionary<string, string>)localDict;
            }
        }
#endif

        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);
            }
        }
    }
}
