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