using System;
using System.Collections.Generic;
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
using System.Collections.Concurrent;
#endif
using System.IO;
using System.Text.RegularExpressions;
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
using System.Threading.Tasks;
#endif
using SabreTools.Metadata.Filter;
using SabreTools.Metadata.DatItems;
using SabreTools.Metadata.DatItems.Formats;

#pragma warning disable IDE0057 // Use range operator
#pragma warning disable IDE0059 // Unnecessary assignment of a value
namespace SabreTools.Metadata.DatFiles
{
    public partial class DatFile
    {
        #region Constants

        /// <summary>
        /// Scene name Regex pattern
        /// </summary>
        private const string SceneNamePattern = @"([0-9]{2}\.[0-9]{2}\.[0-9]{2}-)(.*?-.*?)";

        #endregion

        #region Filtering

        /// <summary>
        /// Execute all filters in a filter runner on the items in the dictionary
        /// </summary>
        /// <param name="filterRunner">Preconfigured filter runner to use</param>
        public void ExecuteFilters(FilterRunner filterRunner)
        {
            ExecuteFiltersImpl(filterRunner);
            ExecuteFiltersImplDB(filterRunner);
        }

        /// <summary>
        /// Use game descriptions as names, updating cloneof/romof/sampleof
        /// </summary>
        /// <param name="throwOnError">True if the error that is thrown should be thrown back to the caller, false otherwise</param>
        public void MachineDescriptionToName(bool throwOnError = false)
        {
            MachineDescriptionToNameImpl(throwOnError);
            MachineDescriptionToNameImplDB(throwOnError);
        }

        /// <summary>
        /// Ensure that all roms are in their own game (or at least try to ensure)
        /// </summary>
        public void SetOneRomPerGame()
        {
            SetOneRomPerGameImpl();
            SetOneRomPerGameImplDB();
        }

        /// <summary>
        /// Filter a DAT using 1G1R logic given an ordered set of regions
        /// </summary>
        /// <param name="regionList">List of regions in order of priority</param>
        /// <remarks>
        /// In the most technical sense, the way that the region list is being used does not
        /// confine its values to be just regions. Since it's essentially acting like a
        /// specialized version of the machine name filter, anything that is usually encapsulated
        /// in parenthesis would be matched on, including disc numbers, languages, editions,
        /// and anything else commonly used. Please note that, unlike other existing 1G1R
        /// solutions, this does not have the ability to contain custom mappings of parent
        /// to clone sets based on name, nor does it have the ability to match on the
        /// Release DatItem type.
        /// </remarks>
        public void SetOneGamePerRegion(List<string> regionList)
        {
            SetOneGamePerRegionImpl(regionList);
            SetOneGamePerRegionImplDB(regionList);
        }

        /// <summary>
        /// Strip the dates from the beginning of scene-style set names
        /// </summary>
        public void StripSceneDatesFromItems()
        {
            StripSceneDatesFromItemsImpl();
            StripSceneDatesFromItemsImplDB();
        }

        #endregion

        #region Filtering Implementations

        /// <summary>
        /// Create machine to description mapping dictionary
        /// </summary>
        /// <remarks>Applies to <see cref="Items"/></remarks>
        private IDictionary<string, string> CreateMachineToDescriptionMapping()
        {
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
            ConcurrentDictionary<string, string> mapping = new();
#else
            Dictionary<string, string> mapping = [];
#endif
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
            Parallel.ForEach(Items.SortedKeys, key =>
#else
            foreach (var key in Items.SortedKeys)
#endif
            {
                var items = GetItemsForBucket(key);
                if (items is null)
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
                    return;
#else
                    continue;
#endif

                foreach (DatItem item in items)
                {
                    // Get the current machine
                    var machine = item.Machine;
                    if (machine is null)
                        continue;

                    // Get the values to check against
                    string? machineName = machine.Name;
                    string? machineDesc = machine.Description;
                    if (machineName is null || machineDesc is null)
                        continue;

                    // Adjust the description
                    machineDesc = machineDesc.Replace('/', '_').Replace("\"", "''").Replace(":", " -");
                    if (machineName == machineDesc)
                        continue;

                    // If the key mapping doesn't exist, add it
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
                    mapping.TryAdd(machineName, machineDesc);
#else
                    if (!mapping.ContainsKey(machineName))
                        mapping[machineName] = machineDesc;
#endif
                }
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
            });
#else
            }
#endif

            return mapping;
        }

        /// <summary>
        /// Create machine to description mapping dictionary
        /// </summary>
        /// <remarks>Applies to <see cref="ItemsDB"/></remarks>
        private Dictionary<string, string> CreateMachineToDescriptionMappingDB()
        {
            Dictionary<string, string> mapping = [];
            foreach (var machine in GetMachinesDB())
            {
                // Get the values to check against
                string? machineName = machine.Value.Name;
                string? machineDesc = machine.Value.Description;
                if (machineName is null || machineDesc is null)
                    continue;

                // Adjust the description
                machineDesc = machineDesc.Replace('/', '_').Replace("\"", "''").Replace(":", " -");
                if (machineName == machineDesc)
                    continue;

                // If the key mapping doesn't exist, add it
                if (!mapping.ContainsKey(machineName))
                    mapping[machineName] = machineDesc;
            }

            return mapping;
        }

        /// <summary>
        /// Execute all filters in a filter runner on the items in the dictionary
        /// </summary>
        /// <param name="filterRunner">Preconfigured filter runner to use</param>
        /// <remarks>Applies to <see cref="Items"/></remarks>
        private void ExecuteFiltersImpl(FilterRunner filterRunner)
        {
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
            Parallel.ForEach(Items.SortedKeys, key =>
#else
            foreach (var key in Items.SortedKeys)
#endif
            {
                ExecuteFilterOnBucket(filterRunner, key);
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
            });
#else
            }
#endif
        }

        /// <summary>
        /// Execute all filters in a filter runner on the items in the dictionary
        /// </summary>
        /// <param name="filterRunner">Preconfigured filter runner to use</param>
        /// <remarks>Applies to <see cref="ItemsDB"/></remarks>
        private void ExecuteFiltersImplDB(FilterRunner filterRunner)
        {
            List<string> keys = [.. ItemsDB.SortedKeys];
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
            Parallel.ForEach(keys, key =>
#else
            foreach (var key in keys)
#endif
            {
                ExecuteFilterOnBucketDB(filterRunner, key);
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
            });
#else
            }
#endif
        }

        /// <summary>
        /// Execute all filters in a filter runner on a single bucket
        /// </summary>
        /// <param name="filterRunner">Preconfigured filter runner to use</param>
        /// <param name="bucketName">Name of the bucket to filter on</param>
        /// <remarks>Applies to <see cref="Items"/></remarks>
        private void ExecuteFilterOnBucket(FilterRunner filterRunner, string bucketName)
        {
            List<DatItem>? items = GetItemsForBucket(bucketName);
            if (items is null)
                return;

            // Filter all items in the current key
            foreach (var item in items)
            {
                if (!item.PassesFilter(filterRunner))
                    item.RemoveFlag = true;
            }
        }

        /// <summary>
        /// Execute all filters in a filter runner on a single bucket
        /// </summary>
        /// <param name="filterRunner">Preconfigured filter runner to use</param>
        /// <param name="bucketName">Name of the bucket to filter on</param>
        /// <remarks>Applies to <see cref="ItemsDB"/></remarks>
        private void ExecuteFilterOnBucketDB(FilterRunner filterRunner, string bucketName)
        {
            var items = GetItemsForBucketDB(bucketName);
            if (items is null)
                return;

            // Filter all items in the current key
            List<long> newItems = [];
            foreach (var item in items)
            {
                if (!item.Value.PassesFilterDB(filterRunner))
                    item.Value.RemoveFlag = true;
            }
        }

        /// <summary>
        /// Use game descriptions as names, updating cloneof/romof/sampleof
        /// </summary>
        /// <param name="throwOnError">True if the error that is thrown should be thrown back to the caller, false otherwise</param>
        /// <remarks>Applies to <see cref="Items"/></remarks>
        private void MachineDescriptionToNameImpl(bool throwOnError = false)
        {
            try
            {
                // First we want to get a mapping for all games to description
                var mapping = CreateMachineToDescriptionMapping();

                // Now we loop through every item and update accordingly
                UpdateMachineNamesFromDescriptions(mapping);
            }
            catch (Exception ex) when (!throwOnError)
            {
                _logger.Warning(ex.ToString());
            }
        }

        /// <summary>
        /// Use game descriptions as names, updating cloneof/romof/sampleof
        /// </summary>
        /// <param name="throwOnError">True if the error that is thrown should be thrown back to the caller, false otherwise</param>
        /// <remarks>Applies to <see cref="ItemsDB"/></remarks>
        private void MachineDescriptionToNameImplDB(bool throwOnError = false)
        {
            try
            {
                // First we want to get a mapping for all games to description
                var mapping = CreateMachineToDescriptionMappingDB();

                // Now we loop through every item and update accordingly
                UpdateMachineNamesFromDescriptionsDB(mapping);
            }
            catch (Exception ex) when (!throwOnError)
            {
                _logger.Warning(ex.ToString());
            }
        }

        /// <summary>
        /// Filter a DAT using 1G1R logic given an ordered set of regions
        /// </summary>
        /// <param name="regionList">List of regions in order of priority</param>
        /// <remarks>Applies to <see cref="Items"/></remarks>
        private void SetOneGamePerRegionImpl(List<string> regionList)
        {
            // For sake of ease, the first thing we want to do is bucket by game
            BucketBy(ItemKey.Machine, norename: true);

            // Then we want to get a mapping of all machines to parents
            Dictionary<string, List<string>> parents = [];
            foreach (string key in Items.SortedKeys)
            {
                DatItem item = GetItemsForBucket(key)[0];

                // Get machine information
                Machine? machine = item.Machine;
                string? machineName = machine?.Name?.ToLowerInvariant();
                if (machine is null || machineName is null)
                    continue;

                // Get the string values
                string? cloneOf = machine.CloneOf?.ToLowerInvariant();
                string? romOf = machine.RomOf?.ToLowerInvariant();

                // Match on CloneOf first
                if (!string.IsNullOrEmpty(cloneOf))
                {
                    if (!parents.ContainsKey(cloneOf!))
                        parents.Add(cloneOf!, []);

                    parents[cloneOf!].Add(machineName);
                }

                // Then by RomOf
                else if (!string.IsNullOrEmpty(romOf))
                {
                    if (!parents.ContainsKey(romOf!))
                        parents.Add(romOf!, []);

                    parents[romOf!].Add(machineName);
                }

                // Otherwise, treat it as a parent
                else
                {
                    if (!parents.ContainsKey(machineName))
                        parents.Add(machineName, []);

                    parents[machineName].Add(machineName);
                }
            }

            // Once we have the full list of mappings, filter out games to keep
            foreach (string key in parents.Keys)
            {
                // Find the first machine that matches the regions in order, if possible
                string? machine = default;
                foreach (string region in regionList)
                {
                    machine = parents[key].Find(m => Regex.IsMatch(m, @"\(.*" + region + @".*\)", RegexOptions.IgnoreCase));
                    if (machine != default)
                        break;
                }

                // If we didn't get a match, use the parent
                if (machine == default)
                    machine = key;

                // Remove the key from the list
                parents[key].Remove(machine);

                // Remove the rest of the items from this key
                parents[key].ForEach(k => RemoveBucket(k));
            }

            // Finally, strip out the parent tags
            RemoveMachineRelationshipTagsImpl();
        }

        /// <summary>
        /// Filter a DAT using 1G1R logic given an ordered set of regions
        /// </summary>
        /// <param name="regionList">List of regions in order of priority</param>
        /// <remarks>Applies to <see cref="ItemsDB"/></remarks>
        private void SetOneGamePerRegionImplDB(List<string> regionList)
        {
            // Then we want to get a mapping of all machines to parents
            Dictionary<string, List<string>> parents = [];
            foreach (var machine in GetMachinesDB())
            {
                // Get machine information
                string? machineName = machine.Value?.Name?.ToLowerInvariant();
                if (machine.Value is null || machineName is null)
                    continue;

                // Get the string values
                string? cloneOf = machine.Value.CloneOf?.ToLowerInvariant();
                string? romOf = machine.Value.RomOf?.ToLowerInvariant();

                // Match on CloneOf first
                if (!string.IsNullOrEmpty(cloneOf))
                {
                    if (!parents.ContainsKey(cloneOf!))
                        parents.Add(cloneOf!, []);

                    parents[cloneOf!].Add(machineName);
                }

                // Then by RomOf
                else if (!string.IsNullOrEmpty(romOf))
                {
                    if (!parents.ContainsKey(romOf!))
                        parents.Add(romOf!, []);

                    parents[romOf!].Add(machineName);
                }

                // Otherwise, treat it as a parent
                else
                {
                    if (!parents.ContainsKey(machineName))
                        parents.Add(machineName, []);

                    parents[machineName].Add(machineName);
                }
            }

            // Once we have the full list of mappings, filter out games to keep
            foreach (string key in parents.Keys)
            {
                // Find the first machine that matches the regions in order, if possible
                string? machineName = default;
                foreach (string region in regionList)
                {
                    machineName = parents[key].Find(m => Regex.IsMatch(m, @"\(.*" + region + @".*\)", RegexOptions.IgnoreCase));
                    if (machineName != default)
                        break;
                }

                // If we didn't get a match, use the parent
                if (machineName == default)
                    machineName = key;

                // Remove the key from the list
                parents[key].Remove(machineName);

                // Remove the rest of the items from this key
                parents[key].ForEach(k => RemoveMachineDB(k));
            }

            // Finally, strip out the parent tags
            RemoveMachineRelationshipTagsImplDB();
        }

        /// <summary>
        /// Ensure that all roms are in their own game (or at least try to ensure)
        /// </summary>
        /// <remarks>Applies to <see cref="Items"/></remarks>
        private void SetOneRomPerGameImpl()
        {
            // For each rom, we want to update the game to be "<game name>/<rom name>"
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
            Parallel.ForEach(Items.SortedKeys, key =>
#else
            foreach (var key in Items.SortedKeys)
#endif
            {
                var items = GetItemsForBucket(key);
                if (items is null)
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
                    return;
#else
                    continue;
#endif

                for (int i = 0; i < items.Count; i++)
                {
                    SetOneRomPerGameImpl(items[i]);
                }
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
            });
#else
            }
#endif
        }

        /// <summary>
        /// Set internal names to match One Rom Per Game (ORPG) logic
        /// </summary>
        /// <param name="datItem">DatItem to run logic on</param>
        /// <remarks>Applies to <see cref="Items"/></remarks>
        private static void SetOneRomPerGameImpl(DatItem datItem)
        {
            // If the item name is null
            string? itemName = datItem.GetName();
            if (itemName is null)
                return;

            // Get the current machine
            var machine = datItem.Machine;
            if (machine is null)
                return;

            // Clone current machine to avoid conflict
            machine = (Machine)machine.Clone();

            // Reassign the item to the new machine
            datItem.Machine = machine;

            // Remove extensions from File and Rom items
            if (datItem is DatItems.Formats.File || datItem is Rom)
            {
                string[] splitname = itemName.Split('.');
                itemName = machine.Name
                    + $"/{string.Join(".", splitname, 0, splitname.Length > 1 ? splitname.Length - 1 : 1)}";
            }
            else
            {
                itemName = machine.Name + $"/{itemName}";
            }

            // Strip off "Default" prefix only for ORPG
            if (itemName.StartsWith("Default"))
                itemName = itemName.Substring("Default".Length + 1);

            machine.Name = itemName;
            datItem.SetName(Path.GetFileName(datItem.GetName()));
        }

        /// <summary>
        /// Ensure that all roms are in their own game (or at least try to ensure)
        /// </summary>
        /// <remarks>Applies to <see cref="ItemsDB"/></remarks>
        private void SetOneRomPerGameImplDB()
        {
            // For each rom, we want to update the game to be "<game name>/<rom name>"
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
            Parallel.ForEach(ItemsDB.SortedKeys, key =>
#else
            foreach (var key in ItemsDB.SortedKeys)
#endif
            {
                var items = GetItemsForBucketDB(key);
                if (items is null)
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
                    return;
#else
                    continue;
#endif

                foreach (var item in items)
                {
                    SetOneRomPerGameImplDB(item);
                }
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
            });
#else
            }
#endif
        }

        /// <summary>
        /// Set internal names to match One Rom Per Game (ORPG) logic
        /// </summary>
        /// <param name="datItem">DatItem to run logic on</param>
        /// <remarks>Applies to <see cref="ItemsDB"/></remarks>
        private void SetOneRomPerGameImplDB(KeyValuePair<long, DatItem> datItem)
        {
            // If the item name is null
            string? itemName = datItem.Value.GetName();
            if (datItem.Key < 0 || itemName is null)
                return;

            // Get the current machine
            var machine = GetMachineDB(datItem.Value.MachineIndex);
            if (machine.Value is null)
                return;

            // Clone current machine to avoid conflict
            long newMachineIndex = AddMachineDB((Machine)machine.Value.Clone());
            machine = GetMachineDB(newMachineIndex);
            if (machine.Value is null)
                return;

            // Reassign the item to the new machine
            datItem.Value.MachineIndex = newMachineIndex;

            // Remove extensions from File and Rom items
            if (datItem.Value is DatItems.Formats.File || datItem.Value is Rom)
            {
                string[] splitname = itemName.Split('.');
                itemName = machine.Value.Name
                    + $"/{string.Join(".", splitname, 0, splitname.Length > 1 ? splitname.Length - 1 : 1)}";
            }
            else
            {
                itemName = machine.Value.Name + $"/{itemName}";
            }

            // Strip off "Default" prefix only for ORPG
            if (itemName.StartsWith("Default"))
                itemName = itemName.Substring("Default".Length + 1);

            machine.Value.Name = itemName;
            datItem.Value.SetName(Path.GetFileName(datItem.Value.GetName()));
        }

        /// <summary>
        /// Strip the dates from the beginning of scene-style set names
        /// </summary>
        /// <remarks>Applies to <see cref="Items"/></remarks>
        private void StripSceneDatesFromItemsImpl()
        {
            // Now process all of the roms
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
            Parallel.ForEach(Items.SortedKeys, key =>
#else
            foreach (var key in Items.SortedKeys)
#endif
            {
                var items = GetItemsForBucket(key);
                if (items is null)
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
                    return;
#else
                    continue;
#endif

                foreach (DatItem item in items)
                {
                    // Get the current machine
                    var machine = item.Machine;
                    if (machine is null)
                        continue;

                    // Get the values to check against
                    string? machineName = machine.Name;
                    string? machineDesc = machine.Description;

#pragma warning disable SYSLIB1045 // Convert to 'GeneratedRegexAttribute'
                    if (machineName is not null && Regex.IsMatch(machineName, SceneNamePattern))
                        item.Machine!.Name = Regex.Replace(machineName, SceneNamePattern, "$2");

                    if (machineDesc is not null && Regex.IsMatch(machineDesc, SceneNamePattern))
                        item.Machine!.Description = Regex.Replace(machineDesc, SceneNamePattern, "$2");
#pragma warning restore SYSLIB1045
                }
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
            });
#else
            }
#endif
        }

        /// <summary>
        /// Strip the dates from the beginning of scene-style set names
        /// </summary>
        /// <remarks>Applies to <see cref="ItemsDB"/></remarks>
        private void StripSceneDatesFromItemsImplDB()
        {
            // Now process all of the machines
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
            Parallel.ForEach(GetMachinesDB(), machine =>
#else
            foreach (var machine in GetMachinesDB())
#endif
            {
                // Get the values to check against
                string? machineName = machine.Value.Name;
                string? machineDesc = machine.Value.Description;

#pragma warning disable SYSLIB1045 // Convert to 'GeneratedRegexAttribute'
                if (machineName is not null && Regex.IsMatch(machineName, SceneNamePattern))
                    machine.Value.Name = Regex.Replace(machineName, SceneNamePattern, "$2");

                if (machineDesc is not null && Regex.IsMatch(machineDesc, SceneNamePattern))
                    machine.Value.Description = Regex.Replace(machineDesc, SceneNamePattern, "$2");
#pragma warning restore SYSLIB1045
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
            });
#else
            }
#endif
        }

        /// <summary>
        /// Update machine names from descriptions according to mappings
        /// </summary>
        /// <remarks>Applies to <see cref="Items"/></remarks>
        private void UpdateMachineNamesFromDescriptions(IDictionary<string, string> mapping)
        {
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
            Parallel.ForEach(Items.SortedKeys, key =>
#else
            foreach (var key in Items.SortedKeys)
#endif
            {
                var items = GetItemsForBucket(key);
                if (items is null)
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
                    return;
#else
                    continue;
#endif

                foreach (DatItem item in items)
                {
                    // Get the current machine
                    var machine = item.Machine;
                    if (machine is null)
                        continue;

                    // Get the values to check against
                    string? machineName = machine.Name;
                    string? cloneOf = machine.CloneOf;
                    string? romOf = machine.RomOf;
                    string? sampleOf = machine.SampleOf;

                    // Update machine name
                    if (machineName is not null && mapping.TryGetValue(machineName, out string? mappedMachineName))
                        machine.Name = mappedMachineName;

                    // Update cloneof
                    if (cloneOf is not null && mapping.TryGetValue(cloneOf, out string? mappedCloneOf))
                        machine.CloneOf = mappedCloneOf;

                    // Update romof
                    if (romOf is not null && mapping.TryGetValue(romOf, out string? mappedRomOf))
                        machine.RomOf = mappedRomOf;

                    // Update sampleof
                    if (sampleOf is not null && mapping.TryGetValue(sampleOf, out string? mappedSampleOf))
                        machine.SampleOf = mappedSampleOf;
                }
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
            });
#else
            }
#endif
        }

        /// <summary>
        /// Update machine names from descriptions according to mappings
        /// </summary>
        /// <remarks>Applies to <see cref="ItemsDB"/></remarks>
        private void UpdateMachineNamesFromDescriptionsDB(Dictionary<string, string> mapping)
        {
            foreach (var machine in GetMachinesDB())
            {
                // Get the values to check against
                string? machineName = machine.Value.Name;
                string? cloneOf = machine.Value.CloneOf;
                string? romOf = machine.Value.RomOf;
                string? sampleOf = machine.Value.SampleOf;

                // Update machine name
                if (machineName is not null && mapping.TryGetValue(machineName, out string? mappedMachineName))
                    machine.Value.Name = mappedMachineName;

                // Update cloneof
                if (cloneOf is not null && mapping.TryGetValue(cloneOf, out string? mappedCloneOf))
                    machine.Value.CloneOf = mappedCloneOf;

                // Update romof
                if (romOf is not null && mapping.TryGetValue(romOf, out string? mappedRomOf))
                    machine.Value.RomOf = mappedRomOf;

                // Update sampleof
                if (sampleOf is not null && mapping.TryGetValue(sampleOf, out string? mappedSampleOf))
                    machine.Value.SampleOf = mappedSampleOf;
            }
        }

        #endregion
    }
}
