// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Diagnostics.Contracts; using System.Linq; using System.Runtime.CompilerServices; namespace CommunityToolkit.Common.Collections; /// /// The extensions methods to simplify the usage of . /// public static class ObservableGroupedCollectionExtensions { /// /// Return the first group with key. /// /// The type of the group key. /// The type of the items in the collection. /// The source instance. /// The key of the group to query. /// The first group matching . /// The target group does not exist. [Pure] public static ObservableGroup First(this ObservableGroupedCollection source, TKey key) where TKey : notnull { ObservableGroup? group = source.FirstOrDefault(key); if (group is null) { static void ThrowArgumentExceptionForKeyNotFound() { throw new InvalidOperationException("The requested key was not present in the collection."); } ThrowArgumentExceptionForKeyNotFound(); } return group!; } /// /// Return the first group with key or null if not found. /// /// The type of the group key. /// The type of the items in the collection. /// The source instance. /// The key of the group to query. /// The first group matching or null. [Pure] public static ObservableGroup? FirstOrDefault(this ObservableGroupedCollection source, TKey key) where TKey : notnull { if (source.TryGetList(out List>? list)) { foreach (ObservableGroup? group in list) { if (EqualityComparer.Default.Equals(group.Key, key)) { return group; } } return null; } // Fallback method [MethodImpl(MethodImplOptions.NoInlining)] static ObservableGroup? FirstOrDefaultWithLinq(ObservableGroupedCollection source, TKey key) { return source.FirstOrDefault(group => EqualityComparer.Default.Equals(group.Key, key)); } return FirstOrDefaultWithLinq(source, key); } /// /// Return the element at position from the first group with key. /// /// The type of the group key. /// The type of the items in the collection. /// The source instance. /// The key of the group to query. /// The index of the item from the targeted group. /// The element. /// The target group does not exist. /// is less than zero or is greater than the group elements' count. [Pure] public static TValue ElementAt( this ObservableGroupedCollection source, TKey key, int index) where TKey : notnull => source.First(key)[index]; /// /// Return the element at position from the first group with key. /// /// The type of the group key. /// The type of the items in the collection. /// The source instance. /// The key of the group to query. /// The index of the item from the targeted group. /// The element or default(TValue) if it does not exist. [Pure] public static TValue? ElementAtOrDefault( this ObservableGroupedCollection source, TKey key, int index) where TKey : notnull { ObservableGroup? group = source.FirstOrDefault(key); if (group is null || (uint)index >= (uint)group.Count) { return default; } return group[index]; } /// /// Adds a key-value item into a target . /// /// The type of the group key. /// The type of the items in the collection. /// The source instance. /// The key of the group where will be added. /// The value to add. /// The added . public static ObservableGroup AddGroup( this ObservableGroupedCollection source, TKey key, TValue value) where TKey : notnull => AddGroup(source, key, new[] { value }); /// /// Adds a key-collection item into a target . /// /// The type of the group key. /// The type of the items in the collection. /// The source instance. /// The key of the group where will be added. /// The collection to add. /// The added . public static ObservableGroup AddGroup( this ObservableGroupedCollection source, TKey key, params TValue[] collection) where TKey : notnull => source.AddGroup(key, (IEnumerable)collection); /// /// Adds a key-collection item into a target . /// /// The type of the group key. /// The type of the items in the collection. /// The source instance. /// The key of the group where will be added. /// The collection to add. /// The added . public static ObservableGroup AddGroup( this ObservableGroupedCollection source, TKey key, IEnumerable collection) where TKey : notnull { ObservableGroup? group = new(key, collection); source.Add(group); return group; } /// /// Add into the first group with key. /// If the group does not exist, it will be added. /// /// The type of the group key. /// The type of the items in the collection. /// The source instance. /// The key of the group where the should be added. /// The item to add. /// The instance of the which will receive the value. It will either be an existing group or a new group. public static ObservableGroup AddItem( this ObservableGroupedCollection source, TKey key, TValue item) where TKey : notnull { ObservableGroup? group = source.FirstOrDefault(key); if (group is null) { group = new ObservableGroup(key); source.Add(group); } group.Add(item); return group; } /// /// Insert into the first group with key at . /// /// The type of the group key. /// The type of the items in the collection. /// The source instance. /// The key of the group where to insert . /// The index where to insert . /// The item to add. /// The instance of the which will receive the value. /// The target group does not exist. /// is less than zero or is greater than the group elements' count. public static ObservableGroup InsertItem( this ObservableGroupedCollection source, TKey key, int index, TValue item) where TKey : notnull { ObservableGroup? existingGroup = source.First(key); existingGroup.Insert(index, item); return existingGroup; } /// /// Replace the element at with in the first group with key. /// /// The type of the group key. /// The type of the items in the collection. /// The source instance. /// The key of the group where to replace the item. /// The index where to insert . /// The item to add. /// The instance of the which will receive the value. /// The target group does not exist. /// is less than zero or is greater than the group elements' count. public static ObservableGroup SetItem( this ObservableGroupedCollection source, TKey key, int index, TValue item) where TKey : notnull { ObservableGroup? existingGroup = source.First(key); existingGroup[index] = item; return existingGroup; } /// /// Remove the first occurrence of the group with from the grouped collection. /// It will not do anything if the group does not exist. /// /// The type of the group key. /// The type of the items in the collection. /// The source instance. /// The key of the group to remove. public static void RemoveGroup( this ObservableGroupedCollection source, TKey key) where TKey : notnull { if (source.TryGetList(out List>? list)) { int index = 0; foreach (ObservableGroup? group in list) { if (EqualityComparer.Default.Equals(group.Key, key)) { source.RemoveAt(index); return; } index++; } } else { // Fallback method [MethodImpl(MethodImplOptions.NoInlining)] static void RemoveGroupWithLinq(ObservableGroupedCollection source, TKey key) { int index = 0; foreach (ObservableGroup? group in source) { if (EqualityComparer.Default.Equals(group.Key, key)) { source.RemoveAt(index); return; } index++; } } RemoveGroupWithLinq(source, key); } } /// /// Remove the first from the first group with from the grouped collection. /// It will not do anything if the group or the item does not exist. /// /// The type of the group key. /// The type of the items in the collection. /// The source instance. /// The key of the group where the should be removed. /// The item to remove. /// If true (default value), the group will be removed once it becomes empty. public static void RemoveItem( this ObservableGroupedCollection source, TKey key, TValue item, bool removeGroupIfEmpty = true) where TKey : notnull { if (source.TryGetList(out List>? list)) { int index = 0; foreach (ObservableGroup? group in list) { if (EqualityComparer.Default.Equals(group.Key, key)) { if (group.Remove(item) && removeGroupIfEmpty && group.Count == 0) { source.RemoveAt(index); } return; } index++; } } else { // Fallback method [MethodImpl(MethodImplOptions.NoInlining)] static void RemoveItemWithLinq( ObservableGroupedCollection source, TKey key, TValue item, bool removeGroupIfEmpty) { int index = 0; foreach (ObservableGroup? group in source) { if (EqualityComparer.Default.Equals(group.Key, key)) { if (group.Remove(item) && removeGroupIfEmpty && group.Count == 0) { source.RemoveAt(index); } return; } index++; } } RemoveItemWithLinq(source, key, item, removeGroupIfEmpty); } } /// /// Remove the item at from the first group with from the grouped collection. /// It will not do anything if the group or the item does not exist. /// /// The type of the group key. /// The type of the items in the collection. /// The source instance. /// The key of the group where the item at should be removed. /// The index of the item to remove in the group. /// If true (default value), the group will be removed once it becomes empty. /// is less than zero or is greater than the group elements' count. public static void RemoveItemAt( this ObservableGroupedCollection source, TKey key, int index, bool removeGroupIfEmpty = true) where TKey : notnull { if (source.TryGetList(out List>? list)) { int groupIndex = 0; foreach (ObservableGroup? group in list) { if (EqualityComparer.Default.Equals(group.Key, key)) { group.RemoveAt(index); if (removeGroupIfEmpty && group.Count == 0) { source.RemoveAt(groupIndex); } return; } groupIndex++; } } else { // Fallback method [MethodImpl(MethodImplOptions.NoInlining)] static void RemoveItemAtWithLinq( ObservableGroupedCollection source, TKey key, int index, bool removeGroupIfEmpty) { int groupIndex = 0; foreach (ObservableGroup? group in source) { if (EqualityComparer.Default.Equals(group.Key, key)) { group.RemoveAt(index); if (removeGroupIfEmpty && group.Count == 0) { source.RemoveAt(groupIndex); } return; } groupIndex++; } } RemoveItemAtWithLinq(source, key, index, removeGroupIfEmpty); } } }