#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member

using System;
using System.Collections.Generic;

using Volpe.Cafe.Generic;
using Volpe.Cafe.MarketData.BaselineInfo;
using Volpe.Cafe.MarketData.Design;
using Volpe.Cafe.Utils;

namespace Volpe.Cafe.MarketData
{
    /// <summary>
    /// Represents an original equipment manufacturer (OEM), which produces vehicles, components, and component
    /// families.
    /// </summary>
    /// <seealso cref="BaselineManufacturer"/>
    /// <seealso cref="BaselineInfo.BaselineCreditBank"/>
    /// <seealso cref="BaselineInfo.CreditsAndAdjustments"/>
    /// <seealso cref="MarketData.Industry"/>
    /// <seealso cref="ManufacturerRevision"/>
    /// <seealso cref="Vehicle"/>
    /// <seealso cref="Platform"/>
    /// <seealso cref="Engine"/>
    /// <seealso cref="Transmission"/>
    /// <seealso cref="PlatformFamily"/>
    /// <seealso cref="EngineFamily"/>
    /// <seealso cref="TransmissionFamily"/>
    [Serializable]
    public class Manufacturer : IManufacturer
    {
        #region <<< Constructors >>>

        /// <summary>
        /// Initializes a new instance of the <see cref="Manufacturer"/> class.
        /// </summary>
        /// <param name="index">The unique runtime-assigned index for the manufacturer.</param>
        /// <param name="baselineInfo">A description of engineering characteristics for the manufacturer.</param>
        /// <param name="baselineCreditBank">
        ///     The initial credit bank, containing the amounts of CAFE and CO-2 credits that are available to the
        ///     manufacturer at the start of analysis.
        /// </param>
        /// <param name="creditsAndAdjustments">
        ///     The various credits and adjustments that the manufacturer may use toward compliance with either NHTSA's
        ///     CAFE standards or EPA's CO-2 standards.
        /// </param>
        public Manufacturer(int index,
                            BaselineManufacturer baselineInfo,
                            BaselineCreditBank baselineCreditBank,
                            CreditsAndAdjustments creditsAndAdjustments)
        {
            this.Index = index;
            this.BaselineInfo = baselineInfo;
            this.BaselineCreditBank = baselineCreditBank;
            this.CreditsAndAdjustments = creditsAndAdjustments;

            // initialize the runtime credit banks based on the baseline credit bank
            int minYear = this.BaselineCreditBank.MinYear;
            int maxYear = this.BaselineCreditBank.MaxYear;

            this.CAFEBank = new AnnualValue<RCValue<double>>(minYear, maxYear);
            this.CO2Bank = new AnnualValue<RCAltValue<double>>(minYear, maxYear);

            for (int year = minYear; year <= maxYear; year++) {
                this.CAFEBank[year] = new RCValue<double>();
                foreach (RegulatoryClass regClass in RegClasses.List) {
                    this.CAFEBank[year][regClass] = this.BaselineCreditBank.GetCAFEValue(regClass, year);
                }

                this.CO2Bank[year] = new RCAltValue<double>();
                foreach (RegulatoryClass regClass in RegClasses.AltList) {
                    this.CO2Bank[year][regClass] = this.BaselineCreditBank.GetCO2Value(regClass, year);
                }
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Manufacturer"/> class.
        /// </summary>
        /// <param name="index">The unique runtime-assigned index for the manufacturer.</param>
        /// <param name="baselineInfos">
        ///     A tuple of the following baseline initialization objects:<br/>
        ///     <see cref="BaselineManufacturer"/>: a description of engineering characteristics for the manufacturer;
        ///     <br/>
        ///     <see cref="BaselineInfo.BaselineCreditBank"/>: the initial credit bank, containing the amounts of
        ///     CAFE and CO-2 credits that are available to the manufacturer at the start of analysis;<br/>
        ///     <see cref="BaselineInfo.CreditsAndAdjustments"/>: the various credits and adjustments that the
        ///     manufacturer may use toward compliance with either NHTSA's CAFE standards or EPA's CO-2 standards.
        /// </param>
        public Manufacturer(int index, (BaselineManufacturer, BaselineCreditBank, CreditsAndAdjustments) baselineInfos)
            : this(index, baselineInfos.Item1, baselineInfos.Item2, baselineInfos.Item3) { }

        #endregion

        #region <<< Variables >>>

        private List<IVehicle> _vehicles;

        // components
        private List<IPlatform> _platforms;
        private List<IEngine> _engines;
        private List<ITransmission> _transmissions;

        // component families
        private List<IPlatformFamily> _platformFamilies;
        private List<IEngineFamily> _engineFamilies;
        private List<ITransmissionFamily> _transmissionFamilies;

        #endregion

        #region <<< Properties >>>

        public int Index { get; }

        public int Code => this.BaselineInfo.Code;
        public string Name => this.BaselineInfo.Name;
        public string ProperName => this.BaselineInfo.ProperName;

        public BaselineManufacturer BaselineInfo { get; }
        public BaselineCreditBank BaselineCreditBank { get; }
        public CreditsAndAdjustments CreditsAndAdjustments { get; }

        public AnnualValue<RCValue<double>> CAFEBank { get; private set; }
        public AnnualValue<RCAltValue<double>> CO2Bank { get; private set; }

        [field: NonSerialized]
        public IManufacturerRevision RevisionInfo { get; set; }

        public int RevisionYear { get; private set; }
        public bool HasRevisions => (this.RevisionYear != 0);

        [field: NonSerialized]
        public AnnualValue<IManufacturerRevision> Revisions { get; private set; }

        public int MinYear => this.Revisions.MinYear;
        public int MaxYear => this.Revisions.MaxYear;

        [field: NonSerialized]
        public bool IsInitializedForAnalysis { get; private set; }

        public ComplianceStatistics ComplianceStats
        {
            get => this.RevisionInfo.ComplianceStats;
            set => this.RevisionInfo.ComplianceStats = value;
        }

        public IIndustry Industry { get; private set; }

        public IReadOnlyList<IVehicle> Vehicles { get; private set; }

        public IReadOnlyList<IPlatform> Platforms { get; private set; }
        public IReadOnlyList<IEngine> Engines { get; private set; }
        public IReadOnlyList<ITransmission> Transmissions { get; private set; }

        public IReadOnlyList<IPlatformFamily> PlatformFamilies { get; private set; }
        public IReadOnlyList<IEngineFamily> EngineFamilies { get; private set; }
        public IReadOnlyList<ITransmissionFamily> TransmissionFamilies { get; private set; }

        public int VehicleCount => this._vehicles.Count;

        public int PlatformCount => this._platforms.Count;
        public int EngineCount => this._engines.Count;
        public int TransmissionCount => this._transmissions.Count;

        public int PlatformFamilyCount => this._platformFamilies.Count;
        public int EngineFamilyCount => this._engineFamilies.Count;
        public int TransmissionFamilyCount => this._transmissionFamilies.Count;

        [field: NonSerialized]
        public Dictionary<string, object> Tag { get; private set; }

        #endregion

        #region <<< Methods >>>

        /// <summary>
        /// Returns the string representation of this <see cref="Manufacturer"/> instance.
        /// </summary>
        /// <returns>The string representation of the <see cref="Manufacturer"/> instance.</returns>
        public override string ToString()
        {
            return this.BaselineInfo.ToString();
        }

        public void LinkToIndustry(IIndustry industry)
        {
            if (industry == null) {
                ThrowHelper.IndustryObjectMayNotBeNull(nameof(industry));
            }
            if (this.Industry != null) {
                ThrowHelper.ObjectAlreadyLinkedToIndustry();
            }

            this.Industry = industry;

            this._vehicles = industry.FindVehiclesByMfrName(this.Name);

            this._platforms = industry.FindPlatformsByMfrName(this.Name);
            this._engines = industry.FindEnginesByMfrName(this.Name);
            this._transmissions = industry.FindTransmissionsByMfrName(this.Name);

            this._platformFamilies = industry.FindPlatformFamiliesByMfrName(this.Name);
            this._engineFamilies = industry.FindEngineFamiliesByMfrName(this.Name);
            this._transmissionFamilies = industry.FindTransmissionFamiliesByMfrName(this.Name);

            // perform some validation
            string errors = string.Empty;
            string errHeader = $"Manufacturer (Code:{this.Code},Name:{this.Name}): ";

            CheckProductList(this._vehicles, "vehicles");
            CheckProductList(this._platforms, "platforms");
            CheckProductList(this._platformFamilies, "platform families");

            //
            // NOTE:
            // While all mfrs require at least one platform and one platform family, the same does
            // not hold true for engines and transmissions. In the case of all-electric mfrs (such
            // as Tesla), no engines, transmissions, or eng/trn families will be associated with
            // that manufacturer.
            // Later, we'll need to do a more in-depth validation to detect whether a mfr requires
            // at least one engine and/or at least one transmission, and adjust accordingly. For
            // now, comment out the lines below.
            //
            //CheckProductList(this._engines, "engines");
            //CheckProductList(this._transmissions, "transmissions");
            //CheckProductList(this._engineFamilies, "engine families");
            //CheckProductList(this._transmissionFamilies, "transmission families");

            if (errors.Length != 0) {
                throw new Exception(errors);
            }

            // create read-only wrappers
            this.Vehicles = this._vehicles.AsReadOnly();

            this.Platforms = this._platforms.AsReadOnly();
            this.Engines = this._engines.AsReadOnly();
            this.Transmissions = this._transmissions.AsReadOnly();

            this.PlatformFamilies = this._platformFamilies.AsReadOnly();
            this.EngineFamilies = this._engineFamilies.AsReadOnly();
            this.TransmissionFamilies = this._transmissionFamilies.AsReadOnly();

            // local helper function
            void CheckProductList<T>(List<T> items, string productText) where T : IOEMProduct
            {
                if (items == null || items.Count == 0) {
                    errors += $"{errHeader}does not contain any valid {productText}.\n";
                }
            }
        }

        public void InitializeForAnalysis(int minYear, int maxYear)
        {
            this.Revisions = new AnnualValue<IManufacturerRevision>(minYear, maxYear, null, -1);
            this.Tag = new Dictionary<string, object>();

            this._vehicles.ForEach(item => item.InitializeForAnalysis(minYear, maxYear));
            this._platforms.ForEach(item => item.InitializeForAnalysis(minYear, maxYear));
            this._engines.ForEach(item => item.InitializeForAnalysis(minYear, maxYear));
            this._transmissions.ForEach(item => item.InitializeForAnalysis(minYear, maxYear));

            this.IsInitializedForAnalysis = true;
        }

        public IManufacturerRevision CreateRevision()
        {
            if (this.Revisions == null) {
                throw new InvalidOperationException(
                    "The manufacturer must be initialized for analysis before manufacturer revisions may be created.");
            }
            if (this.RevisionYear == this.Revisions.MaxYear) {
                throw new InvalidOperationException(
                    "A manufacturer revision may not be created for a model year beyond the last analysis year.");
            }

            int newYear = (this.HasRevisions) ? this.RevisionYear + 1 : this.Revisions.MinYear;
            var newRevision = new ManufacturerRevision(newYear, this);

            this.Revisions[newYear] = newRevision;
            this.RevisionYear = newYear;
            this.RevisionInfo = newRevision;

            return newRevision;
        }

        public IManufacturerRevision GetRevision(int? year = null)
        {
            return (!this.HasRevisions) ? null
                 : (year == null) ? this.RevisionInfo
                 : this.Revisions[year.Value];
        }

        //----- filtering for Vehicle(s) by component code -----
        public List<IVehicle> FindVehiclesByPlatformCode(int code)
        {
            return this._vehicles.FindAll(item => item.BaselineInfo.PlatformCode == code);
        }
        public List<IVehicle> FindVehiclesByEngineCode(int code)
        {
            return this._vehicles.FindAll(item => item.BaselineInfo.EngineCode == code);
        }
        public List<IVehicle> FindVehiclesByTransmissionCode(int code)
        {
            return this._vehicles.FindAll(item => item.BaselineInfo.TransmissionCode == code);
        }

        //----- filtering for OEMProduct by product code -----
        public IVehicle FindVehicleByCode(int code)
        {
            return this._vehicles.Find(item => item.BaselineInfo.Code == code);
        }

        public IPlatform FindPlatformByCode(int code)
        {
            return this._platforms.Find(item => item.BaselineInfo.Code == code);
        }
        public IEngine FindEngineByCode(int code)
        {
            return this._engines.Find(item => item.BaselineInfo.Code == code);
        }
        public ITransmission FindTransmissionByCode(int code)
        {
            return this._transmissions.Find(item => item.BaselineInfo.Code == code);
        }

        public IPlatformFamily FindPlatformFamilyByCode(int code)
        {
            return this._platformFamilies.Find(item => item.BaselineInfo.Code == code);
        }
        public IEngineFamily FindEngineFamilyByCode(int code)
        {
            return this._engineFamilies.Find(item => item.BaselineInfo.Code == code);
        }
        public ITransmissionFamily FindTransmissionFamilyByCode(int code)
        {
            return this._transmissionFamilies.Find(item => item.BaselineInfo.Code == code);
        }

        //----- filtering for OEMComponentFamily by family name -----
        public IPlatformFamily FindPlatformFamilyByName(string familyName)
        {
            return this._platformFamilies.Find(
                item => StringHelper.Equals(item.BaselineInfo.FamilyName, familyName));
        }
        public IEngineFamily FindEngineFamilyByName(string familyName)
        {
            return this._engineFamilies.Find(
                item => StringHelper.Equals(item.BaselineInfo.FamilyName, familyName));
        }
        public ITransmissionFamily FindTransmissionFamilyByName(string familyName)
        {
            return this._transmissionFamilies.Find(
                item => StringHelper.Equals(item.BaselineInfo.FamilyName, familyName));
        }

        //----- filtering for OEMComponent(s) by family name -----
        public List<IPlatform> FindPlatformsByFamilyName(string familyName)
        {
            return this._platforms.FindAll(
                item => StringHelper.Equals(item.BaselineInfo.FamilyName, familyName));
        }
        public List<IEngine> FindEnginesByFamilyName(string familyName)
        {
            return this._engines.FindAll(
                item => StringHelper.Equals(item.BaselineInfo.FamilyName, familyName));
        }
        public List<ITransmission> FindTransmissionsByFamilyName(string familyName)
        {
            return this._transmissions.FindAll(
                item => StringHelper.Equals(item.BaselineInfo.FamilyName, familyName));
        }

        //----- statistics about mix of vehicles -----
        public RCValue<bool> CalcRegClassMix(int? year = null)
        {
            return OEMQueryableHelper.CalcRegClassMix(this._vehicles, year);
        }
        public RCAltValue<bool> CalcAltRegClassMix(int? year = null)
        {
            return OEMQueryableHelper.CalcAltRegClassMix(this._vehicles, year);
        }
        public VCValue<bool> CalcVehClassMix(int? year = null)
        {
            return OEMQueryableHelper.CalcVehClassMix(this._vehicles, year);
        }
        public FTValue<bool> CalcFuelTypeMix(int? year = null)
        {
            return OEMQueryableHelper.CalcFuelTypeMix(this._vehicles, year);
        }

        #endregion
    }
}
