using System;
using System.Collections.Generic;
using Chernobyl.Event;
using Chernobyl.Utility;
using Chernobyl.Values;
namespace Chernobyl.Collections.Generic.Event
{
///
/// Behaves like the method
///
/// in that it projects each element of a sequence into a new form by
/// incorporating the element's index and maintains that form even if the
/// source changes.
///
/// The type of the elements of source.
/// The type of the value returned by selector.
public class SelectEventEnumerable : DecoratingEventEnumerable
{
///
/// Initializes a new instance of the
/// class.
///
/// A sequence of values to invoke a transform
/// function on.
/// A transform function to apply to each source
/// element. The second parameter represents the index of the source
/// element. The third parameter is the callback to invoke to keep the
/// transformed value up to date.
/// The method that is invoked when updates to
/// the transformed value are no longer needed. The second parameter
/// represents the index of the source element. The third parameter is
/// the callback to invoke to keep the transformed value up to date.
public SelectEventEnumerable(IEventEnumerable source,
Action>> selector,
Action>> unselector)
: this(source, selector, unselector, new Dictionary())
{}
///
/// Initializes a new instance of the
/// class.
///
/// A sequence of values to invoke a transform
/// function on.
/// A transform function to apply to each source
/// element. The second parameter represents the index of the source
/// element. The third parameter is the callback to invoke to keep the
/// transformed value up to date.
/// The method that is invoked when updates to
/// the transformed value are no longer needed. The second parameter
/// represents the index of the source element. The third parameter is
/// the callback to invoke to keep the transformed value up to date.
/// The being
/// decorated or extended with event capabilities.
protected SelectEventEnumerable(IEventEnumerable source,
Action>> selector,
Action>> unselector,
IDictionary items)
: base(items.Values)
{
selector.ThrowIfNull("selector");
_selector = selector;
unselector.ThrowIfNull("unselector");
_unselector = unselector;
source.ThrowIfNull("source");
Source = source;
items.ThrowIfNull("implementation");
_items = items;
}
///
/// A sequence of values to invoke a transform function on.
///
public IEventEnumerable Source
{
get { return _source; }
private set
{
// Unconfigure the old instance. If the ItemsNeeded is false then
// the unconfiguration has already occured.
if (_source != null && ItemsNeeded)
UnreadyItems();
_source = value;
// Configure the new instance but only if the the items from this
// instance are needed.
if (_source != null && ItemsNeeded)
ReadyItems();
}
}
///
/// Invoked when the items in this instance need to be ready for
/// iteration or events. This method is invoked when
/// is changed to true.
///
protected override void ReadyItems()
{
int index = 0;
foreach (TSource source in _source)
{
var eventHandler = CreateOnValueChanged(index);
_selector(source, index, eventHandler);
++index;
}
_source.ItemsAdded += OnSourceItemsAdded;
_source.ItemsRemoved += OnSourceItemsRemoved;
base.ReadyItems();
}
///
/// Invoked when the items in this instance no longer need to be ready
/// for iteration or events. This method is invoked when
/// is changed to false.
///
protected override void UnreadyItems()
{
// No longer need to know when new items are added.
_source.ItemsAdded -= OnSourceItemsAdded;
_source.ItemsRemoved -= OnSourceItemsRemoved;
// No longer need to know when transformations change.
int index = 0;
foreach (TSource source in _source)
{
var eventHandler = _eventHandlers[index];
_unselector(source, index, eventHandler);
++index;
}
_items.Clear();
_eventHandlers.Clear();
base.UnreadyItems();
}
///
/// An event handler that is invoked when items are added to
/// . This method ensures that transformations are
/// performed on the new items.
///
/// The sender of the event.
/// The instance
/// containing the event data.
void OnSourceItemsAdded(object sender, ItemsEventArgs e)
{
foreach (var item in e.Items)
{
int index = _source.IndexOf(item);
var eventHandler = CreateOnValueChanged(index);
_selector(item, index, eventHandler);
}
}
///
/// An event handler that is invoked when items are removed from
/// . This method ensures that transformations do
/// not continue on the removed items.
///
/// The sender of the event.
/// The instance
/// containing the event data.
void OnSourceItemsRemoved(object sender, ItemsEventArgs e)
{
foreach (var item in e.Items)
{
int index = _source.IndexOf(item);
var eventHandler = _eventHandlers[index];
_unselector(item, index, eventHandler);
_eventHandlers.Remove(index);
}
}
///
/// An event handler that is invoked when the transformation of an item
/// in is changed.
///
/// The
/// instance containing the event data.
/// The zero based location of the item that was
/// changed.
void OnValueChanged(ValueChangedEventArgs e, int index)
{
// Remove the old value of the item.
_items.Remove(index);
if (ItemsRemovedHandler != null)
ItemsRemovedHandler(this, new ItemsEventArgs(e.OldValue));
// Add the new value of the item into the location in the list that
// corresponds with the location in the Source. Both the source and
// this list must correlate with regard to order since Enumerable.Select
// correlates in this manner and differing would be confusing to those
// using this code.
_items.Add(index, e.NewValue);
if(ItemsAddedHandler != null)
ItemsAddedHandler(this, new ItemsEventArgs(e.NewValue));
}
///
/// Creates and stores an event handler that invokes
///
/// with the provided index.
///
/// The index that is to be passed to
/// .
/// The event handler requested.
EventHandler> CreateOnValueChanged(int index)
{
EventHandler> eventHandler =
(valueChangeSender, valueChangeEventArgs) =>
OnValueChanged(valueChangeEventArgs, index);
_eventHandlers.Add(index, eventHandler);
return eventHandler;
}
///
/// A sequence of values to invoke a transform function on.
///
IEventEnumerable _source;
///
/// A transform function to apply to each source element. The second
/// parameter represents the index of the source element. The third
/// parameter is the callback to invoke to keep the transformed value up
/// to date.
///
readonly Action>> _selector;
///
/// The method that is invoked when updates to the transformed value are
/// no longer needed. The second parameter represents the index of the
/// source element. The third parameter is the callback to invoke to
/// keep the transformed value up to date.
///
readonly Action>> _unselector;
///
/// The instance that contains items selected from
/// mapped to their index in . The items are mapped
/// to this index because each item may be provided at a different time.
///
readonly IDictionary _items;
///
/// The instance that contains the event handlers that react to the
/// providing of values by the instance contained in .
/// Each method is mapped to an index within where
/// the item the method reports on is located.
///
readonly IDictionary>> _eventHandlers =
new Dictionary>>();
}
///
/// Extension and utility methods for the
/// and its dependencies.
///
public static class SelectEventEnumerable
{
///
/// Behaves like the method
///
/// in that it projects each element of a sequence into a new form by
/// incorporating the element's index and maintains that form even if the
/// source changes.
///
/// The type of the elements of source.
/// The type of the value returned by selector.
/// A sequence of values to invoke a transform function on.
/// A transform function to apply to each source
/// element; the second parameter of the function represents the index
/// of the source element.
/// An whose elements are the
/// result of invoking the transform function on each element of source.
public static IEventEnumerable Select(
this IEventEnumerable source, Func selector)
{
return source.Select((value, i) => selector(value));
}
///
/// Behaves like the method
///
/// in that it projects each element of a sequence into a new form by
/// incorporating the element's index and maintains that form even if the
/// source changes.
///
/// The type of the elements of source.
/// The type of the value returned by selector.
/// A sequence of values to invoke a transform function on.
/// A transform function to apply to each source
/// element; the second parameter of the function represents the index
/// of the source element.
/// An whose elements are the
/// result of invoking the transform function on each element of source.
public static IEventEnumerable Select(
this IEventEnumerable source, Func selector)
{
return new SelectEventEnumerable(source,
(value, index, handler) => handler(null, new ValueChangedEventArgs(default(TResult), selector(value, index))),
(value, index, handler) => { /* no-op */ });
}
///
/// Takes the internal values from a collection of
/// and stores them in an and maintains
/// that list so that added or removed are reflected.
///
/// The type of items stored in the
/// and in the returned
/// .
/// The instance that contains the
/// instances whose internals are to be reflected
/// in the return .
/// The that reflects the
/// internal values of the .
public static IEventEnumerable AsValues(this IEventEnumerable> source)
{
return new SelectEventEnumerable, T>(source,
(value, index, handler) => value.Provide += handler,
(value, index, handler) => value.Provide -= handler);
}
}
}