using System;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using RoboSharp.Interfaces;
using RoboSharp.EventArgObjects;

namespace RoboSharp.Results
{
    /// <summary>
    /// Information about number of items Copied, Skipped, Failed, etc.
    /// <para/>
    /// <see cref="RoboCopyResults"/> will not typically raise any events, but this object is used for other items, such as <see cref="ProgressEstimator"/> and <see cref="RoboCopyResultsList"/> to present results whose values may update periodically.
    /// </summary>
    /// <remarks>
    /// <see href="https://github.com/tjscience/RoboSharp/wiki/Statistic"/>
    /// </remarks>
    public class Statistic : IStatistic
    {
        internal static IStatistic Default_Bytes = new Statistic(type: StatType.Bytes);
        internal static IStatistic Default_Files = new Statistic(type: StatType.Files);
        internal static IStatistic Default_Dirs = new Statistic(type: StatType.Directories);

        #region < Constructors >

        /// <summary> Create a new Statistic object of <see cref="StatType"/> </summary>
        [Obsolete("Statistic Types require Initialization with a StatType")]
        private Statistic() { }

        /// <summary> Create a new Statistic object </summary>
        public Statistic(StatType type) { Type = type; }

        /// <summary> Create a new Statistic object </summary>
        public Statistic(StatType type, string name) { Type = type; Name = name; }

        /// <summary> Clone an existing Statistic object</summary>
        public Statistic(Statistic stat)
        {
            Type = stat.Type;
            NameField = stat.Name;
            TotalField = stat.Total;
            CopiedField = stat.Copied;
            SkippedField = stat.Skipped;
            MismatchField = stat.Mismatch;
            FailedField = stat.Failed;
            ExtrasField = stat.Extras;
        }

        /// <summary> Clone an existing Statistic object</summary>
        internal  Statistic(StatType type, string name, long total, long copied, long skipped, long mismatch, long failed, long extras)
        {
            Type = type;
            NameField = name;
            TotalField = total;
            CopiedField = copied;
            SkippedField = skipped;
            MismatchField = mismatch;
            FailedField = failed;
            ExtrasField = extras;
        }

        /// <summary> Describe the Type of Statistics Object </summary>
        public enum StatType
        {
            /// <summary> Statistics object represents count of Directories </summary>
            Directories,
            /// <summary> Statistics object represents count of Files </summary>
            Files,
            /// <summary> Statistics object represents a Size ( number of bytes )</summary>
            Bytes
        }

        #endregion 

        #region < Fields >

        private string NameField = "";
        private long TotalField = 0;
        private long CopiedField = 0;
        private long SkippedField = 0;
        private long MismatchField = 0;
        private long FailedField = 0;
        private long ExtrasField = 0;

        #endregion

        #region < Events >

        /// <summary> This toggle Enables/Disables firing the <see cref="PropertyChanged"/> Event to avoid firing it when doing multiple consecutive changes to the values </summary>
        internal bool EnablePropertyChangeEvent = true;
        private bool PropertyChangedListener => EnablePropertyChangeEvent && PropertyChanged != null;
        private bool Listener_TotalChanged => EnablePropertyChangeEvent && OnTotalChanged != null;
        private bool Listener_CopiedChanged => EnablePropertyChangeEvent && OnCopiedChanged != null;
        private bool Listener_SkippedChanged => EnablePropertyChangeEvent && OnSkippedChanged != null;
        private bool Listener_MismatchChanged => EnablePropertyChangeEvent && OnMisMatchChanged != null;
        private bool Listener_FailedChanged => EnablePropertyChangeEvent && OnFailedChanged != null;
        private bool Listener_ExtrasChanged => EnablePropertyChangeEvent && OnExtrasChanged != null;

        /// <summary>
        /// This event will fire when the value of the statistic is updated via Adding / Subtracting methods. <br/>
        /// Provides <see cref="StatisticPropertyChangedEventArgs"/> object.
        /// </summary>
        /// <remarks>
        /// Allows use with both binding to controls and <see cref="ProgressEstimator"/> binding. <br/>
        /// EventArgs can be passed into <see cref="AddStatistic(PropertyChangedEventArgs)"/> after casting.
        /// </remarks>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>Handles any value changes </summary>
        public delegate void StatChangedHandler(Statistic sender, StatChangedEventArg e);

        /// <summary> Occurs when the <see cref="Total"/> Property is updated. </summary>
        public event StatChangedHandler OnTotalChanged;

        /// <summary> Occurs when the <see cref="Copied"/> Property is updated. </summary>
        public event StatChangedHandler OnCopiedChanged;

        /// <summary> Occurs when the <see cref="Skipped"/> Property is updated. </summary>
        public event StatChangedHandler OnSkippedChanged;

        /// <summary> Occurs when the <see cref="Mismatch"/> Property is updated. </summary>
        public event StatChangedHandler OnMisMatchChanged;

        /// <summary> Occurs when the <see cref="Failed"/> Property is updated. </summary>
        public event StatChangedHandler OnFailedChanged;

        /// <summary> Occurs when the <see cref="Extras"/> Property is updated. </summary>
        public event StatChangedHandler OnExtrasChanged;

        #endregion

        #region < Properties >

        /// <summary>
        /// Checks all values and determines if any of them are != 0.
        /// </summary>
        public bool NonZeroValue => TotalField != 0 || CopiedField != 0 || SkippedField != 0 || MismatchField != 0 || FailedField != 0 || ExtrasField != 0;

        /// <summary>
        /// Name of the Statistics Object
        /// </summary>
        public string Name
        {
            get => NameField;
            set
            {
                if (value != NameField)
                {
                    NameField = value ?? "";
                    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Name"));
                }
            }
        }
        

        /// <summary>
        /// <inheritdoc cref="StatType"/>
        /// </summary>
        public StatType Type { get; }

        /// <inheritdoc cref="IStatistic.Total"/>
        public long Total { 
            get => TotalField;
            internal set
            {
                if (TotalField != value)
                {
                    var e = PrepEventArgs(TotalField, value, "Total");
                    TotalField = value;
                    if (e != null)
                    {
                        PropertyChanged?.Invoke(this, e.Value.Item2);
                        OnTotalChanged?.Invoke(this, e.Value.Item1);
                    }
                }
            }
        }

        /// <inheritdoc cref="IStatistic.Copied"/>
        public long Copied { 
            get => CopiedField;
            internal set
            {
                if (CopiedField != value)
                {
                    var e = PrepEventArgs(CopiedField, value, "Copied");
                    CopiedField = value;
                    if (e != null)
                    {
                        PropertyChanged?.Invoke(this, e.Value.Item2);
                        OnCopiedChanged?.Invoke(this, e.Value.Item1);
                    }
                }
            }
        }

        /// <inheritdoc cref="IStatistic.Skipped"/>
        public long Skipped { 
            get => SkippedField;
            internal set
            {
                if (SkippedField != value)
                {
                    var e = PrepEventArgs(SkippedField, value, "Skipped");
                    SkippedField = value;
                    if (e != null)
                    {
                        PropertyChanged?.Invoke(this, e.Value.Item2);
                        OnSkippedChanged?.Invoke(this, e.Value.Item1);
                    }
                }
            }
        }

        /// <inheritdoc cref="IStatistic.Mismatch"/>
        public long Mismatch { 
            get => MismatchField;
            internal set
            {
                if (MismatchField != value)
                {
                    var e = PrepEventArgs(MismatchField, value, "Mismatch");
                    MismatchField = value;
                    if (e != null)
                    {
                        PropertyChanged?.Invoke(this, e.Value.Item2);
                        OnMisMatchChanged?.Invoke(this, e.Value.Item1);
                    }
                }
            }
        }

        /// <inheritdoc cref="IStatistic.Failed"/>
        public long Failed { 
            get => FailedField;
            internal set
            {
                if (FailedField != value)
                {
                    var e = PrepEventArgs(FailedField, value, "Failed");
                    FailedField = value;
                    if (e != null)
                    {
                        PropertyChanged?.Invoke(this, e.Value.Item2);
                        OnFailedChanged?.Invoke(this, e.Value.Item1);
                    }
                }
            }
        }

        /// <inheritdoc cref="IStatistic.Extras"/>
        public long Extras { 
            get => ExtrasField;
            internal set
            {
                if (ExtrasField != value)
                {
                    var e = PrepEventArgs(ExtrasField, value, "Extras");
                    ExtrasField = value;
                    if (e != null)
                    {
                        PropertyChanged?.Invoke(this, e.Value.Item2);
                        OnExtrasChanged?.Invoke(this, e.Value.Item1);
                    }
                }
            }
        }

        #endregion

        #region < ToString Methods >

        /// <summary>
        /// Returns a string that represents the current object.
        /// </summary>
        public override string ToString() => ToString(false, true, ", ");

        /// <summary>
        /// Customize the returned string
        /// </summary>
        /// <param name="IncludeType">Include string representation of <see cref="Type"/></param>
        /// <param name="IncludePrefix">Include "Total:" / "Copied:" / etc in the string to identify the values</param>
        /// <param name="Delimiter">Value Delimieter</param>
        /// <param name="DelimiterAfterType">
        /// Include the delimiter after the 'Type' - Only used if <paramref name="IncludeType"/> us also true. <br/>
        /// When <paramref name="IncludeType"/> is true, a space always exist after the type string. This would add delimiter instead of the space. 
        /// </param>
        /// <returns>
        /// TRUE, TRUE, "," --> $"{Type} Total: {Total}, Copied: {Copied}, Skipped: {Skipped}, Mismatch: {Mismatch}, Failed: {Failed}, Extras: {Extras}" <para/>
        /// FALSE, TRUE, "," --> $"Total: {Total}, Copied: {Copied}, Skipped: {Skipped}, Mismatch: {Mismatch}, Failed: {Failed}, Extras: {Extras}" <para/>
        /// FALSE, FALSE, "," --> $"{Total}, {Copied}, {Skipped}, {Mismatch}, {Failed}, {Extras}"
        /// </returns>
        public string ToString(bool IncludeType, bool IncludePrefix, string Delimiter, bool DelimiterAfterType = false)
        {
            return $"{ToString_Type(IncludeType, DelimiterAfterType)}" + $"{(IncludeType && DelimiterAfterType ? Delimiter : "")}" +
                $"{ToString_Total(false, IncludePrefix)}{Delimiter}" +
                $"{ToString_Copied(false, IncludePrefix)}{Delimiter}" +
                $"{ToString_Skipped(false, IncludePrefix)}{Delimiter}" +
                $"{ToString_Mismatch(false, IncludePrefix)}{Delimiter}" +
                $"{ToString_Failed(false, IncludePrefix)}{Delimiter}" +
                $"{ToString_Extras(false, IncludePrefix)}";
        }

        /// <summary> Get the <see cref="Type"/> as a string </summary>
        public string ToString_Type() => ToString_Type(true).Trim();
        private string ToString_Type(bool IncludeType, bool Trim = false) => IncludeType ? $"{Type}{(Trim? "" : " ")}" : "";

        /// <summary>Get the string describing the <see cref="Total"/></summary>
        /// <returns></returns>
        /// <inheritdoc cref="ToString(bool, bool, string, bool)"/>
        public string ToString_Total(bool IncludeType = false, bool IncludePrefix = true) => $"{ToString_Type(IncludeType)}{(IncludePrefix? "Total: " : "")}{Total}";

        /// <summary>Get the string describing the <see cref="Copied"/></summary>
        /// <inheritdoc cref="ToString_Total"/>
        public string ToString_Copied(bool IncludeType = false, bool IncludePrefix = true) => $"{ToString_Type(IncludeType)}{(IncludePrefix ? "Copied: " : "")}{Copied}";

        /// <summary>Get the string describing the <see cref="Extras"/></summary>
        /// <inheritdoc cref="ToString_Total"/>
        public string ToString_Extras(bool IncludeType = false, bool IncludePrefix = true) => $"{ToString_Type(IncludeType)}{(IncludePrefix ? "Extras: " : "")}{Extras}";

        /// <summary>Get the string describing the <see cref="Failed"/></summary>
        /// <inheritdoc cref="ToString_Total"/>
        public string ToString_Failed(bool IncludeType = false, bool IncludePrefix = true) => $"{ToString_Type(IncludeType)}{(IncludePrefix ? "Failed: " : "")}{Failed}";

        /// <summary>Get the string describing the <see cref="Mismatch"/></summary>
        /// <inheritdoc cref="ToString_Total"/>
        public string ToString_Mismatch(bool IncludeType = false, bool IncludePrefix = true) => $"{ToString_Type(IncludeType)}{(IncludePrefix ? "Mismatch: " : "")}{Mismatch}";

        /// <summary>Get the string describing the <see cref="Skipped"/></summary>
        /// <inheritdoc cref="ToString_Total"/>
        public string ToString_Skipped(bool IncludeType = false, bool IncludePrefix = true) => $"{ToString_Type(IncludeType)}{(IncludePrefix ? "Skipped: " : "")}{Skipped}";

        #endregion

        #region < Parsing Methods >

        /// <summary>
        /// Parse a string and for the tokens reported by RoboCopy
        /// </summary>
        /// <param name="type">Statistic Type to produce</param>
        /// <param name="line">LogLine produced by RoboCopy in Summary Section</param>
        /// <returns>New Statistic Object</returns>
        public static Statistic Parse(StatType type, string line)
        {
            var res = new Statistic(type);

            var tokenNames = new[] { nameof(Total), nameof(Copied), nameof(Skipped), nameof(Mismatch), nameof(Failed), nameof(Extras) };
            var patternBuilder = new StringBuilder(@"^.*:");

            foreach (var tokenName in tokenNames)
            {
                var tokenPattern = GetTokenPattern(tokenName);
                patternBuilder.Append(@"\s+").Append(tokenPattern);
            }

            var pattern = patternBuilder.ToString();
            var match = Regex.Match(line, pattern);
            if (!match.Success)
                return res;

            var props = res.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);
            foreach (var tokenName in tokenNames)
            {
                var prop = props.FirstOrDefault(x => x.Name == tokenName);
                if (prop == null)
                    continue;

                var tokenString = match.Groups[tokenName].Value;
                var tokenValue = ParseTokenString(tokenString);
                prop.SetValue(res, tokenValue, null);
            }

            return res;
        }

        private static string GetTokenPattern(string tokenName)
        {
            return $@"(?<{tokenName}>[\d\.]+(\s\w)?)";
        }

        private static long ParseTokenString(string tokenString)
        {
            if (string.IsNullOrWhiteSpace(tokenString))
                return 0;

            tokenString = tokenString.Trim();
            if (Regex.IsMatch(tokenString, @"^\d+$", RegexOptions.Compiled))
                return long.Parse(tokenString);

            var match = Regex.Match(tokenString, @"(?<Mains>[\d\.,]+)(\.(?<Fraction>\d+))\s(?<Unit>\w)", RegexOptions.Compiled);
            if (match.Success)
            {
                var mains = match.Groups["Mains"].Value.Replace(".", "").Replace(",", "");
                var fraction = match.Groups["Fraction"].Value;
                var unit = match.Groups["Unit"].Value.ToLower();

                var number = double.Parse($"{mains}.{fraction}", NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture);
                switch (unit)
                {
                    case "k":
                        // Byte = kBytes * 1024
                        number *= Math.Pow(1024, 1);
                        break;
                    case "m":
                        // Byte = MBytes * 1024 * 1024
                        number *= Math.Pow(1024, 2);
                        break;
                    case "g":
                        // Byte = GBytes * 1024 * 1024 * 1024
                        number *= Math.Pow(1024, 3);
                        break;
                    case "t":
                        // Byte = TBytes * 1024 * 1024 * 1024 * 1024
                        number *= Math.Pow(1024, 4);
                        break;
                }

                return Convert.ToInt64(number);
            }

            return 0;
        }

        #endregion

        #region < Reset Method >

        /// <summary>
        /// Set the values for this object to 0
        /// </summary>
        public void Reset(bool enablePropertyChangeEvent)
        {
            EnablePropertyChangeEvent = enablePropertyChangeEvent;
            Reset();
            EnablePropertyChangeEvent = true;
        }


        /// <summary>
        /// Reset all values to Zero ( 0 ) -- Used by <see cref="RoboCopyResultsList"/> for the properties
        /// </summary>
        [MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)]
        public void Reset()
        {
            Statistic OriginalValues = PropertyChangedListener && NonZeroValue ? this.Clone() : null;

            //Total
            var eTotal = ConditionalPrepEventArgs(Listener_TotalChanged, TotalField, 0, "Total");
            TotalField = 0;
            //Copied
            var eCopied = ConditionalPrepEventArgs(Listener_CopiedChanged, CopiedField, 0, "Copied");
            CopiedField = 0;
            //Extras
            var eExtras = ConditionalPrepEventArgs(Listener_ExtrasChanged, ExtrasField, 0, "Extras");
            ExtrasField = 0;
            //Failed
            var eFailed = ConditionalPrepEventArgs(Listener_FailedChanged, FailedField, 0, "Failed");
            FailedField = 0;
            //Mismatch
            var eMismatch = ConditionalPrepEventArgs(Listener_MismatchChanged, MismatchField, 0, "Mismatch");
            MismatchField = 0;
            //Skipped
            var eSkipped = ConditionalPrepEventArgs(Listener_SkippedChanged, SkippedField, 0, "Skipped");
            SkippedField = 0;

            //Trigger Events 
            TriggerDeferredEvents(OriginalValues, eTotal, eCopied, eExtras, eFailed, eMismatch, eSkipped);
        }


        #endregion

        #region < Trigger Deferred Events >

        /// <summary>
        /// Prep Event Args for SETTERS of the properties
        /// </summary>
        [MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)]
        private Lazy<Tuple<StatChangedEventArg, StatisticPropertyChangedEventArgs>> PrepEventArgs(long OldValue, long NewValue, string PropertyName)
        {
            if (!EnablePropertyChangeEvent) return null;
            var old = this.Clone();
            return new Lazy<Tuple<StatChangedEventArg, StatisticPropertyChangedEventArgs>>(() =>
            {
                StatChangedEventArg e1 = new StatChangedEventArg(this, OldValue, NewValue, PropertyName);
                var e2 = new StatisticPropertyChangedEventArgs(this, old, PropertyName);
                return new Tuple<StatChangedEventArg, StatisticPropertyChangedEventArgs>(e1, e2);
            });
        }

        /// <summary>
        /// Prep event args for the ADD and RESET methods
        /// </summary>
        [MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)]
        private StatChangedEventArg ConditionalPrepEventArgs(bool Listener, long OldValue, long NewValue, string PropertyName)=> !Listener || OldValue == NewValue ? null : new StatChangedEventArg(this, OldValue, NewValue, PropertyName);

        /// <summary>
        /// Raises the events that were deferred while item was object was still being calculated by ADD / RESET
        /// </summary>
        [MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)]
        private void TriggerDeferredEvents(Statistic OriginalValues, StatChangedEventArg eTotal, StatChangedEventArg eCopied, StatChangedEventArg eExtras, StatChangedEventArg eFailed, StatChangedEventArg eMismatch, StatChangedEventArg eSkipped)
        {
            //Perform Events
            int i = 0;
            if (eTotal != null)
            {
                i = 1;
                OnTotalChanged?.Invoke(this, eTotal);
            }
            if (eCopied != null)
            {
                i += 2;
                OnCopiedChanged?.Invoke(this, eCopied);
            }
            if (eExtras != null)
            {
                i += 4;
                OnExtrasChanged?.Invoke(this, eExtras);
            }
            if (eFailed != null)
            {
                i += 8;
                OnFailedChanged?.Invoke(this, eFailed);
            }
            if (eMismatch != null)
            {
                i += 16;
                OnMisMatchChanged?.Invoke(this, eMismatch);
            }
            if (eSkipped != null)
            {
                i += 32;
                OnSkippedChanged?.Invoke(this, eSkipped);
            }

            //Trigger PropertyChangeEvent
            if (OriginalValues != null)
            {
                switch (i)
                {
                    case 1: PropertyChanged?.Invoke(this, new StatisticPropertyChangedEventArgs(this, OriginalValues, eTotal.PropertyName)); return;
                    case 2: PropertyChanged?.Invoke(this, new StatisticPropertyChangedEventArgs(this, OriginalValues, eCopied.PropertyName)); return;
                    case 4: PropertyChanged?.Invoke(this, new StatisticPropertyChangedEventArgs(this, OriginalValues, eExtras.PropertyName)); return;
                    case 8: PropertyChanged?.Invoke(this, new StatisticPropertyChangedEventArgs(this, OriginalValues, eFailed.PropertyName)); return;
                    case 16: PropertyChanged?.Invoke(this, new StatisticPropertyChangedEventArgs(this, OriginalValues, eMismatch.PropertyName)); return;
                    case 32: PropertyChanged?.Invoke(this, new StatisticPropertyChangedEventArgs(this, OriginalValues, eSkipped.PropertyName)); return;
                    default: PropertyChanged?.Invoke(this, new StatisticPropertyChangedEventArgs(this, OriginalValues, String.Empty)); return;
                }
            }
        }

        #endregion

        #region < ADD Methods >

        /// <summary>
        /// Add the supplied values to this Statistic object. <br/>
        /// Events are defered until all the fields have been added together.
        /// </summary>
        /// <param name="total"></param>
        /// <param name="copied"></param>
        /// <param name="extras"></param>
        /// <param name="failed"></param>
        /// <param name="mismatch"></param>
        /// <param name="skipped"></param>
        [MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)]
        public void Add(long total = 0, long copied = 0, long extras = 0, long failed = 0, long mismatch = 0, long skipped = 0)
        {
            //Store the original object values for the event args
            Statistic originalValues = PropertyChangedListener ? this.Clone() : null;

            //Total
            long i = TotalField;
            TotalField += total;
            var eTotal = ConditionalPrepEventArgs(Listener_TotalChanged, i, TotalField, "Total");

            //Copied
            i = CopiedField;
            CopiedField += copied;
            var eCopied = ConditionalPrepEventArgs(Listener_CopiedChanged, i, CopiedField, "Copied");

            //Extras
            i = ExtrasField;
            ExtrasField += extras;
            var eExtras = ConditionalPrepEventArgs(Listener_ExtrasChanged, i, ExtrasField, "Extras");

            //Failed
            i = FailedField;
            FailedField += failed;
            var eFailed = ConditionalPrepEventArgs(Listener_FailedChanged, i, FailedField, "Failed");

            //Mismatch
            i = MismatchField;
            MismatchField += mismatch;
            var eMismatch = ConditionalPrepEventArgs(Listener_MismatchChanged, i, MismatchField, "Mismatch");

            //Skipped
            i = SkippedField;
            SkippedField += skipped;
            var eSkipped = ConditionalPrepEventArgs(Listener_SkippedChanged, i, SkippedField, "Skipped");

            //Trigger Events 
            if (EnablePropertyChangeEvent)
                TriggerDeferredEvents(originalValues, eTotal, eCopied, eExtras, eFailed, eMismatch, eSkipped);
        }

        /// <summary>
        /// Add the results of the supplied Statistics object to this Statistics object. <br/>
        /// Events are defered until all the fields have been added together.
        /// </summary>
        /// <param name="stat">Statistics Item to add</param>
        [MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)]
        public void AddStatistic(IStatistic stat)
        {
            if (stat != null && stat.Type == this.Type && stat.NonZeroValue) 
                Add(stat.Total, stat.Copied, stat.Extras, stat.Failed, stat.Mismatch, stat.Skipped);
        }

        
        #pragma warning disable CS1573 // Parameter has no matching param tag in the XML comment (but other parameters do)
        /// <param name="enablePropertyChangedEvent"><inheritdoc cref="EnablePropertyChangeEvent" path="*"/></param>
        /// <inheritdoc cref="AddStatistic(IStatistic)"/>        
        internal void AddStatistic(IStatistic stats, bool enablePropertyChangedEvent)
        {
            EnablePropertyChangeEvent = enablePropertyChangedEvent;
            AddStatistic(stats);
            EnablePropertyChangeEvent = true;

        }
        #pragma warning restore CS1573


        /// <summary>
        /// Add the results of the supplied Statistics objects to this Statistics object.
        /// </summary>
        /// <param name="stats">Statistics Item to add</param>
        public void AddStatistic(IEnumerable<IStatistic> stats)
        {
            foreach (Statistic stat in stats)
            {
                EnablePropertyChangeEvent = stat == stats.Last();
                AddStatistic(stat);
            }
        }

        /// <summary>
        /// Adds <see cref="StatChangedEventArg.Difference"/> to the appropriate property based on the 'PropertyChanged' value. <br/>
        /// Will only add the value if the <see cref="StatChangedEventArg.StatType"/> == <see cref="Type"/>.
        /// </summary>
        /// <param name="eventArgs">Arg provided by either <see cref="PropertyChanged"/> or a Statistic's object's On*Changed events</param>
        public void AddStatistic(PropertyChangedEventArgs eventArgs)
        {
            //Only process the args if of the proper type
            var e = eventArgs as IStatisticPropertyChangedEventArg;
            if (e == null) return;
            
            if (e.StatType != this.Type || (e.Is_StatisticPropertyChangedEventArgs && e.Is_StatChangedEventArg))
            {
                // INVALID!
            }
            else if (e.Is_StatisticPropertyChangedEventArgs)
            {
                var e1 = (StatisticPropertyChangedEventArgs)eventArgs;
                AddStatistic(e1.Difference);
            }
            else if (e.Is_StatChangedEventArg)
            {
                var e2 = (StatChangedEventArg)eventArgs;
                switch (e.PropertyName)
                {
                    case "": //String.Empty means all fields have changed
                        AddStatistic(e2.Sender);
                        break;
                    case "Copied":
                        this.Copied += e2.Difference;
                        break;
                    case "Extras":
                        this.Extras += e2.Difference;
                        break;
                    case "Failed":
                        this.Failed += e2.Difference;
                        break;
                    case "Mismatch":
                        this.Mismatch += e2.Difference;
                        break;
                    case "Skipped":
                        this.Skipped += e2.Difference;
                        break;
                    case "Total":
                        this.Total += e2.Difference;
                        break;
                }
            }
        }

        /// <summary>
        /// Combine the results of the supplied statistics objects of the specified type.
        /// </summary>
        /// <param name="stats">Collection of <see cref="Statistic"/> objects</param>
        /// <param name="statType">Create a new Statistic object of this type.</param>
        /// <returns>New Statistics Object</returns>
        public static Statistic AddStatistics(IEnumerable<IStatistic> stats, StatType statType)
        {
            Statistic ret = new Statistic(statType);
            ret.AddStatistic(stats.Where(s => s.Type == statType) );
            return ret;
        }


        #endregion ADD

        #region < AVERAGE Methods >

        /// <summary>
        /// Combine the supplied <see cref="Statistic"/> objects, then get the average.
        /// </summary>
        /// <param name="stats">Array of Stats objects</param>
        public void AverageStatistic(IEnumerable<IStatistic> stats)
        {
            this.AddStatistic(stats);
            int cnt = stats.Count() + 1;
            Total /= cnt;
            Copied /= cnt;
            Extras /= cnt;
            Failed /= cnt;
            Mismatch /= cnt;
            Skipped /= cnt;

        }

        /// <returns>New Statistics Object</returns>
        /// <inheritdoc cref=" AverageStatistic(IEnumerable{IStatistic})"/>
        public static Statistic AverageStatistics(IEnumerable<IStatistic> stats, StatType statType)
        {
            Statistic stat = AddStatistics(stats, statType);
            int cnt = stats.Count(s => s.Type == statType);
            if (cnt > 1)
            {
                stat.Total /= cnt;
                stat.Copied /= cnt;
                stat.Extras /= cnt;
                stat.Failed /= cnt;
                stat.Mismatch /= cnt;
                stat.Skipped /= cnt;
            }
            return stat;
        }

        #endregion AVERAGE

        #region < Subtract Methods >

        /// <summary>
        /// Subtract Method used by <see cref="RoboCopyResultsList"/> <br/>
        /// Events are deferred until all value changes have completed.
        /// </summary>
        /// <param name="stat">Statistics Item to subtract</param>
#if !NET40
        [MethodImpl(methodImplOptions: MethodImplOptions.AggressiveInlining)]
#endif
        public void Subtract(IStatistic stat)
        {
            if (stat.Type == this.Type && stat.NonZeroValue)
                Add(-stat.Total, -stat.Copied, -stat.Extras, -stat.Failed, -stat.Mismatch, -stat.Skipped);
        }

        #pragma warning disable CS1573 // Parameter has no matching param tag in the XML comment (but other parameters do)
        /// <param name="enablePropertyChangedEvent"><inheritdoc cref="EnablePropertyChangeEvent" path="*"/></param>
        /// <inheritdoc cref="Subtract(IStatistic)"/>        
        internal void Subtract(IStatistic stats, bool enablePropertyChangedEvent)
        {
            EnablePropertyChangeEvent = enablePropertyChangedEvent;
            Subtract(stats);
            EnablePropertyChangeEvent = true;
        }
        #pragma warning restore CS1573

        /// <summary>
        /// Subtract the results of the supplied Statistics objects to this Statistics object.
        /// </summary>
        /// <param name="stats">Statistics Item to subtract</param>
        public void Subtract(IEnumerable<IStatistic> stats)
        {
            foreach (Statistic stat in stats)
            {
                EnablePropertyChangeEvent = stat == stats.Last();
                Subtract(stat);
            }
        }

        /// <param name="MainStat">Statistics object to clone</param>
        /// <param name="stats"><inheritdoc cref="Subtract(IStatistic)"/></param>
        /// <returns>Clone of the <paramref name="MainStat"/> object with the <paramref name="stats"/> subtracted from it.</returns>
        /// <inheritdoc cref="Subtract(IStatistic)"/>       
        public static Statistic Subtract(IStatistic MainStat, IStatistic stats)
        {
            var ret = MainStat.Clone();
            ret.Subtract(stats);
            return ret;
        }

        #endregion Subtract


        /// <inheritdoc cref="IStatistic.Clone"/>
        public Statistic Clone() => new Statistic(this);

        object ICloneable.Clone() => new Statistic(this);

    }
}
