// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
namespace ICSharpCode.SharpDevelop.Dom
{
///
/// Provides LINQ operators for .
///
public static class ModelCollectionLinq
{
// A general 'AsObservableCollection()' would be nice; but I don't see any good way
// to implement that without leaking memory.
// The problem is that IModelCollection is unordered; but ObservableCollection requires us to maintain a stable order.
#region OfType / Cast
public static IModelCollection OfType(this IModelCollection source)
{
return SelectMany(source, item => item is TResult ? new ImmutableModelCollection(new[] { (TResult)item }) : ImmutableModelCollection.Empty);
}
public static IModelCollection Cast(this IModelCollection source)
{
return SelectMany(source, item => new ImmutableModelCollection(new[] { (TResult)item }));
}
#endregion
#region Where
public static IModelCollection Where(this IModelCollection source, Func predicate)
{
return SelectMany(source, item => predicate(item) ? new ImmutableModelCollection(new[] { item }) : ImmutableModelCollection.Empty);
}
#endregion
#region Select
public static IModelCollection Select(this IModelCollection source, Func selector)
{
return SelectMany(source, item => new ImmutableModelCollection(new[] { selector(item) }));
}
#endregion
#region SelectMany
public static IModelCollection SelectMany(this IModelCollection input, Func> selector)
{
return SelectMany(input, selector, (a, b) => b);
}
public static IModelCollection SelectMany(this IModelCollection source, Func> collectionSelector, Func resultSelector)
{
if (source == null)
throw new ArgumentNullException("source");
if (collectionSelector == null)
throw new ArgumentNullException("collectionSelector");
if (resultSelector == null)
throw new ArgumentNullException("resultSelector");
return new SelectManyModelCollection(source, collectionSelector, resultSelector);
}
sealed class SelectManyModelCollection : IModelCollection
{
readonly IModelCollection source;
readonly Func> collectionSelector;
readonly Func resultSelector;
List inputCollections;
public SelectManyModelCollection(IModelCollection source, Func> collectionSelector, Func resultSelector)
{
this.source = source;
this.collectionSelector = collectionSelector;
this.resultSelector = resultSelector;
}
ModelCollectionChangedEventHandler collectionChanged;
public event ModelCollectionChangedEventHandler CollectionChanged {
add {
if (value == null)
return;
if (inputCollections == null) {
source.CollectionChanged += OnSourceCollectionChanged;
inputCollections = new List();
foreach (var item in source) {
var inputCollection = new InputCollection(this, item);
inputCollection.RegisterEvent();
inputCollections.Add(inputCollection);
}
}
collectionChanged += value;
}
remove {
if (collectionChanged == null)
return;
collectionChanged -= value;
if (collectionChanged == null) {
source.CollectionChanged -= OnSourceCollectionChanged;
foreach (var inputCollection in inputCollections) {
inputCollection.UnregisterEvent();
}
inputCollections = null;
}
}
}
void OnCollectionChanged(IReadOnlyCollection removedItems, IReadOnlyCollection addedItems)
{
if (collectionChanged != null)
collectionChanged(removedItems, addedItems);
}
void OnSourceCollectionChanged(IReadOnlyCollection removedItems, IReadOnlyCollection addedItems)
{
List removedResults = new List();
foreach (TSource removedSource in removedItems) {
for (int i = 0; i < inputCollections.Count; i++) {
var inputCollection = inputCollections[i];
if (EqualityComparer.Default.Equals(inputCollection.source, removedSource)) {
inputCollection.AddResultsToList(removedResults);
inputCollection.UnregisterEvent();
inputCollections.RemoveAt(i);
break;
}
}
}
List addedResults = new List();
foreach (TSource addedSource in addedItems) {
var inputCollection = new InputCollection(this, addedSource);
inputCollection.AddResultsToList(addedResults);
inputCollection.RegisterEvent();
inputCollections.Add(inputCollection);
}
OnCollectionChanged(removedResults, addedResults);
}
class InputCollection
{
readonly SelectManyModelCollection parent;
internal readonly TSource source;
readonly IModelCollection collection;
public InputCollection(SelectManyModelCollection parent, TSource source)
{
this.parent = parent;
this.source = source;
this.collection = parent.collectionSelector(source);
}
public void AddResultsToList(List output)
{
foreach (var item in collection) {
output.Add(parent.resultSelector(source, item));
}
}
public void RegisterEvent()
{
collection.CollectionChanged += OnCollectionChanged;
}
public void UnregisterEvent()
{
collection.CollectionChanged -= OnCollectionChanged;
}
IReadOnlyCollection GetResults(IReadOnlyCollection itemsCollection)
{
List results = new List(itemsCollection.Count);
foreach (var item in itemsCollection) {
results.Add(parent.resultSelector(source, item));
}
return results;
}
void OnCollectionChanged(IReadOnlyCollection removedItems, IReadOnlyCollection addedItems)
{
parent.OnCollectionChanged(GetResults(removedItems), GetResults(addedItems));
}
}
IReadOnlyCollection IModelCollection.CreateSnapshot()
{
return this.ToList();
}
public IEnumerator GetEnumerator()
{
return source.AsEnumerable().SelectMany(collectionSelector, resultSelector).GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public int Count {
get {
return source.Sum(c => collectionSelector(c).Count);
}
}
}
#endregion
}
}