// Copyright(c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace NuGet.Configuration
{
    public sealed class VirtualSettingSection : SettingSection
    {
        internal VirtualSettingSection(SettingSection section)
            : this(section.ElementName, section.Attributes, section.Items)
        {
        }

        internal VirtualSettingSection(string name, params SettingItem[] children)
            : this(name, attributes: null, children: new HashSet<SettingItem>(children))
        {
        }

        internal VirtualSettingSection(string name, IReadOnlyDictionary<string, string> attributes, IEnumerable<SettingItem> children)
            : base(name, attributes, children)
        {
        }

        internal VirtualSettingSection Merge(SettingSection other)
        {
            if (!Equals(other))
            {
                throw new ArgumentException(Resources.Error_MergeTwoDifferentSections);
            }

            foreach (var item in other.Items.Where(item => item != null))
            {
                if (item is ClearItem)
                {
                    if (CanBeCleared)
                    {
                        Children.Clear();
                    }

                    Children.Add(item);

                    continue;
                }

                if (TryGetChild(item, out var currentItem))
                {
                    if (item is UnknownItem unknown)
                    {
                        unknown.Merge(currentItem as UnknownItem);
                    }

                    item.MergedWith = currentItem;
                    Children.Remove(currentItem);
                    Children.Add(item);
                }
                else
                {
                    Children.Add(item);
                }
            }

            return this;
        }

        internal override bool Add(SettingItem setting)
        {
            if (setting == null)
            {
                throw new ArgumentNullException(nameof(setting));
            }

            if (!Children.Contains(setting) && !setting.IsEmpty())
            {
                Children.Add(setting);

                return true;
            }

            return false;
        }

        internal override void Remove(SettingItem setting)
        {
            if (setting == null)
            {
                throw new ArgumentNullException(nameof(setting));
            }

            if (TryGetChild(setting, out var currentSetting))
            {
                Debug.Assert(!currentSetting.IsAbstract());

                if (currentSetting.Origin != null && currentSetting.Origin.IsMachineWide)
                {
                    throw new InvalidOperationException(Resources.CannotUpdateMachineWide);
                }

                if (currentSetting.Origin != null && currentSetting.Origin.IsReadOnly)
                {
                    throw new InvalidOperationException(Resources.CannotUpdateReadOnlyConfig);
                }

                if (Children.Remove(currentSetting))
                {
                    // Remove it from the appropriate config
                    if (currentSetting.Parent != null && currentSetting.Parent != this)
                    {
                        currentSetting.Parent.Remove(currentSetting);
                    }
                }

                if (currentSetting.MergedWith != null)
                {
                    // Add that back to the set since, we should leave the machine wide setting intact.
                    if (!TryRemoveAllMergedWith(currentSetting, out var undeletedItem))
                    {
                        Children.Add(undeletedItem);
                    }
                }
            }
        }

        private bool TryRemoveAllMergedWith(SettingItem currentSetting, out SettingItem undeletedItem)
        {
            undeletedItem = null;
            var mergedSettings = new List<SettingItem>();
            var mergedWith = currentSetting.MergedWith;
            while (mergedWith != null)
            {
                mergedSettings.Add(mergedWith);
                mergedWith = mergedWith.MergedWith;
            }

            foreach (var elementToDelete in mergedSettings)
            {
                try
                {
                    elementToDelete.Parent.Remove(elementToDelete);
                }
                // This means setting was merged with a machine wide settings.
                catch
                {
                    undeletedItem = elementToDelete;
                    return false;
                }
            }

            return true;
        }

        public override SettingBase Clone()
        {
            return new VirtualSettingSection(ElementName, Attributes, Items.Select(s => s.Clone() as SettingItem));
        }
    }
}
