<commit_before>// Copyright (c) ppy Pty Ltd <<EMAIL>>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

#nullable enable

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using osu.Framework.Caching;
using osu.Framework.Lists;

namespace osu.Framework.Bindables
{
    public class BindableDictionary<TKey, TValue> : IBindableDictionary<TKey, TValue>, IBindable, IParseable, IDictionary<TKey, TValue>, IDictionary
        where TKey : notnull
    {
        /// <summary>
        /// An event which is raised when this <see cref="BindableDictionary{TKey, TValue}"/> changes.
        /// </summary>
        public event NotifyDictionaryChangedEventHandler<TKey, TValue>? CollectionChanged;

        /// <summary>
        /// An event which is raised when <see cref="Disabled"/>'s state has changed (or manually via <see cref="triggerDisabledChange(bool)"/>).
        /// </summary>
        public event Action<bool>? DisabledChanged;

        private readonly Dictionary<TKey, TValue> collection;

        private readonly Cached<WeakReference<BindableDictionary<TKey, TValue>>> weakReferenceCache = new Cached<WeakReference<BindableDictionary<TKey, TValue>>>();

        private WeakReference<BindableDictionary<TKey, TValue>> weakReference
            => weakReferenceCache.IsValid ? weakReferenceCache.Value : weakReferenceCache.Value = new WeakReference<BindableDictionary<TKey, TValue>>(this);

        private LockedWeakList<BindableDictionary<TKey, TValue>>? bindings;

        /// <summary>
        /// Creates a new <see cref="BindableDictionary{TKey,TValue}"/>.
        /// </summary>
        /// <param name="comparer">An optional key-comparer.</param>
        public BindableDictionary(IEqualityComparer<TKey>? comparer = null)
            : this(0, comparer)
        {
        }

        /// <summary>
        /// Creates a new <see cref="BindableDictionary{TKey,TValue}"/>.
        /// </summary>
        /// <param name="dictionary">A source dictionary to populate this dictionary from.</param>
        /// <param name="comparer">An optional key-comparer.</param>
        public BindableDictionary(IDictionary<TKey, TValue> dictionary, IEqualityComparer<TKey>? comparer = null)
            : this((IEnumerable<KeyValuePair<TKey, TValue>>)dictionary, comparer)
        {
        }

        /// <summary>
        /// Creates a new <see cref="BindableDictionary{TKey,TValue}"/>.
        /// </summary>
        /// <param name="capacity">The initial capacity.</param>
        /// <param name="comparer">An optional key-comparer.</param>
        public BindableDictionary(int capacity, IEqualityComparer<TKey>? comparer = null)
        {
            collection = new Dictionary<TKey, TValue>(capacity, comparer);
        }

        /// <summary>
        /// Creates a new <see cref="BindableDictionary{TKey,TValue}"/>.
        /// </summary>
        /// <param name="collection">A source enumerable to populate this dictionary from.</param>
        /// <param name="comparer">An optional key-comparer.</param>
        public BindableDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection, IEqualityComparer<TKey>? comparer = null)
        {
            this.collection = new Dictionary<TKey, TValue>(collection, comparer);
        }

        #region IDictionary<TKey, Value>

        /// <summary>
        /// Adds a single item to this <see cref="BindableDictionary{TKey,TValue}"/>.
        /// </summary>
        /// <param name="key">The item key.</param>
        /// <param name="value">The item value.</param>
        /// <exception cref="InvalidOperationException">Thrown when this <see cref="BindableDictionary{TKey, TValue}"/> is <see cref="Disabled"/>.</exception>
        public void Add(TKey key, TValue value)
            => add(key, value, null);

        private void add(TKey key, TValue value, BindableDictionary<TKey, TValue>? caller)
        {
            ensureMutationAllowed();

            collection.Add(key, value);

            if (bindings != null)
            {
                foreach (var b in bindings)
                {
                    Debug.Assert(b != null);

                    // prevent re-adding the item back to the callee.
                    // That would result in a <see cref="StackOverflowException"/>.
                    if (b != caller)
                        b.add(key, value, this);
                }
            }

            notifyDictionaryChanged(new NotifyDictionaryChangedEventArgs<TKey, TValue>(NotifyDictionaryChangedAction.Add, new KeyValuePair<TKey, TValue>(key, value)));
        }

        public bool ContainsKey(TKey key) => collection.ContainsKey(key);

        /// <summary>
        /// Removes an item from this <see cref="BindableDictionary{TKey,TValue}"/>.
        /// </summary>
        /// <param name="key">The item key.</param>
        /// <returns><code>true</code> if the removal was successful.</returns>
        /// <exception cref="InvalidOperationException">Thrown if this <see cref="BindableDictionary{TKey, TValue}"/> is <see cref="Disabled"/>.</exception>
        public bool Remove(TKey key)
            => remove(key, out _, null);

        /// <summary>
        /// Removes an item from this <see cref="BindableDictionary{TKey,TValue}"/>.
        /// </summary>
        /// <param name="key">The item key.</param>
        /// <param name="value">The removed item value.</param>
        /// <returns><code>true</code> if the removal was successful.</returns>
        /// <exception cref="InvalidOperationException">Thrown if this <see cref="BindableDictionary{TKey, TValue}"/> is <see cref="Disabled"/>.</exception>
        public bool Remove(TKey key, [MaybeNullWhen(false)] out TValue value)
            => remove(key, out value, null);

        private bool remove(TKey key, [MaybeNullWhen(false)] out TValue value, BindableDictionary<TKey, TValue>? caller)
        {
            ensureMutationAllowed();

            if (!collection.Remove(key, out value))
                return false;

            if (bindings != null)
            {
                foreach (var b in bindings)
                {
                    Debug.Assert(b != null);

                    // prevent re-adding the item back to the callee.
                    // That would result in a <see cref="StackOverflowException"/>.
                    if (b != caller)
                        b.remove(key, out _, this);
                }
            }

            notifyDictionaryChanged(new NotifyDictionaryChangedEventArgs<TKey, TValue>(NotifyDictionaryChangedAction.Remove, new KeyValuePair<TKey, TValue>(key, value)));

            return true;
        }

        public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) => collection.TryGetValue(key, out value);

        /// <summary>
        /// Gets or sets an item with a specified key in this <see cref="BindableDictionary{TKey,TValue}"/>.
        /// </summary>
        /// <param name="key">The item key.</param>
        /// <exception cref="InvalidOperationException">Thrown when setting an item while this <see cref="BindableDictionary{TKey, TValue}"/> is <see cref="Disabled"/>.</exception>
        public TValue this[TKey key]
        {
            get => collection[key];
            set => setKey(key, value, null);
        }

        private void setKey(TKey key, TValue value, BindableDictionary<TKey, TValue>? caller)
        {
            ensureMutationAllowed();<commit_msg>Fix warning when compiling against netstandard<commit_after>// Copyright (c) ppy Pty Ltd <<EMAIL>>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

#nullable enable

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using osu.Framework.Caching;
using osu.Framework.Lists;

namespace osu.Framework.Bindables
{
    public class BindableDictionary<TKey, TValue> : IBindableDictionary<TKey, TValue>, IBindable, IParseable, IDictionary<TKey, TValue>, IDictionary
        where TKey : notnull
    {
        /// <summary>
        /// An event which is raised when this <see cref="BindableDictionary{TKey, TValue}"/> changes.
        /// </summary>
        public event NotifyDictionaryChangedEventHandler<TKey, TValue>? CollectionChanged;

        /// <summary>
        /// An event which is raised when <see cref="Disabled"/>'s state has changed (or manually via <see cref="triggerDisabledChange(bool)"/>).
        /// </summary>
        public event Action<bool>? DisabledChanged;

        private readonly Dictionary<TKey, TValue> collection;

        private readonly Cached<WeakReference<BindableDictionary<TKey, TValue>>> weakReferenceCache = new Cached<WeakReference<BindableDictionary<TKey, TValue>>>();

        private WeakReference<BindableDictionary<TKey, TValue>> weakReference
            => weakReferenceCache.IsValid ? weakReferenceCache.Value : weakReferenceCache.Value = new WeakReference<BindableDictionary<TKey, TValue>>(this);

        private LockedWeakList<BindableDictionary<TKey, TValue>>? bindings;

        /// <summary>
        /// Creates a new <see cref="BindableDictionary{TKey,TValue}"/>.
        /// </summary>
        /// <param name="comparer">An optional key-comparer.</param>
        public BindableDictionary(IEqualityComparer<TKey>? comparer = null)
            : this(0, comparer)
        {
        }

        /// <summary>
        /// Creates a new <see cref="BindableDictionary{TKey,TValue}"/>.
        /// </summary>
        /// <param name="dictionary">A source dictionary to populate this dictionary from.</param>
        /// <param name="comparer">An optional key-comparer.</param>
        public BindableDictionary(IDictionary<TKey, TValue> dictionary, IEqualityComparer<TKey>? comparer = null)
            : this((IEnumerable<KeyValuePair<TKey, TValue>>)dictionary, comparer)
        {
        }

        /// <summary>
        /// Creates a new <see cref="BindableDictionary{TKey,TValue}"/>.
        /// </summary>
        /// <param name="capacity">The initial capacity.</param>
        /// <param name="comparer">An optional key-comparer.</param>
        public BindableDictionary(int capacity, IEqualityComparer<TKey>? comparer = null)
        {
            collection = new Dictionary<TKey, TValue>(capacity, comparer);
        }

        /// <summary>
        /// Creates a new <see cref="BindableDictionary{TKey,TValue}"/>.
        /// </summary>
        /// <param name="collection">A source enumerable to populate this dictionary from.</param>
        /// <param name="comparer">An optional key-comparer.</param>
        public BindableDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection, IEqualityComparer<TKey>? comparer = null)
        {
            this.collection = new Dictionary<TKey, TValue>(collection, comparer);
        }

        #region IDictionary<TKey, Value>

        /// <summary>
        /// Adds a single item to this <see cref="BindableDictionary{TKey,TValue}"/>.
        /// </summary>
        /// <param name="key">The item key.</param>
        /// <param name="value">The item value.</param>
        /// <exception cref="InvalidOperationException">Thrown when this <see cref="BindableDictionary{TKey, TValue}"/> is <see cref="Disabled"/>.</exception>
        public void Add(TKey key, TValue value)
            => add(key, value, null);

        private void add(TKey key, TValue value, BindableDictionary<TKey, TValue>? caller)
        {
            ensureMutationAllowed();

            collection.Add(key, value);

            if (bindings != null)
            {
                foreach (var b in bindings)
                {
                    Debug.Assert(b != null);

                    // prevent re-adding the item back to the callee.
                    // That would result in a <see cref="StackOverflowException"/>.
                    if (b != caller)
                        b.add(key, value, this);
                }
            }

            notifyDictionaryChanged(new NotifyDictionaryChangedEventArgs<TKey, TValue>(NotifyDictionaryChangedAction.Add, new KeyValuePair<TKey, TValue>(key, value)));
        }

        public bool ContainsKey(TKey key) => collection.ContainsKey(key);

        /// <summary>
        /// Removes an item from this <see cref="BindableDictionary{TKey,TValue}"/>.
        /// </summary>
        /// <param name="key">The item key.</param>
        /// <returns><code>true</code> if the removal was successful.</returns>
        /// <exception cref="InvalidOperationException">Thrown if this <see cref="BindableDictionary{TKey, TValue}"/> is <see cref="Disabled"/>.</exception>
        public bool Remove(TKey key)
            => remove(key, out _, null);

        /// <summary>
        /// Removes an item from this <see cref="BindableDictionary{TKey,TValue}"/>.
        /// </summary>
        /// <param name="key">The item key.</param>
        /// <param name="value">The removed item value.</param>
        /// <returns><code>true</code> if the removal was successful.</returns>
        /// <exception cref="InvalidOperationException">Thrown if this <see cref="BindableDictionary{TKey, TValue}"/> is <see cref="Disabled"/>.</exception>
        public bool Remove(TKey key, [MaybeNullWhen(false)] out TValue value)
            => remove(key, out value, null);

        private bool remove(TKey key, [MaybeNullWhen(false)] out TValue value, BindableDictionary<TKey, TValue>? caller)
        {
            ensureMutationAllowed();

            if (!collection.Remove(key, out value))
                return false;

            if (bindings != null)
            {
                foreach (var b in bindings)
                {
                    Debug.Assert(b != null);

                    // prevent re-adding the item back to the callee.
                    // That would result in a <see cref="StackOverflowException"/>.
                    if (b != caller)
                        b.remove(key, out _, this);
                }
            }

            notifyDictionaryChanged(new NotifyDictionaryChangedEventArgs<TKey, TValue>(NotifyDictionaryChangedAction.Remove, new KeyValuePair<TKey, TValue>(key, value)));

            return true;
        }

#if NETSTANDARD
        public bool TryGetValue(TKey key, out TValue value) => collection.TryGetValue(key, out value);
#else
        public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) => collection.TryGetValue(key, out value);
#endif

        /// <summary>
        /// Gets or sets an item with a specified key in this <see cref="BindableDictionary{TKey,TValue}"/>.
        /// </summary>
        /// <param name="key">The item key.</param>
        /// <exception cref="InvalidOperationException">Thrown when setting an item while this <see cref="BindableDictionary{TKey, TValue}"/> is <see cref="Disabled"/>.</exception>
        public TValue this[TKey key]
        {
            get => collection[key];
            set => setKey(key, value, null);
        }

        private void setKey(TKey key, TValue value, BindableDictionary<TKey, TValue>? caller)
        {
            ensureMutationAllowed();
{'fraction_non_alphanumeric': 0.1273386418074707, 'fraction_numerical': 0.00012947497896031592, 'mean_word_length': 2.4131683605832963, 'pattern_counts': {'":': 0, '<': 247, '<?xml version=': 0, '>': 264, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3424964', 'n_tokens_mistral': 4132, 'n_tokens_neox': 4046, 'n_words': 1080}