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

using System;

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

using TechKey = Volpe.Cafe.TechKeys.TechKey128;

namespace Volpe.Cafe.MarketData
{
    /// <summary>
    /// Represents a vehicle model that encapsulates a description of its engineering characteristics as well as
    /// provides an annual historic view of its revisions.
    /// </summary>
    /// <seealso cref="BaselineVehicle"/>
    /// <seealso cref="VehicleRevision"/>
    [Serializable]
    public class Vehicle : OEMProduct<BaselineVehicle, IVehicleRevision>, IVehicle
    {
        #region <<< Constructors >>>

        /// <summary>
        /// Initializes a new instance of the <see cref="Vehicle"/> class, using the specified baseline info.
        /// </summary>
        /// <param name="baselineInfo">
        ///     A description of engineering characteristics for this <see cref="Vehicle"/>.
        /// </param>
        public Vehicle(BaselineVehicle baselineInfo) : base(baselineInfo)
        {
            this.TechUtilization = new TechnologyUtilization[TechnologyIndexes.TechnologyCount];
        }

        #endregion

        #region <<< Properties >>>

        public override int[] TechnologyList => TechnologyIndexes.AllTechnologies;

        public IPlatform Platform { get; private set; }
        public IEngine Engine { get; private set; }
        public ITransmission Transmission { get; private set; }

        public IVehicle ZevReferenceVehicle { get; private set; }

        //----- general vehicle info -----
        public string Brand => this.RevisionInfo.Brand;
        public string Model => this.RevisionInfo.Model;
        public string Nameplate => this.RevisionInfo.Nameplate;

        public int PlatformCode => this.RevisionInfo.PlatformCode;
        public int EngineCode => this.RevisionInfo.EngineCode;
        public int TransmissionCode => this.RevisionInfo.TransmissionCode;

        //----- fuel-economy values -----
        public FuelValue FuelEconomy => this.RevisionInfo.FuelEconomy;
        public FuelValue FuelShare => this.RevisionInfo.FuelShare;
        public FuelType FuelType => this.RevisionInfo.FuelType;

        //----- sales/msrp -----
        public double Sales => this.RevisionInfo.Sales;
        public double Msrp => this.RevisionInfo.Msrp;

        //----- regulatory classification -----
        public RegulatoryClass RegClass => this.RevisionInfo.RegClass;
        public SafetyClass SafetyClass => this.RevisionInfo.SafetyClass;
        public VehicleClass VehicleClass => this.RevisionInfo.VehicleClass;

        public TechnologyClass TechnologyClass => this.RevisionInfo.TechnologyClass;
        public EngineTechnologyClass EngineTechnologyClass => this.RevisionInfo.EngineTechnologyClass;

        public bool IsZevCandidate => this.RevisionInfo.IsZevCandidate;
        public int? ZEVCandidate => this.RevisionInfo.ZEVCandidate;
        public int ZevApplicationYear => this.RevisionInfo.ZevApplicationYear;
        public int ZevReferenceVehicleCode => this.RevisionInfo.ZevReferenceVehicleCode;

        public bool CARBAgreement => this.RevisionInfo.CARBAgreement;

        //----- vehicle characteristics -----
        public Origin Origin => this.RevisionInfo.Origin;

        public VehicleStyle Style => this.RevisionInfo.Style;
        public VehicleFrame Structure => this.RevisionInfo.Structure;
        public DriveType Drive => this.RevisionInfo.Drive;

        public double Footprint => this.RevisionInfo.Footprint;

        public double CurbWeightMR0 => this.RevisionInfo.CurbWeightMR0;
        public double CurbWeight => this.RevisionInfo.CurbWeight;
        public double GVWR => this.RevisionInfo.GVWR;
        public double GCWR => this.RevisionInfo.GCWR;

        public double FuelCapacity => this.RevisionInfo.FuelCapacity;

        //----- vehicle powertrain -----
        public HEVType HevType => this.RevisionInfo.HevType;

        //----- explicit interface implementation -----
        IAnnualValue<IVehicleRevision> IVehicle.Revisions => this.Revisions;

        #endregion

        #region <<< Methods >>>

        public override void LinkToIndustry(IIndustry industry)
        {
            base.LinkToIndustry(industry);

            string errors = string.Empty;
            string errHeader = $"Vehicle (Code:{this.Code},Mfr:{this.ManufacturerName}): ";

            if (this.BaselineInfo.Sales < 0) {
                errors += $"{errHeader}does not have any valid production years.";
            }

            if (this.Manufacturer == null) {
                errors += $"{errHeader}Manufacturer Name is not valid.\n";
            }

            // check for valid engine and transmission
            //  - SHEV and later should not have an engine or a transmission
            HEVType hevType = this.BaselineInfo.HevType;
            bool requireTrn = (hevType == HEVType.Conventional
                            || hevType == HEVType.MildHybrid);
            bool requireEng = (hevType == HEVType.Conventional
                            || hevType == HEVType.MildHybrid);

            if (requireTrn) {
                this.Transmission = industry.FindTransmissionByCode(this.BaselineInfo.TransmissionCode);
                if (this.Transmission == null) {
                    errors += $"{errHeader}Transmission Code is not valid.\n";
                }
            }

            if (requireEng) {
                this.Engine = industry.FindEngineByCode(this.BaselineInfo.EngineCode);
                if (this.Engine == null) {
                    errors += $"{errHeader}Engine Code is not valid.\n";
                }
            }

            // platforms are always required
            this.Platform = industry.FindPlatformByCode(this.BaselineInfo.PlatformCode);
            if (this.Platform == null) {
                errors += $"{errHeader}Platform Code is not valid.\n";
            }

            if (this.IsZevCandidate && this.ZevReferenceVehicleCode != 0) {
                this.ZevReferenceVehicle = industry.FindVehicleByCode(this.ZevReferenceVehicleCode);
                if (this.ZevReferenceVehicle == null) {
                    errors += $"{errHeader}ZEV Reference Vehicle Code is not valid.\n";
                }
            }

            // cross-validate vehicle's FE/FS with its engine's fuel type
            errors += this.CrossValidateBaselineFuelStates(errHeader, requireEng);

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

        public IOEMComponentRevision GetPlatformRevision(int year, bool closestMatch = false)
        {
            var vehRevision = this.GetRevision(year);
            int code = vehRevision.PlatformCode;
            var c = this.Manufacturer.FindPlatformByCode(code);
            return this.GetComponentRevision(c, year, vehRevision.TechKey, closestMatch);
        }

        public IOEMComponentRevision GetEngineRevision(int year, bool closestMatch = false)
        {
            var vehRevision = this.GetRevision(year);
            int code = vehRevision.EngineCode;
            var c = this.Manufacturer.FindEngineByCode(code);
            return this.GetComponentRevision(c, year, vehRevision.TechKey, closestMatch);
        }

        public IOEMComponentRevision GetTransmissionRevision(int year, bool closestMatch = false)
        {
            var vehRevision = this.GetRevision(year);
            int code = vehRevision.TransmissionCode;
            var c = this.Manufacturer.FindTransmissionByCode(code);
            return this.GetComponentRevision(c, year, vehRevision.TechKey, closestMatch);
        }

        // Helper method for Get*Revision(int year) methods
        private IOEMComponentRevision GetComponentRevision(
            IOEMComponent c, int maxYear, TechKey vehTechKey, bool closestMatch)
        {
            if (c != null) {
                // filter veh-key for component techs only
                vehTechKey &= TechKey.BuildKey(c.TechnologyList);
                maxYear = Math.Min(maxYear, c.Revisions.MaxYear);

                if (closestMatch) {
                    for (int year = maxYear; year >= c.Revisions.MinYear; year--) {
                        if (vehTechKey >= c.Revisions[year].TechKey) {
                            return c.Revisions[year];
                        }
                    }
                }
                else {
                    for (int year = maxYear; year >= c.Revisions.MinYear; year--) {
                        if (vehTechKey == c.Revisions[year].TechKey) {
                            return c.Revisions[year];
                        }
                    }
                }
            }
            return null;
        }

        /// <summary>
        /// Creates and returns a new vehicle revision for the specified <paramref name="newYear"/>.
        /// <br/>
        /// If the vehicle does not have any revisions, the new revision will be derived from the vehicle's baseline
        /// information. Otherwise, the new revision will be derived from the vehicle's most recent revision.
        /// <br/>
        /// This method returns a new revision object of type <see cref="VehicleRevision"/>.
        /// </summary>
        protected override IVehicleRevision CreateNewRevision(int newYear)
        {
            var currentRevision = this.RevisionInfo;
            var newRevision = (this.HasRevisions)
                ? new VehicleRevision(newYear, currentRevision)
                : new VehicleRevision(newYear, this);

            return newRevision;
        }

        // Helper validation method(s) for LinkToIndustry
        private string CrossValidateBaselineFuelStates(string errHeader, bool requireEng)
        {
            string errors = string.Empty;
            FuelType engFuel = (this.Engine == null) ? FuelType.None : this.Engine.BaselineInfo.Fuel;
            FuelValue vehFE = this.BaselineInfo.FuelEconomy;
            FuelValue vehFS = this.BaselineInfo.FuelShare;

            if (engFuel != FuelType.None) {
                string hdr = $"{errHeader}Engine is listed as using {engFuel} Fuel, but Vehicle {engFuel}";

                if (vehFS[engFuel] <= 0) {
                    errors += $"{hdr} Fuel Share is not specified or not valid.\n";
                }
                if (vehFE[engFuel] <= 0) {
                    errors += $"{hdr} Fuel Economy is not specified or not valid.\n";
                }
            }
            else if (requireEng) {
                var engOnlyFuels = new FuelType[] { FuelType.Gasoline, FuelType.Diesel, FuelType.CNG };
                foreach (FuelType fuel in engOnlyFuels) {
                    string ftr = $"but the Engine is not listed as using {fuel} Fuel.";

                    if (vehFS[fuel] > 0) {
                        errors += $"{errHeader}Vehicle specifies a {fuel} Fuel Share, {ftr}\n";
                    }
                    if (vehFE[fuel] > 0) {
                        errors += $"{errHeader}Vehicle specifies a {fuel} Fuel Economy, {ftr}\n";
                    }
                }
            }

            return errors;
        }

        // NOTE: The ClearEngine() and ClearTransmission() methods are temporary and may be removed later.
        public bool ClearEngine()
        {
            if (this.Engine != null) {
                this.Engine = null;
                return true;
            }
            return false;
        }
        public bool ClearTransmission()
        {
            if (this.Transmission != null) {
                this.Transmission = null;
                return true;
            }
            return false;
        }

        #endregion
    }
}
