using System;
using System.Linq;
using Volpe.Cafe.Generic;

namespace Volpe.Cafe
{
    /// <summary>
    /// Represents an object that stores modeling data, as double precision numeric values, for each regulatory class.
    /// <br/>
    /// This object supports and operates on all regulatory classes as defined by <see cref="RegClasses.List"/>.
    /// </summary>
    [Serializable]
    public class RCDouble : RCValue<double>, ICloneable
    {
        #region <<< Constructors >>>

        /// <summary>
        /// Initializes a new instance of the <see cref="RCDouble"/> class.
        /// </summary>
        public RCDouble() : base() { }

        /// <summary>
        /// Initializes a new instance of the <see cref="RCDouble"/> class using the specified initial value for all
        /// members.
        /// </summary>
        RCDouble(double initialValue) : base(() => initialValue) { }

        #endregion

        #region <<< Properties >>>

        //----- static values -----
        /// <summary>Gets the <see cref="RCDouble"/> value whose elements are all 0.</summary>
        public static RCDouble Zero => new RCDouble();
        /// <summary>Gets the <see cref="RCDouble"/> value whose elements are all 1.</summary>
        public static RCDouble One => new RCDouble(1D);

        //----- instance values -----
        /// <summary>Gets or sets the modeling data value based on the given <see cref="RegulatoryClass"/>.</summary>
        /// <exception cref="IndexOutOfRangeException">
        ///     The regClass specified does not represent a valid <see cref="RegulatoryClass"/>.
        /// </exception>
        public new double this[RegulatoryClass regClass]
        {
            get
            {
                switch (regClass)
                {
                    case RegulatoryClass.All            : return this.Total;
                    case RegulatoryClass.AllLightDuty   : return this.TotalLD;
                    case RegulatoryClass.AllPassengerCar: return this.TotalPC;

                    default:
                        return base[regClass];
                }
            }
            set => base[regClass] = value;
        }

        /// <summary>Gets the sum of the modeling data of all passenger regulatory classes.</summary>
        public double TotalPC => base[RegulatoryClass.DomesticCar] + base[RegulatoryClass.ImportedCar];
        /// <summary>Gets the sum of the modeling data of all light duty regulatory classes.</summary>
        public double TotalLD => this.TotalPC + base[RegulatoryClass.LightTruck];
        /// <summary>Gets the sum of the modeling data of all regulatory classes.</summary>
        public double Total => this.Items.Sum();

        #endregion

        #region <<< Methods >>>

        #region < Overloaded Operators >

        /// <summary>Returns a new <see cref="RCDouble"/> that is the sum of the two specified values.</summary>
        public static RCDouble operator +(RCDouble value1, RCDouble value2)
        {
            RCDouble result = new RCDouble();
            for (int i = 0; i < result.Items.Length; i++)
            {
                result.Items[i] = value1.Items[i] + value2.Items[i];
            }
            return result;
        }
        /// <summary>Returns a new <see cref="RCDouble"/> that is the difference of the two specified values.</summary>
        public static RCDouble operator -(RCDouble value1, RCDouble value2)
        {
            RCDouble result = new RCDouble();
            for (int i = 0; i < result.Items.Length; i++)
            {
                result.Items[i] = value1.Items[i] - value2.Items[i];
            }
            return result;
        }
        /// <summary>Returns a new <see cref="RCDouble"/> that is the product of the two specified values.</summary>
        public static RCDouble operator *(RCDouble value1, RCDouble value2)
        {
            RCDouble result = new RCDouble();
            for (int i = 0; i < result.Items.Length; i++)
            {
                result.Items[i] = value1.Items[i] * value2.Items[i];
            }
            return result;
        }
        /// <summary>Returns a new <see cref="RCDouble"/> that is the first value divided by the second value.</summary>
        public static RCDouble operator /(RCDouble value1, RCDouble value2)
        {
            RCDouble result = new RCDouble();
            for (int i = 0; i < result.Items.Length; i++)
            {
                result.Items[i] = value1.Items[i] / value2.Items[i];
            }
            return result;
        }

        /// <summary>Returns a copy of the specified value.</summary>
        public static RCDouble operator +(RCDouble value) => value.Clone();
        /// <summary>Returns a negated copy of the specified value.</summary>
        public static RCDouble operator -(RCDouble value) => Zero - value;
        /// <summary>Returns a copy of the specified value, incremented by one.</summary>
        public static RCDouble operator ++(RCDouble value) => value + One;
        /// <summary>Returns a copy of the specified value, decremented by one.</summary>
        public static RCDouble operator --(RCDouble value) => value - One;

        #endregion

        #region < ICloneable Members >

        /// <summary>
        /// Creates a new object that is a copy of the current <see cref="RCDouble"/> instance.
        /// </summary>
        /// <returns>A new object that is a copy of this <see cref="RCDouble"/>.</returns>
        object ICloneable.Clone()
        {
            return this.Clone();
        }
        /// <summary>
        /// Creates a copy of the current <see cref="RCDouble"/> instance.
        /// </summary>
        /// <returns>A new object that is a copy of this <see cref="RCDouble"/>.</returns>
        public new RCDouble Clone()
        {
            RCDouble value = new RCDouble();
            this.CloneInternal(this, value, null);
            return value;
        }

        #endregion

        /// <summary>
        /// Returns the string representation of this <see cref="RCDouble"/> instance, using the specified format for
        /// all elements.
        /// </summary>
        /// <param name="format">A format string.</param>
        /// <returns>
        /// The string representation of the <see cref="RCDouble"/> instance, using the specified format.
        /// </returns>
        public string ToString(string format)
        {
            return GenericsHelper.CalcString(this.Items, RegClasses.Names, d => d.ToString(format));
        }
        /// <summary>
        /// Returns the string representation of this <see cref="RCDouble"/> instance, whose elements are rounded with
        /// the specified precision.
        /// </summary>
        /// <param name="digits">The number of fractional digits (precision) in the return value.</param>
        /// <returns>The string representation of the <see cref="RCDouble"/> instance.</returns>
        public string ToString(int digits)
        {
            return GenericsHelper.CalcString(this.Items, RegClasses.Names, d => Math.Round(d, digits).ToString());
        }

        /// <summary>
        /// Appends the specified value to this instance.
        /// </summary>
        /// <param name="value">The value to append to this instance.</param>
        public void Append(RCDouble value)
        {
            for (int rc = 0; rc < value.Items.Length; rc++)
            {
                this.Items[rc] += value.Items[rc];
            }
        }

        //----- methods for computing averages -----
        /// <summary>
        /// Computes the weighted arithmetic mean of this instance for the specified regulatory class. If non-aggregate
        /// regulatory classes are used, the individual components are returned.
        /// </summary>
        /// <param name="regClass">The regulatory class for which to compute the weighted arithmetic mean.</param>
        /// <param name="weight">The weight value to use for averaging.</param>
        /// <returns>
        /// The weighted arithmetic mean, whenever aggregate regulatory classes are used, or the individual components
        /// for non-aggregate classes.
        /// </returns>
        public double CalcArithmeticMean(RegulatoryClass regClass, RCDouble weight)
        {
            if (IsAggregateRC(regClass))
            {
                double w = weight[regClass];
                double p = (this * weight)[regClass];
                return (w == 0) ? 0 : p / w;
            }
            else
            {
                return (regClass == RegulatoryClass.None) ? 0 : this[regClass];
            }
        }

        /// <summary>
        /// Computes the weighted geometric mean of this instance for the specified regulatory class. If non-aggregate
        /// regulatory classes are used, the individual components are returned.
        /// </summary>
        /// <param name="regClass">The regulatory class for which to compute the weighted arithmetic mean.</param>
        /// <param name="weight">The weight value to use for averaging.</param>
        /// <returns>
        /// The weighted geometric mean, whenever aggregate regulatory classes are used, or the individual components
        /// for non-aggregate classes.
        /// </returns>
        public double CalcGeometricMean(RegulatoryClass regClass, RCDouble weight)
        {
            if (IsAggregateRC(regClass))
            {
                double w = weight[regClass];
                double p = (SafeLog(this) * weight)[regClass];
                return (w == 0) ? 0 : Math.Exp(p / w);
            }
            else
            {
                return (regClass == RegulatoryClass.None) ? 0 : this[regClass];
            }
        }

        /// <summary>
        /// Computes the weighted harmonic mean of this instance for the specified regulatory class. If non-aggregate
        /// regulatory classes are used, the individual components are returned.
        /// </summary>
        /// <param name="regClass">The regulatory class for which to compute the weighted harmonic mean.</param>
        /// <param name="weight">The weight value to use for averaging.</param>
        /// <returns>
        /// The weighted harmonic mean, whenever aggregate regulatory classes are used, or the individual components
        /// for non-aggregate classes.
        /// </returns>
        public double CalcHarmonicMean(RegulatoryClass regClass, RCDouble weight)
        {
            if (IsAggregateRC(regClass))
            {
                double w = weight[regClass];
                double p = SafeDivide(weight, this)[regClass];
                return (p == 0) ? 0 : w / p;
            }
            else
            {
                return (regClass == RegulatoryClass.None) ? 0 : this[regClass];
            }
        }

        // helper methods for computing averages
        static bool IsAggregateRC(RegulatoryClass regClass)
        {
            return regClass == RegulatoryClass.All
                || regClass == RegulatoryClass.AllLightDuty
                || regClass == RegulatoryClass.AllPassengerCar;
        }
        static RCDouble SafeDivide(RCDouble numerator, RCDouble denominator)
        {
            RCDouble result = Zero;
            for (int i = 0; i < numerator.Items.Length; i++)
            {
                if (denominator.Items[i] != 0)
                {
                    result.Items[i] = numerator.Items[i] / denominator.Items[i];
                }
            }
            return result;
        }
        static RCDouble SafeLog(RCDouble value)
        {
            RCDouble result = Zero;
            for (int i = 0; i < value.Items.Length; i++)
            {
                if (value.Items[i] > 0)
                {
                    result.Items[i] = Math.Log(value.Items[i]);
                }
            }
            return result;
        }

        #endregion
    }
}
