using System;
using Volpe.Cafe.Generic;
using Volpe.Cafe.Utils;

using RC = Volpe.Cafe.RegulatoryClass;

namespace Volpe.Cafe.MarketData.BaselineInfo
{
    /// <summary>
    /// Defines various credits and adjustments that a manufacturer may use toward compliance with either NHTSA's CAFE
    /// standards or EPA's CO-2 standards, for all regulatory classes and model years.
    /// </summary>
    [Serializable]
    public class CreditsAndAdjustments
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="CreditsAndAdjustments"/> class.
        /// </summary>
        public CreditsAndAdjustments(int minYear, int maxYear, RCAltValue<CreditsAndAdjustmentsInfo[]> values)
        {
            this.MinYear = minYear;
            this.MaxYear = maxYear;

            // CreditsAndAdjustments is an immutable object --
            // to enforce it's constant state, we need to clone the data before
            // assigning it to the underlying property
            // note: not all reg-classes are required to be defined in input
            this.Values = values?.Clone(item => item?.CloneArray());
        }

        /// <summary>
        /// Represents an empty <see cref="CreditsAndAdjustments"/> instance. This value is read-only.
        /// </summary>
        public static readonly CreditsAndAdjustments Empty = new CreditsAndAdjustments(0, 0, null);

        /// <summary>
        /// Gets the credits and adjustments values for the specified regulatory class and model year. If no values are
        /// defined for a given regulatory class or model year, an <see cref="CreditsAndAdjustmentsInfo.Empty"/> value
        /// will be returned.
        /// </summary>
        public CreditsAndAdjustmentsInfo this[RC regClass, int year]
        {
            get {
                var values = this.Values?[regClass];
                int yearIndex = Math.Max(this.MinYear, Math.Min(this.MaxYear, year)) - this.MinYear;

                return (values == null)
                    ? CreditsAndAdjustmentsInfo.Empty
                    : values[yearIndex];

                //// ***** pending *****
                //// when getting credits and adjustments for a model year that is not defined,
                //// at the lower bound, assume empty; at the upper bound, use info from last defined year
                //// this logic may need to be updated
                //return (values != null && year >= this.MinYear)
                //    ? values[Math.Min(year, this.MaxYear) - this.MinYear]
                //    : CreditsAndAdjustmentsInfo.Empty;
            }
        }

        /// <summary>
        /// Gets the lowest model year for which credits and adjustments values are defined.
        /// </summary>
        public int MinYear { get; }
        /// <summary>
        /// Gets the highest model year for which credits and adjustments values are defined.
        /// </summary>
        public int MaxYear { get; }

        /// <summary>
        /// Gets the credits and adjustments values for each regulatory class and model year.
        /// </summary>
        private RCAltValue<CreditsAndAdjustmentsInfo[]> Values { get; }

        /// <summary>
        /// Computes the weighted arithmetic mean of this instance for the specified regulatory class and model year.
        /// 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="year">The model year 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 CreditsAndAdjustmentsInfo CalcArithmeticMean(RC regClass, int year, RCDouble weight)
        {
            double totalW = weight[regClass];

            // since a "None" reg-class is considered unknown, undefined, or invalid,
            // we do not want to return anything meaningful if rc.None is supplied
            if (regClass == RC.None || totalW == 0) {
                return CreditsAndAdjustmentsInfo.Empty;
            }

            double acEfficiencySum = 0;
            double acLeakageSum = 0;
            double offCycleCreditsSum = 0;
            double fspCreditsSum = 0;
            double ffvCreditsSum = 0;

            foreach (RC rc in RegClasses.List) {
                double w = weight[rc];

                if (w != 0 && regClass.Contains(rc)) {
                    var value = this[rc, year];

                    acEfficiencySum += w * value.ACEfficiency;
                    acLeakageSum += w * value.ACLeakage;
                    offCycleCreditsSum += w * value.OffCycleCredits;
                    fspCreditsSum += w * value.FSPCredits;
                    ffvCreditsSum += w * value.FFVCredits;
                }
            }

            return new CreditsAndAdjustmentsInfo(acEfficiencySum / totalW,
                                                 acLeakageSum / totalW,
                                                 offCycleCreditsSum / totalW,
                                                 fspCreditsSum / totalW,
                                                 ffvCreditsSum / totalW);
        }

    }
}
