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

namespace Volpe.Cafe
{
    /// <summary>
    /// Provides basic information about the various target functions supported by the CAFE Model.
    /// </summary>
    public static class TargetFunctionInformation
    {
        private enum FunctionType
        {
            Flat = 0,
            AreaBased = 1,
            WeightBased = 2,
            WorkFactorBased = 3
        }

        private class FunctionInfo
        {
            public FunctionInfo(FunctionType type, int coefCount, bool isCO2 = false)
            {
                this.Type = type;
                this.CoefficientCount = coefCount;
                this.CoefficientNames = Enumerable.Range('A', coefCount).Select(index => (char)index).ToArray();
                this.IsCO2Only = isCO2;
            }

            public FunctionType Type { get; }
            public int CoefficientCount { get; }
            public char[] CoefficientNames { get; }
            public bool IsCO2Only { get; }
        }

        private static readonly Dictionary<int, FunctionInfo> Functions = new Dictionary<int, FunctionInfo> {
            // CAFE/CO2 functions
            [  1] = new FunctionInfo(FunctionType.Flat, coefCount: 1),

            [  2] = new FunctionInfo(FunctionType.AreaBased, coefCount: 4),
            [  4] = new FunctionInfo(FunctionType.AreaBased, coefCount: 3),
            [  6] = new FunctionInfo(FunctionType.AreaBased, coefCount: 4),
            [ 16] = new FunctionInfo(FunctionType.AreaBased, coefCount: 8),
            [206] = new FunctionInfo(FunctionType.AreaBased, coefCount: 8),

            [  3] = new FunctionInfo(FunctionType.WeightBased, coefCount: 4),
            [  5] = new FunctionInfo(FunctionType.WeightBased, coefCount: 3),
            [  7] = new FunctionInfo(FunctionType.WeightBased, coefCount: 4),
            [ 17] = new FunctionInfo(FunctionType.WeightBased, coefCount: 8),
            [207] = new FunctionInfo(FunctionType.WeightBased, coefCount: 8),

            [  8] = new FunctionInfo(FunctionType.WorkFactorBased, coefCount: 4),
            [  9] = new FunctionInfo(FunctionType.WorkFactorBased, coefCount: 2),
            [ 10] = new FunctionInfo(FunctionType.WorkFactorBased, coefCount: 3),
            [208] = new FunctionInfo(FunctionType.WorkFactorBased, coefCount: 5),

            // CO2-only functions
            [306] = new FunctionInfo(FunctionType.AreaBased, coefCount:  6, isCO2: true),
            [316] = new FunctionInfo(FunctionType.AreaBased, coefCount: 10, isCO2: true),
            [406] = new FunctionInfo(FunctionType.AreaBased, coefCount:  9, isCO2: true),

            [307] = new FunctionInfo(FunctionType.WeightBased, coefCount:  6, isCO2: true),
            [317] = new FunctionInfo(FunctionType.WeightBased, coefCount: 10, isCO2: true),
            [407] = new FunctionInfo(FunctionType.WeightBased, coefCount:  9, isCO2: true),

            [308] = new FunctionInfo(FunctionType.WorkFactorBased, coefCount: 4, isCO2: true),
            [309] = new FunctionInfo(FunctionType.WorkFactorBased, coefCount: 2, isCO2: true),
            [310] = new FunctionInfo(FunctionType.WorkFactorBased, coefCount: 3, isCO2: true),
        };

        /// <summary>
        /// Determines whether the specified target <paramref name="function"/> is supported by the modeling system.
        /// </summary>
        public static bool IsSupportedFunction(int function)
        {
            return Functions.ContainsKey(function);
        }

        /// <summary>
        /// Returns the number of coefficients for the specified target <paramref name="function"/>, or -1 if the
        /// <paramref name="function"/> is not supported.
        /// </summary>
        public static int GetCoefficientCount(int function)
        {
            return Functions.TryGetValue(function, out var value) ? value.CoefficientCount : -1;
        }

        /// <summary>
        /// Returns an array of chars representing the coefficient names for the specified target
        /// <paramref name="function"/>, or <c>null</c> if the <paramref name="function"/> is not supported.
        /// </summary>
        public static char[] GetCoefficientNames(int function)
        {
            return Functions.TryGetValue(function, out var value) ? value.CoefficientNames : null;
        }

        /// <summary>
        /// Determines whether the specified target <paramref name="function"/> is defined for the CO2 program only.
        /// </summary>
        public static bool IsCO2OnlyFunction(int function)
        {
            return Functions.TryGetValue(function, out var value) && value.IsCO2Only;
        }

        /// <summary>
        /// Determines whether the specified target <paramref name="function"/> represents an attribute-based
        /// functional form, using the vehicles' footprints as the attributes.
        /// </summary>
        public static bool IsAreaBasedFunction(int function)
        {
            return IsAttributeBasedHelper(function, FunctionType.AreaBased);
        }

        /// <summary>
        /// Determines whether the specified target <paramref name="function"/> represents an attribute-based
        /// functional form, using the vehicles' curb weights as the attributes.
        /// </summary>
        public static bool IsWeightBasedFunction(int function)
        {
            return IsAttributeBasedHelper(function, FunctionType.WeightBased);
        }

        /// <summary>
        /// Determines whether the specified target <paramref name="function"/> represents an attribute-based
        /// functional form, using the vehicles' work factors as the attributes.
        /// </summary>
        public static bool IsWorkFactorBasedFunction(int function)
        {
            return IsAttributeBasedHelper(function, FunctionType.WorkFactorBased);
        }

        private static bool IsAttributeBasedHelper(int function, FunctionType desiredType)
        {
            return Functions.TryGetValue(function, out var value) && value.Type == desiredType;
        }

    }
}
