using System;
using System.Linq;

namespace Volpe.Cafe.Generic
{
    /// <summary>
    /// Provides extension methods for various "Generic{T}" objects (such as RCValue{T}), where each element of type T
    /// implements the <see cref="IAppendable"/> interface.
    /// </summary>
    public static class GenericsExtensions
    {
        /// <summary>
        /// Appends all elements from the specified value to the specified target instance.
        /// </summary>
        public static void Append<T>(this FTValue<T> target, FTValue<T> value) where T : IAppendable
        {
            AppendHelper(target.Items, value.Items);
        }
        /// <summary>
        /// Appends all elements from the specified value to the specified target instance.
        /// </summary>
        public static void Append<T>(this PollutantValue<T> target, PollutantValue<T> value) where T : IAppendable
        {
            AppendHelper(target.Items, value.Items);
        }
        /// <summary>
        /// Appends all elements from the specified value to the specified target instance.
        /// </summary>
        public static void Append<T>(this RCAltValue<T> target, RCAltValue<T> value) where T : IAppendable
        {
            AppendHelper(target.Items, value.Items);
        }
        /// <summary>
        /// Appends all elements from the specified value to the specified target instance.
        /// </summary>
        public static void Append<T>(this RCValue<T> target, RCValue<T> value) where T : IAppendable
        {
            AppendHelper(target.Items, value.Items);
        }
        /// <summary>
        /// Appends all elements from the specified value to the specified target instance.
        /// </summary>
        public static void Append<T>(this SCValue<T> target, SCValue<T> value) where T : IAppendable
        {
            AppendHelper(target.Items, value.Items);
        }
        /// <summary>
        /// Appends all elements from the specified value to the specified target instance.
        /// </summary>
        public static void Append<T>(this VCValue<T> target, VCValue<T> value) where T : IAppendable
        {
            AppendHelper(target.Items, value.Items);
        }
        /// <summary>
        /// Appends all elements from the specified value to the specified target instance.
        /// </summary>
        public static void Append<T>(this VSValue<T> target, VSValue<T> value) where T : IAppendable
        {
            AppendHelper(target.Items, value.Items);
        }

        /// <summary>
        /// Returns the modeling data value based on the given <see cref="FuelType"/>.
        /// This method supports fuel type aggregation. Refer to the remarks section for more information.
        /// </summary>
        /// <remarks>
        /// This method supports composite or aggregate fuel types. That is, <see cref="FuelType.All"/> may be used to
        /// obtain a total sum across all fuel types; multiple <see cref="FuelType"/> values may also be combined to
        /// obtain a sum of those fuel types.<br/>
        /// Due to the nature of aggregation, this method incurs a performance penalty and is not intended for use
        /// during modeling/analysis. The use of this method is intended for reporting purposes.
        /// </remarks>
        /// <exception cref="ArgumentOutOfRangeException">
        ///     The fuelType specified does not represent a valid <see cref="FuelType"/>.
        /// </exception>
        public static T GetValue<T>(this FTValue<T> target, FuelType fuelType) where T : IAppendable, new()
        {
            return GetValueHelper(
                target.Items, fuelType.GetListIndex(), FuelTypes.List, fuel => fuelType.Contains(fuel));
        }
        /// <summary>
        /// Returns the modeling data value based on the given <see cref="RegulatoryClass"/>.
        /// This method supports regulatory class aggregation. Refer to the remarks section for more information.
        /// </summary>
        /// <remarks>
        /// This method supports composite or aggregate regulatory classes. That is, <see cref="RegulatoryClass.All"/>
        /// and <see cref="RegulatoryClass.AllLightDuty"/> may be used to obtain a total sum across all applicable
        /// regulatory classes.<br/>
        /// Due to the nature of aggregation, this method incurs a performance penalty and is not intended for use
        /// during modeling/analysis. The use of this method is intended for reporting purposes.
        /// </remarks>
        /// <exception cref="ArgumentOutOfRangeException">
        ///     The regClass specified does not represent a valid <see cref="RegulatoryClass"/>.
        /// </exception>
        public static T GetValue<T>(this RCAltValue<T> target, RegulatoryClass regClass) where T : IAppendable, new()
        {
            return GetValueHelper(
                target.Items, regClass.GetAltListIndex(), RegClasses.AltList, rc => regClass.Contains(rc));
        }
        /// <summary>
        /// Returns the modeling data value based on the given <see cref="RegulatoryClass"/>.
        /// This method supports regulatory class aggregation. Refer to the remarks section for more information.
        /// </summary>
        /// <remarks>
        /// This method supports composite or aggregate regulatory classes. That is, <see cref="RegulatoryClass.All"/>,
        /// <see cref="RegulatoryClass.AllLightDuty"/>, and <see cref="RegulatoryClass.AllPassengerCar"/> may be used
        /// to obtain a total sum across all applicable regulatory classes.<br/>
        /// Due to the nature of aggregation, this method incurs a performance penalty and is not intended for use
        /// during modeling/analysis. The use of this method is intended for reporting purposes.
        /// </remarks>
        /// <exception cref="ArgumentOutOfRangeException">
        ///     The regClass specified does not represent a valid <see cref="RegulatoryClass"/>.
        /// </exception>
        public static T GetValue<T>(this RCValue<T> target, RegulatoryClass regClass) where T : IAppendable, new()
        {
            return GetValueHelper(
                target.Items, regClass.GetListIndex(), RegClasses.List, rc => regClass.Contains(rc));
        }
        /// <summary>
        /// Returns the modeling data value based on the given <see cref="VehicleClass"/>.
        /// This method supports vehicle class aggregation. Refer to the remarks section for more information.
        /// </summary>
        /// <remarks>
        /// This method supports composite or aggregate vehicle classes. That is, <see cref="VehicleClass.All"/> may be
        /// used to obtain a total sum across all vehicle classes.<br/>
        /// Due to the nature of aggregation, this method incurs a performance penalty and is not intended for use
        /// during modeling/analysis. The use of this method is intended for reporting purposes.
        /// </remarks>
        /// <exception cref="ArgumentOutOfRangeException">
        ///     The vehClass specified does not represent a valid <see cref="VehicleClass"/>.
        /// </exception>
        public static T GetValue<T>(this VCValue<T> target, VehicleClass vehClass) where T : IAppendable, new()
        {
            return GetValueHelper(
                target.Items, vehClass.GetAltListIndex(), VehicleClasses.AltList, vc => vehClass.Contains(vc));
        }

        //----- private helper methods -----
        /// <summary>
        /// Helper method for all Append{T} methods.
        /// Appends all elements from the specified valueItems array to the specified targetItems array.
        /// </summary>
        private static void AppendHelper<T>(T[] targetItems, T[] valueItems) where T : IAppendable
        {
            for (int i = 0; i < targetItems.Length; i++) {
                targetItems[i].Append(valueItems[i]);
            }
        }

        /// <summary>
        /// Helper method for all GetValue{T} methods.
        /// Returns the modeling data value based on the given TClass.
        /// </summary>
        /// <typeparam name="TItem">Type of items from the target object.</typeparam>
        /// <typeparam name="TClass">Type of vehicle or fuel classification (reg-class, fuel-type, etc.).</typeparam>
        private static TItem GetValueHelper<TItem, TClass>(
            TItem[] targetItems, int classIndex, TClass[] classList, Func<TClass, bool> contains)
            where TItem : IAppendable, new()
        {
            // "individual" classes
            if (classIndex != -1) {
                return targetItems[classIndex];
            }

            // invalid classes (for given type)
            if (!classList.Any(c => contains(c))) {
                throw new ArgumentOutOfRangeException(
                    "class/category",
                    $"The class or category specified does not represent a valid {typeof(TClass)}.");
            }

            // "aggregation" classes
            var result = new TItem();
            result.Clear();
            for (int i = 0; i < classList.Length; i++) {
                if (contains(classList[i])) {
                    result.Append(targetItems[i]);
                }
            }
            return result;
        }

    }
}
