using System;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using FSI.Lib.Tools.RoboSharp.Interfaces;
namespace FSI.Lib.Tools.RoboSharp.Results
{
///
/// Contains information regarding average Transfer Speed.
/// Note: Runs that do not perform any copy operations or that exited prematurely ( ) will result in a null object.
///
///
///
///
public class SpeedStatistic : INotifyPropertyChanged, ISpeedStatistic
{
///
/// Create new SpeedStatistic
///
public SpeedStatistic() { }
///
/// Clone a SpeedStatistic
///
public SpeedStatistic(SpeedStatistic stat)
{
BytesPerSec = stat.BytesPerSec;
MegaBytesPerMin = stat.MegaBytesPerMin;
}
#region < Private & Protected Members >
private decimal BytesPerSecField = 0;
private decimal MegaBytesPerMinField = 0;
/// This toggle Enables/Disables firing the Event to avoid firing it when doing multiple consecutive changes to the values
protected bool EnablePropertyChangeEvent { get; set; } = true;
#endregion
#region < Public Properties & Events >
/// This event will fire when the value of the SpeedStatistic is updated
public event PropertyChangedEventHandler PropertyChanged;
/// Raise Property Change Event
protected void OnPropertyChange(string PropertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(PropertyName));
///
public virtual decimal BytesPerSec
{
get => BytesPerSecField;
protected set
{
if (BytesPerSecField != value)
{
BytesPerSecField = value;
if (EnablePropertyChangeEvent) OnPropertyChange("MegaBytesPerMin");
}
}
}
///
public virtual decimal MegaBytesPerMin
{
get => MegaBytesPerMinField;
protected set
{
if (MegaBytesPerMinField != value)
{
MegaBytesPerMinField = value;
if (EnablePropertyChangeEvent) OnPropertyChange("MegaBytesPerMin");
}
}
}
#endregion
#region < Methods >
///
/// Returns a string that represents the current object.
///
public override string ToString()
{
return $"Speed: {BytesPerSec} Bytes/sec{Environment.NewLine}Speed: {MegaBytesPerMin} MegaBytes/min";
}
///
public virtual SpeedStatistic Clone() => new SpeedStatistic(this);
object ICloneable.Clone() => Clone();
internal static SpeedStatistic Parse(string line1, string line2)
{
var res = new SpeedStatistic();
var pattern = new Regex(@"\d+([\.,]\d+)?");
Match match;
match = pattern.Match(line1);
if (match.Success)
{
res.BytesPerSec = Convert.ToDecimal(match.Value.Replace(',', '.'), CultureInfo.InvariantCulture);
}
match = pattern.Match(line2);
if (match.Success)
{
res.MegaBytesPerMin = Convert.ToDecimal(match.Value.Replace(',', '.'), CultureInfo.InvariantCulture);
}
return res;
}
#endregion
}
///
/// This object represents the Average of several objects, and contains
/// methods to facilitate that functionality.
///
public sealed class AverageSpeedStatistic : SpeedStatistic
{
#region < Constructors >
///
/// Initialize a new object with the default values.
///
public AverageSpeedStatistic() : base() { }
///
/// Initialize a new object.
/// Values will be set to the return values of and
///
///
/// Either a or a object.
/// If a is passed into this constructor, it wil be treated as the base instead.
///
public AverageSpeedStatistic(ISpeedStatistic speedStat) : base()
{
Divisor = 1;
Combined_BytesPerSec = speedStat.BytesPerSec;
Combined_MegaBytesPerMin = speedStat.MegaBytesPerMin;
CalculateAverage();
}
///
/// Initialize a new object using .
///
///
///
public AverageSpeedStatistic(IEnumerable speedStats) : base()
{
Average(speedStats);
}
///
/// Clone an AverageSpeedStatistic
///
public AverageSpeedStatistic(AverageSpeedStatistic stat) : base(stat)
{
Divisor = stat.Divisor;
Combined_BytesPerSec = stat.BytesPerSec;
Combined_MegaBytesPerMin = stat.MegaBytesPerMin;
}
#endregion
#region < Fields >
/// Sum of all
private decimal Combined_BytesPerSec = 0;
/// Sum of all
private decimal Combined_MegaBytesPerMin = 0;
/// Total number of SpeedStats that were combined to produce the Combined_* values
private long Divisor = 0;
#endregion
#region < Public Methods >
///
public override SpeedStatistic Clone() => new AverageSpeedStatistic(this);
#endregion
#region < Reset Value Methods >
///
/// Set the values for this object to 0
///
#if !NET40
[MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)]
#endif
public void Reset()
{
Combined_BytesPerSec = 0;
Combined_MegaBytesPerMin = 0;
Divisor = 0;
BytesPerSec = 0;
MegaBytesPerMin = 0;
}
///
/// Set the values for this object to 0
///
#if !NET40
[MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)]
#endif
internal void Reset(bool enablePropertyChangeEvent)
{
EnablePropertyChangeEvent = enablePropertyChangeEvent;
Reset();
EnablePropertyChangeEvent = true;
}
#endregion
// Add / Subtract methods are internal to allow usage within the RoboCopyResultsList object.
// The 'Average' Methods will first Add the statistics to the current one, then recalculate the average.
// Subtraction is only used when an item is removed from a RoboCopyResultsList
// As such, public consumers should typically not require the use of subtract methods
#region < ADD ( internal ) >
///
/// Add the results of the supplied SpeedStatistic objects to this object.
/// Does not automatically recalculate the average, and triggers no events.
///
///
/// If any supplied Speedstat object is actually an object, default functionality will combine the private fields
/// used to calculate the average speed instead of using the publicly reported speeds.
/// This ensures that combining the average of multiple objects returns the correct value.
/// Ex: One object with 2 runs and one with 3 runs will return the average of all 5 runs instead of the average of two averages.
///
/// SpeedStatistic Item to add
///
/// Setting this to TRUE will instead combine the calculated average of the , treating it as a single object.
/// Ignore the private fields, and instead use the calculated speeds)
///
#if !NET40
[MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)]
#endif
internal void Add(ISpeedStatistic stat, bool ForceTreatAsSpeedStat = false)
{
if (stat == null) return;
bool IsAverageStat = !ForceTreatAsSpeedStat && stat.GetType() == typeof(AverageSpeedStatistic);
AverageSpeedStatistic AvgStat = IsAverageStat ? (AverageSpeedStatistic)stat : null;
Divisor += IsAverageStat ? AvgStat.Divisor : 1;
Combined_BytesPerSec += IsAverageStat ? AvgStat.Combined_BytesPerSec : stat.BytesPerSec;
Combined_MegaBytesPerMin += IsAverageStat ? AvgStat.Combined_MegaBytesPerMin : stat.MegaBytesPerMin;
}
///
/// Add the supplied SpeedStatistic collection to this object.
///
/// SpeedStatistic collection to add
///
///
#if !NET40
[MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)]
#endif
internal void Add(IEnumerable stats, bool ForceTreatAsSpeedStat = false)
{
foreach (SpeedStatistic stat in stats)
Add(stat, ForceTreatAsSpeedStat);
}
#endregion
#region < Subtract ( internal ) >
///
/// Subtract the results of the supplied SpeedStatistic objects from this object.
///
/// Statistics Item to add
///
#if !NET40
[MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)]
#endif
internal void Subtract(SpeedStatistic stat, bool ForceTreatAsSpeedStat = false)
{
if (stat == null) return;
bool IsAverageStat = !ForceTreatAsSpeedStat && stat.GetType() == typeof(AverageSpeedStatistic);
AverageSpeedStatistic AvgStat = IsAverageStat ? (AverageSpeedStatistic)stat : null;
Divisor -= IsAverageStat ? AvgStat.Divisor : 1;
//Combine the values if Divisor is still valid
if (Divisor >= 1)
{
Combined_BytesPerSec -= IsAverageStat ? AvgStat.Combined_BytesPerSec : stat.BytesPerSec;
Combined_MegaBytesPerMin -= IsAverageStat ? AvgStat.Combined_MegaBytesPerMin : stat.MegaBytesPerMin;
}
//Cannot have negative speeds or divisors -> Reset all values
if (Divisor < 1 || Combined_BytesPerSec < 0 || Combined_MegaBytesPerMin < 0)
{
Combined_BytesPerSec = 0;
Combined_MegaBytesPerMin = 0;
Divisor = 0;
}
}
///
/// Subtract the supplied SpeedStatistic collection from this object.
///
/// SpeedStatistic collection to subtract
///
#if !NET40
[MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)]
#endif
internal void Subtract(IEnumerable stats, bool ForceTreatAsSpeedStat = false)
{
foreach (SpeedStatistic stat in stats)
Subtract(stat, ForceTreatAsSpeedStat);
}
#endregion
#region < AVERAGE ( public ) >
///
/// Immediately recalculate the BytesPerSec and MegaBytesPerMin values
///
#if !NET40
[MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)]
#endif
internal void CalculateAverage()
{
EnablePropertyChangeEvent = false;
//BytesPerSec
var i = Divisor < 1 ? 0 : Math.Round(Combined_BytesPerSec / Divisor, 3);
bool TriggerBPS = BytesPerSec != i;
BytesPerSec = i;
//MegaBytes
i = Divisor < 1 ? 0 : Math.Round(Combined_MegaBytesPerMin / Divisor, 3);
bool TriggerMBPM = MegaBytesPerMin != i;
MegaBytesPerMin = i;
//Trigger Events
EnablePropertyChangeEvent = true;
if (TriggerBPS) OnPropertyChange("BytesPerSec");
if (TriggerMBPM) OnPropertyChange("MegaBytesPerMin");
}
///
/// Combine the supplied objects, then get the average.
///
/// Stats object
///
public void Average(ISpeedStatistic stat)
{
Add(stat);
CalculateAverage();
}
///
/// Combine the supplied objects, then get the average.
///
/// Collection of objects
///
public void Average(IEnumerable stats)
{
Add(stats);
CalculateAverage();
}
/// New Statistics Object
///
public static AverageSpeedStatistic GetAverage(IEnumerable stats)
{
AverageSpeedStatistic stat = new AverageSpeedStatistic();
stat.Average(stats);
return stat;
}
#endregion
}
}