using System;
using Volpe.Cafe.MarketData.Design;

using TechKey = Volpe.Cafe.TechKeys.TechKey128;
using TI = Volpe.Cafe.TechnologyIndexes;

namespace Volpe.Cafe.Reporting.Generators
{
    [Serializable]
    internal static class GeneratorUtils
    {
        private static readonly string[] EngineFuelAcronyms = { "G", "D", "CNG" };
        private static readonly string[] EngineFuelNames = { "Gasoline", "Diesel", "CNG" };

        public static string GetVehicleEngineFuel(this IVehicleRevision vehRevision, bool acronym)
        {
            string[] engFuelStrings = acronym ? EngineFuelAcronyms : EngineFuelNames;
            FuelType fuel = vehRevision.FuelType;

            return fuel.Contains(FuelType.Gasoline) ? engFuelStrings[0]
                 : fuel.Contains(FuelType.Diesel) ? engFuelStrings[1]
                 : fuel.Contains(FuelType.CNG) ? engFuelStrings[2]
                 : string.Empty;
        }

        public static string GetVehicleEngineType(this IVehicleRevision vehRevision, bool acronym)
        {
            TechKey techKey = vehRevision.TechKey;

            bool isTurbo = techKey[TI.TURBO0]
                        || techKey[TI.TURBOE]
                        || techKey[TI.TURBOD]
                        || techKey[TI.TURBO1]
                        || techKey[TI.TURBO2]
                        || techKey[TI.VCR]
                        || techKey[TI.VTG]
                        || techKey[TI.VTGE]
                        || techKey[TI.TURBOAD]
                        || techKey[TI.ADSL]
                        || techKey[TI.DSLI];

            return !isTurbo ? "NA"
                 : acronym ? "T"
                 : "Turbo";
        }

        public static string GetVehicleTransmissionType(this IVehicleRevision vehRevision)
        {
            TechKey techKey = vehRevision.TechKey;

            return
                // CVTs
                techKey[TI.CVTL2] || techKey[TI.CVT] ? "CVT"

                // DCTs
                : techKey[TI.DCT8] ? "S8"
                : techKey[TI.DCT6] ? "S6"

                // automatics
                : techKey[TI.AT10L3] || techKey[TI.AT10L2] ? "A10"
                : techKey[TI.AT9L2] ? "A9"
                : techKey[TI.AT8L3] || techKey[TI.AT8L2] || techKey[TI.AT8] ? "A8"
                : techKey[TI.AT7L2] ? "A7"
                : techKey[TI.AT6] ? "A6"
                : techKey[TI.AT5] ? "A5"

                // not applicable
                : "N/A";
        }

        public static string GetRedesignRefreshString(this IOEMProduct product, int year)
        {
            return product == null ? string.Empty
                 : product.IsAtRedesign(year) ? "At Redesign"
                 : product.IsAtRefresh(year) ? "At Refresh"
                 : string.Empty;
        }

        #region <<< Numeric Precision & Formatting >>>

        //-----------------------------------------------------------------------------------------
        // NOTES:
        //
        // We want to allow for default-vs-high precision rounding of numeric values when writing
        // modeling reports. This affects general-purpose precision, vehs/compliance costs, veh
        // ratings, weights, and footprints. For some values (e.g., veh ratings), there is no
        // distinction between "default" and "high" modes. Some values (e.g., CAFE in compliance
        // report) will only be defined locally (within that report).
        //
        // This setting should be set **globally** for all modeling reports by the reporter object.
        // "Default" mode is used for production runs; "high" mode is for testing. Helper "append"
        // methods are provided to write values to the ReportDataGenerator's buffer.
        //
        // We want to keep these settings internal to our assembly only.
        //-----------------------------------------------------------------------------------------
        public class NumericPrecision
        {
            // applies to (almost) all numeric values not explicitly defined
            // all societal eff & costs and consumer costs reports should use this throughout
            public int DefaultDigits;
            public string DefaultFormat;

            // applies to cost (and cost-like) values in vehicles/diag-vehs and compliance reports
            public int CostDigits;
            public string CostFormat;

            // applies to FE/FS, and CO2 ratings in vehicles/diag-vehs and components reports
            public int VehRatingsDigits;
            public string VehRatingsFormat;

            // applies to CW, GW, TW, GVWR, GCWR, work-factor in all reports (except effects)
            public int WeightsDigits;
            // applies to footprint in all reports
            public int FootprintDigits;

        }

        private static readonly NumericPrecision LowPrecision = new NumericPrecision
        {
            DefaultDigits    = 4,   DefaultFormat    = "0.####",
            CostDigits       = 2,   CostFormat       = "0.##",
            VehRatingsDigits = 4,   VehRatingsFormat = "0.0###",
            WeightsDigits    = 0,
            FootprintDigits  = 1
        };

        private static readonly NumericPrecision DefaultPrecision = new NumericPrecision
        {
            DefaultDigits    = 8,   DefaultFormat    = "0.########",
            CostDigits       = 4,   CostFormat       = "0.####",
            VehRatingsDigits = 4,   VehRatingsFormat = "0.0###",
            WeightsDigits    = 0,
            FootprintDigits  = 1
        };

        public static NumericPrecision Precision { get; private set; } = DefaultPrecision;

        public static void SetLowPrecisionMode()
        {
            Precision = LowPrecision;
        }

        public static void SetDefaultPrecisionMode()
        {
            Precision = DefaultPrecision;
        }

        /// <summary>
        /// Applies rounding to the specified <paramref name="value"/> then writes it to the buffer, using the default
        /// values in the <see cref="Precision"/> object.
        /// </summary>
        public static void AppendRounded(this ReportDataGenerator generator, double value)
        {
            AppendRoundedHelper(generator, value, Precision.DefaultDigits, Precision.DefaultFormat);
        }

        public static void AppendCostValue(this ReportDataGenerator generator, double value)
        {
            AppendRoundedHelper(generator, value, Precision.CostDigits, Precision.CostFormat);
        }

        public static void AppendWeightValue(this ReportDataGenerator generator, double value)
        {
            AppendRoundedHelper(generator, value, Precision.WeightsDigits);
        }

        public static void AppendFootprintValue(this ReportDataGenerator generator, double value)
        {
            AppendRoundedHelper(generator, value, Precision.FootprintDigits);
        }

        public static void AppendVehRating(this ReportDataGenerator generator, double value)
        {
            AppendRoundedHelper(generator, value, Precision.VehRatingsDigits, Precision.VehRatingsFormat);
        }

        /// <summary>
        /// Appends:<br/>
        ///  - primary, secondary, and average ratings (FE or CO2)<br/>
        ///  - [optional] then fuelType string<br/>
        ///  - [optional] then fuelShare string
        /// </summary>
        public static void AppendVehRatings(this ReportDataGenerator generator,
            FuelValue rating, double averageRating, FuelValue fuelShare, bool appendFuelInfo)
        {
            int digits = Precision.VehRatingsDigits;
            string format = Precision.VehRatingsFormat;

            // primary rating value
            generator.AppendRoundedHelper(rating.PrimaryValue, digits, format);

            // secondary rating value (if exists)
            if (fuelShare.IsMultiFuel) {
                // only electricity and E85 may be secondary fuel types
                // in case of tertiary fuel vehicles (G+E85+E), only electricity is used
                if (fuelShare.Electricity != 0) {
                    generator.AppendRoundedHelper(rating.Electricity, digits, format);
                }
                else if (fuelShare.Ethanol85 != 0) {
                    generator.AppendRoundedHelper(rating.Ethanol85, digits, format);
                }
            }
            else {
                generator.AppendEmpty(1);
            }

            // average rating
            generator.AppendRoundedHelper(averageRating, digits, format);

            // fuel type and fuel share strings
            if (appendFuelInfo) {
                generator.AppendBuffer(fuelShare.FuelType.ToAcronymString());
                generator.AppendBuffer(fuelShare.ToString(format, true, true));
            }
        }

        /// <summary>
        /// Appends array of values (using default rounding), optionally appending as incremental over baseline.
        /// </summary>
        /// <remarks>
        /// See <see cref="AppendRounded(ReportDataGenerator, double)"/>.
        /// </remarks>
        public static void AppendArrayOfValues(
            this ReportDataGenerator generator, double[] scenValues, double[] baseValues)
        {
            if (baseValues != null) {
                for (int i = 0; i < scenValues.Length; i++) {
                    scenValues[i] -= baseValues[i];
                }
            }

            for (int i = 0; i < scenValues.Length; i++) {
                generator.AppendRounded(scenValues[i]);
            }
        }

        private static void AppendRoundedHelper(
            this ReportDataGenerator generator, double value, int digits, string numberFormat = null)
        {
            generator.AppendBuffer(Math.Round(value, digits).ToString(numberFormat));
        }

        #endregion
    }
}
