using System;
using System.Collections.Generic;
using System.Collections;

namespace FastReport.AdvMatrix
{
    /// <summary>
    /// Hold the list of registered aggregate functions.
    /// </summary>
    public static class Aggregates
    {
        private static Dictionary<string, Type> registeredAggregates = new Dictionary<string, Type>();
        private static List<string> names = new List<string>();

        /// <summary>
        /// Gets names of aggregates registered.
        /// </summary>
        public static string[] Names
        {
            get { return names.ToArray(); }
        }

        /// <summary>
        /// Registers the aggregate function.
        /// </summary>
        /// <param name="name">The function name.</param>
        /// <param name="aggregateType">The type of aggregate class.</param>
        public static void Register(string name, Type aggregateType)
        {
            if (!aggregateType.IsSubclassOf(typeof(AggregateBase)))
                throw new Exception("The 'aggregateType' parameter must be of AggregateBase type.");
            if (!registeredAggregates.ContainsKey(name))
            {
                registeredAggregates[name] = aggregateType;
                names.Add(name);
            }
        }

        /// <summary>
        /// Returns an aggregate with specified name.
        /// </summary>
        /// <param name="name">The name of aggregate function.</param>
        /// <returns>The aggregate class type.</returns>
        public static Type Find(string name)
        {
            Type aggregateType;
            if (registeredAggregates.TryGetValue(name, out aggregateType))
                return aggregateType;
            return null;
        }

        static Aggregates()
        {
            Register("Sum", typeof(SumAggregate));
            Register("Avg", typeof(AvgAggregate));
            Register("Min", typeof(MinAggregate));
            Register("Max", typeof(MaxAggregate));
            Register("Count", typeof(CountAggregate));
            Register("CountDistinct", typeof(CountDistinctAggregate));
            Register("StDev", typeof(StDevAggregate));
            Register("StDevP", typeof(StDevPAggregate));
            Register("Var", typeof(VarAggregate));
            Register("VarP", typeof(VarPAggregate));
            Register("First", typeof(FirstAggregate));
            Register("Last", typeof(LastAggregate));
            Register("ValuesList", typeof(ValuesListAggregate));
        }
    }

    /// <summary>
    /// Represents base class for AdvMatrixObject aggregates.
    /// </summary>
    public abstract class AggregateBase
    {
        /// <summary>
        /// Adds a value to aggregate.
        /// </summary>
        /// <param name="value">The value.</param>
        public abstract void AddValue(object value);
        
        /// <summary>
        /// Gets aggregate value.
        /// </summary>
        /// <returns>Aggregate value.</returns>
        public abstract object GetValue();

        /// <summary>
        /// Merges value from another, similar aggregate.
        /// </summary>
        /// <param name="aggr">Aggregate to merge value from.</param>
        public abstract void Merge(AggregateBase aggr);
    }

    /// <summary>
    /// Represents the "Sum" aggregate.
    /// </summary>
    public class SumAggregate: AggregateBase
    {
        private object value;

        /// <inheritdoc/>
        public override void AddValue(object value)
        {
            if (this.value == null)
                this.value = value;
            else
                this.value = (dynamic)this.value + (dynamic)value;
        }

        /// <inheritdoc/>
        public override object GetValue()
        {
            return value;
        }

        /// <inheritdoc/>
        public override void Merge(AggregateBase aggr)
        {
            SumAggregate a = aggr as SumAggregate;
            if (a != null)
            {
                AddValue(a.value);
            }
        }
    }

    /// <summary>
    /// Represents the "Avg" aggregate.
    /// </summary>
    public class AvgAggregate : AggregateBase
    {
        private object value;
        private int count;

        /// <inheritdoc/>
        public override void AddValue(object value)
        {
            if (this.value == null)
                this.value = value;
            else
                this.value = (dynamic)this.value + (dynamic)value;
            count++;
        }

        /// <inheritdoc/>
        public override object GetValue()
        {
            if (count == 0)
                return null;
            if (value is float || value is double || value is decimal)
                return (dynamic)value / count;
            return (dynamic)value / (double)count;
        }

        /// <inheritdoc/>
        public override void Merge(AggregateBase aggr)
        {
            AvgAggregate a = aggr as AvgAggregate;
            if (a != null && a.value != null)
            {
                value = (dynamic)value + (dynamic)a.value;
                count += a.count;
            }
        }
    }

    /// <summary>
    /// Represents the "Min" aggregate.
    /// </summary>
    public class MinAggregate : AggregateBase
    {
        private object value;

        /// <inheritdoc/>
        public override void AddValue(object value)
        {
            if (this.value == null)
                this.value = value;
            else
            {
                if ((dynamic)value < (dynamic)this.value)
                    this.value = value;
            }
        }

        /// <inheritdoc/>
        public override object GetValue()
        {
            return value;
        }

        /// <inheritdoc/>
        public override void Merge(AggregateBase aggr)
        {
            MinAggregate a = aggr as MinAggregate;
            if (a != null)
            {
                AddValue(a.value);
            }
        }
    }

    /// <summary>
    /// Represents the "Max" aggregate.
    /// </summary>
    public class MaxAggregate : AggregateBase
    {
        private object value;

        /// <inheritdoc/>
        public override void AddValue(object value)
        {
            if (this.value == null)
                this.value = value;
            else
            {
                if ((dynamic)value > (dynamic)this.value)
                    this.value = value;
            }
        }

        /// <inheritdoc/>
        public override object GetValue()
        {
            return value;
        }

        /// <inheritdoc/>
        public override void Merge(AggregateBase aggr)
        {
            MaxAggregate a = aggr as MaxAggregate;
            if (a != null)
            {
                AddValue(a.value);
            }
        }
    }

    /// <summary>
    /// Represents the "Count" aggregate.
    /// </summary>
    public class CountAggregate : AggregateBase
    {
        private int count;

        /// <inheritdoc/>
        public override void AddValue(object value)
        {
            count++;
        }

        /// <inheritdoc/>
        public override object GetValue()
        {
            return count;
        }

        /// <inheritdoc/>
        public override void Merge(AggregateBase aggr)
        {
            CountAggregate a = aggr as CountAggregate;
            if (a != null)
            {
                count += a.count;
            }
        }
    }

    /// <summary>
    /// Represents the "CountDistinct" aggregate.
    /// </summary>
    public class CountDistinctAggregate : AggregateBase
    {
        private Hashtable values = new Hashtable();

        /// <inheritdoc/>
        public override void AddValue(object value)
        {
            values[value] = 1;
        }

        /// <inheritdoc/>
        public override object GetValue()
        {
            return values.Keys.Count;
        }

        /// <inheritdoc/>
        public override void Merge(AggregateBase aggr)
        {
            CountDistinctAggregate a = aggr as CountDistinctAggregate;
            if (a != null)
            {
                foreach (DictionaryEntry e in a.values)
                {
                    values[e.Key] = 1;
                }
            }
        }
    }

    /// <summary>
    /// Represents the "Var" aggregate.
    /// </summary>
    public class VarAggregate : AggregateBase
    {
        private List<dynamic> values = new List<dynamic>();

        /// <inheritdoc/>
        public override void AddValue(object value)
        {
            values.Add(value);
        }

        /// <inheritdoc/>
        public override object GetValue()
        {
            if (values.Count < 2)
                return null;

            // calculate average
            double average = 0;
            foreach (dynamic v in values)
            {
                average += (double)v;
            }
            average = average / values.Count;

            // sum each difference
            double diff = 0;
            foreach (dynamic v in values)
            {
                diff += Math.Pow((double)v - average, 2);
            }

            return diff / (values.Count - 1);
        }

        /// <inheritdoc/>
        public override void Merge(AggregateBase aggr)
        {
            VarAggregate a = aggr as VarAggregate;
            if (a != null)
            {
                values.AddRange(a.values);
            }
        }
    }

    /// <summary>
    /// Represents the "VarP" aggregate.
    /// </summary>
    public class VarPAggregate : AggregateBase
    {
        private List<dynamic> values = new List<dynamic>();

        /// <inheritdoc/>
        public override void AddValue(object value)
        {
            values.Add(value);
        }

        /// <inheritdoc/>
        public override object GetValue()
        {
            if (values.Count == 0)
                return null;

            // calculate average
            double average = 0;
            foreach (dynamic v in values)
            {
                average += (double)v;
            }
            average = average / values.Count;

            // sum each difference
            double diff = 0;
            foreach (dynamic v in values)
            {
                diff += Math.Pow((double)v - average, 2);
            }

            return diff / values.Count;
        }

        /// <inheritdoc/>
        public override void Merge(AggregateBase aggr)
        {
            VarPAggregate a = aggr as VarPAggregate;
            if (a != null)
            {
                values.AddRange(a.values);
            }
        }
    }

    /// <summary>
    /// Represents the "StDev" aggregate.
    /// </summary>
    public class StDevAggregate : VarAggregate
    {
        /// <inheritdoc/>
        public override object GetValue()
        {
            return Math.Sqrt((double)base.GetValue());
        }
    }

    /// <summary>
    /// Represents the "StDevP" aggregate.
    /// </summary>
    public class StDevPAggregate : VarPAggregate
    {
        /// <inheritdoc/>
        public override object GetValue()
        {
            return Math.Sqrt((double)base.GetValue());
        }
    }

    /// <summary>
    /// Represents the "First" aggregate.
    /// </summary>
    public class FirstAggregate : AggregateBase
    {
        private object value;

        /// <inheritdoc/>
        public override void AddValue(object value)
        {
            if (this.value == null)
                this.value = value;
        }

        /// <inheritdoc/>
        public override object GetValue()
        {
            return value;
        }

        /// <inheritdoc/>
        public override void Merge(AggregateBase aggr)
        {
            FirstAggregate a = aggr as FirstAggregate;
            if (a != null)
            {
                AddValue(a.value);
            }
        }
    }

    /// <summary>
    /// Represents the "Last" aggregate.
    /// </summary>
    public class LastAggregate : AggregateBase
    {
        private object value;

        /// <inheritdoc/>
        public override void AddValue(object value)
        {
            this.value = value;
        }

        /// <inheritdoc/>
        public override object GetValue()
        {
            return value;
        }

        /// <inheritdoc/>
        public override void Merge(AggregateBase aggr)
        {
            LastAggregate a = aggr as LastAggregate;
            if (a != null)
            {
                AddValue(a.value);
            }
        }
    }

    /// <summary>
    /// Represents the "ValuesList" aggregate.
    /// </summary>
    public class ValuesListAggregate: UserAggregate
    {
    }
    
    /// <summary>
    /// Represents the "User" aggregate.
    /// </summary>
    public class UserAggregate : AggregateBase
    {
        private List<dynamic> values = new List<dynamic>();

        /// <inheritdoc/>
        public override void AddValue(object value)
        {
            values.Add(value);
        }

        /// <inheritdoc/>
        public override object GetValue()
        {
            return values;
        }

        /// <inheritdoc/>
        public override void Merge(AggregateBase aggr)
        {
            UserAggregate a = aggr as UserAggregate;
            if (a != null)
            {
                values.AddRange(a.values);
            }
        }
    }
}