using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Chernobyl.Event;
namespace Chernobyl.Values
{
///
/// An that represents multiple
/// instances. This type will only raise
/// if all contained
/// instances have provided values. Additionally, after all
/// instances have provided values, this type will
/// raise when any of the contained
/// instances raise there .
///
/// The type that is contained with the
/// this type will be composed of.
public class Composite : BasicValue>
{
///
/// Initializes a new instance of the class.
///
/// The instances whose internal value is to be
/// represented as an by this instance.
public Composite(IEnumerable> values)
{
values.ThrowIfNull("values");
_values = values.ToArray();
}
///
/// The value held by this instance. If the value has not yet been
/// provided it will be equal to default(T). Setting this property
/// will make this instance ready.
///
protected override IEnumerable BackingValue
{
get { return _value; }
set
{
_value.Clear();
_value.AddRange(value);
}
}
///
/// The previous value of .
///
protected override IEnumerable PreviousValue
{
get { return _previousValue; }
set
{
_previousValue.Clear();
_previousValue.AddRange(value);
}
}
///
/// Invoked when is changed to true.
/// Implementations of this method should invoke the base class method.
///
protected override void Needed()
{
// Set the capacity so that we don't have to trim the excess after
// we've added the defaults values.
_value.Capacity = _previousValue.Capacity = _valuesProvided.Length =
_eventHandlers.Capacity = _values.Length;
// Now ensure that we get the values and their updates.
if (_values.Length != 0)
{
// Add default values so that we can just set the values when they
// are provided.
T[] defaults = new T[_values.Length];
_value.AddRange(defaults);
_previousValue.AddRange(defaults);
_eventHandlers.AddRange(new EventHandler>[_values.Length]);
for (int i = 0; i < _values.Length; i++)
{
int index = i; // Variable to prevent modified closure issue.
// Create, store, and assign the event handler.
EventHandler> eventHandler =
(sender, e) => OnValueProvide(e, index);
_eventHandlers[index] = eventHandler;
_values[i].Provide += eventHandler;
}
}
else
IsReady = true;
base.Needed();
}
///
/// Invoked when is changed to false.
/// Implementations of this method should invoke the base class method.
///
protected override void NotNeeded()
{
for (int i = 0; i < _values.Length; i++)
_values[i].Provide -= _eventHandlers[i];
_previousValue.Clear();
_value.Clear();
_eventHandlers.Clear();
_valuesProvided.SetAll(false);
_providedCount = 0;
IsReady = false;
base.NotNeeded();
}
///
/// Invoked when one of the contained by this
/// instance has it value changed or made ready.
///
/// The
/// instance containing the event data.
/// The index of the item that was changed.
void OnValueProvide(ValueChangedEventArgs e, int index)
{
_previousValue[index] = e.OldValue;
_value[index] = e.NewValue;
// Check if this Composite can be made ready. If we are ready then
// we can notify client code of the change.
bool raiseProvide = IsReady;
if (_valuesProvided[index] == false)
{
_valuesProvided[index] = true;
_providedCount++;
// If all of the values have been provided, then we are ready.
if (_providedCount == _values.Length)
{
// Don't invoke Provide if we change IsReady to true since
// IsReady will invoke Provide for us.
if (IsReady == false)
IsReady = true;
else
raiseProvide = true;
}
}
if (raiseProvide && ProvideHandler != null)
ProvideHandler(this, new ValueChangedEventArgs>(
PreviousValue, Value));
}
///
/// The instances whose internal value is to be represented as an
/// by this instance.
///
readonly IValue[] _values;
///
/// Specifies which values have been provided where 0 is not ready, 1 is
/// ready.
///
readonly BitArray _valuesProvided = new BitArray(0);
///
/// The number of instances in that have provided
/// a first value.
///
uint _providedCount;
///
/// The instance that contains the event handlers that react to the
/// providing of values by the instance contained in .
///
readonly List>> _eventHandlers =
new List>>();
///
/// The backing field to .
///
readonly List _value = new List();
///
/// The backing field to .
///
readonly List _previousValue = new List();
}
///
/// Extension and utility methods for .
///
public static class CompositeExt
{
///
/// Returns an that represents multiple
/// instances. The instance will only raise if all contained
/// instances have provided values. Additionally, after all
/// instances have provided values, this type will raise
/// when any of the contained
/// instances raises its event.
///
public static IValue> Composite(this IEnumerable> values) =>
new Composite(values);
}
}