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

namespace Volpe.Cafe.MarketData.BaselineInfo
{
    /// <summary>
    /// Defines the initial credit bank, containing the amounts of CAFE and CO-2 credits that are available to a
    /// manufacturer at the start of analysis.
    /// </summary>
    [Serializable]
    public class BaselineCreditBank
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="BaselineCreditBank"/> class.
        /// </summary>
        public BaselineCreditBank(int minYear, int maxYear, RCValue<double[]> cafeBank, RCAltValue<double[]> co2Bank)
        {
            this.MinYear = minYear;
            this.MaxYear = maxYear;

            // BaselineCreditBank 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.CAFEBank = cafeBank.Clone(item => item?.CloneArray());
            this.CO2Bank = co2Bank.Clone(item => item?.CloneArray());
        }

        /// <summary>
        /// Gets the amount of credit available for the specified compliance program, regulatory class, and model year.
        /// </summary>
        /// <exception cref="ArgumentOutOfRangeException">The compliance program specified is not valid.</exception>
        public double this[ComplianceProgram program, RegulatoryClass regClass, int year]
        {
            get {
                switch (program) {
                    case ComplianceProgram.CAFE: return this.GetCAFEValue(regClass, year);
                    case ComplianceProgram.CO2: return this.GetCO2Value(regClass, year);

                    default:
                        throw new ArgumentOutOfRangeException(
                            nameof(program), "The compliance program specified must be CAFE or CO2.");
                }
            }
        }

        /// <summary>
        /// Gets the lowest model year for which the baseline credit bank has values defined.
        /// </summary>
        public int MinYear { get; }
        /// <summary>
        /// Gets the highest model year for which the baseline credit bank has values defined.
        /// </summary>
        public int MaxYear { get; }

        private RCValue<double[]> CAFEBank { get; }
        private RCAltValue<double[]> CO2Bank { get; }

        /// <summary>
        /// Determines whether the baseline credit bank has any values defined for the specified model year.
        /// </summary>
        /// <param name="year">The model year to test.</param>
        /// <returns>
        /// true, if the baseline credit bank has values defined for the specified model year; false, otherwise.
        /// </returns>
        public bool HasValue(int year)
        {
            return (this.MinYear <= year && year <= this.MaxYear);
        }

        /// <summary>
        /// Returns the amount of credit available in the CAFE bank for the specified regulatory class and model year.
        /// </summary>
        /// <param name="regClass">The regulatory class for which to obtain the CAFE credit value.</param>
        /// <param name="year">The model year for which to obtain the CAFE credit value.</param>
        /// <returns>The credit value from the CAFE credit bank.</returns>
        public double GetCAFEValue(RegulatoryClass regClass, int year)
        {
            return this.GetBankedValueHelper(this.CAFEBank?[regClass], year);
        }
        /// <summary>
        /// Returns the amount of credit available in the CO-2 bank for the specified regulatory class and model year.
        /// </summary>
        /// <param name="regClass">The regulatory class for which to obtain the CO-2 credit value.</param>
        /// <param name="year">The model year for which to obtain the CO-2 credit value.</param>
        /// <returns>The credit value from the CO-2 credit bank.</returns>
        public double GetCO2Value(RegulatoryClass regClass, int year)
        {
            return this.GetBankedValueHelper(this.CO2Bank?[regClass], year);
        }

        /// <summary>
        /// Returns an array containing a copy of all credit values available in the CAFE bank for the specified
        /// regulatory class, where the first element in the array corresponds to <see cref="MinYear"/> and the last
        /// element corresponds to <see cref="MaxYear"/>.
        /// </summary>
        public double[] GetAllCAFEValues(RegulatoryClass regClass)
        {
            return this.GetAllBankedValuesHelper(this.CAFEBank?[regClass]);
        }
        /// <summary>
        /// Returns an array containing a copy of all credit values available in the CO-2 bank for the specified
        /// regulatory class, where the first element in the array corresponds to <see cref="MinYear"/> and the last
        /// element corresponds to <see cref="MaxYear"/>.
        /// </summary>
        public double[] GetAllCO2Values(RegulatoryClass regClass)
        {
            return this.GetAllBankedValuesHelper(this.CO2Bank?[regClass]);
        }

        // Helper methods for Get*Value() and GetAll*Values()
        private double GetBankedValueHelper(double[] values, int year)
        {
            return (values != null && this.HasValue(year))
                ? values[year - this.MinYear]
                : 0;
        }

        private double[] GetAllBankedValuesHelper(double[] values)
        {
            return (values != null)
                ? values.CloneArray()
                : new double[this.MaxYear - this.MinYear + 1];
        }

    }
}
