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

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

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

namespace Volpe.Cafe.MarketData
{
    /// <summary>
    /// Represents the overall industry. This object contains information about all manufacturers, vehicles,
    /// components, and component families available for modeling.
    /// </summary>
    /// <seealso cref="Manufacturer"/>
    /// <seealso cref="Vehicle"/>
    /// <seealso cref="Platform"/>
    /// <seealso cref="Engine"/>
    /// <seealso cref="Transmission"/>
    /// <seealso cref="PlatformFamily"/>
    /// <seealso cref="EngineFamily"/>
    /// <seealso cref="TransmissionFamily"/>
    [Serializable]
    public class Industry : IIndustry
    {
        #region <<< Constructors >>>

        /// <summary>
        /// Initializes a new instance of the <see cref="Industry"/> class.
        /// </summary>
        public Industry(
            List<(BaselineManufacturer, BaselineCreditBank, CreditsAndAdjustments)> bMfrs,
            List<BaselineVehicle> bVehs,
            List<BaselinePlatform> bPlts,
            List<BaselineEngine> bEngs,
            List<BaselineTransmission> bTrns,
            List<BaselineComponentFamily> bPltFams,
            List<BaselineComponentFamily> bEngFams,
            List<BaselineComponentFamily> bTrnFams)
        {
            // before we do any initialization, sort the baseline-info lists by mfr and code
            bMfrs = bMfrs.OrderBy(item => item.Item1.Code).ToList();
            bVehs = bVehs.OrderBy(item => item.ManufacturerName).ThenBy(item => item.Code).ToList();

            bPlts = bPlts.OrderBy(item => item.ManufacturerName).ThenBy(item => item.Code).ToList();
            bEngs = bEngs.OrderBy(item => item.ManufacturerName).ThenBy(item => item.Code).ToList();
            bTrns = bTrns.OrderBy(item => item.ManufacturerName).ThenBy(item => item.Code).ToList();

            bPltFams = bPltFams.OrderBy(item => item.ManufacturerName).ThenBy(item => item.Code).ToList();
            bEngFams = bEngFams.OrderBy(item => item.ManufacturerName).ThenBy(item => item.Code).ToList();
            bTrnFams = bTrnFams.OrderBy(item => item.ManufacturerName).ThenBy(item => item.Code).ToList();

            // we need to initialize all products/components based on their baseline info first
            // we cannot link to industry right away, b/c some objects do not yet exist
            int mfrIndex = 0;
            this._manufacturers = bMfrs.ConvertAll(bInfo => new Manufacturer(mfrIndex++, bInfo));
            this._vehicles = bVehs.ConvertAll(bInfo => new Vehicle(bInfo));

            this._platforms = bPlts.ConvertAll(bInfo => new Platform(bInfo));
            this._engines = bEngs.ConvertAll(bInfo => new Engine(bInfo));
            this._transmissions = bTrns.ConvertAll(bInfo => new Transmission(bInfo));

            this._platformFamilies = bPltFams.ConvertAll(bInfo => new PlatformFamily(bInfo));
            this._engineFamilies = bEngFams.ConvertAll(bInfo => new EngineFamily(bInfo));
            this._transmissionFamilies = bTrnFams.ConvertAll(bInfo => new TransmissionFamily(bInfo));

            // we can now link all items to industry, initializing any references (cross-linking)
            // to other products/components
            this._manufacturers.ForEach(item => item.LinkToIndustry(this));
            this._vehicles.ForEach(item => item.LinkToIndustry(this));

            this._platforms.ForEach(item => item.LinkToIndustry(this));
            this._engines.ForEach(item => item.LinkToIndustry(this));
            this._transmissions.ForEach(item => item.LinkToIndustry(this));

            this._platformFamilies.ForEach(item => item.LinkToIndustry(this));
            this._engineFamilies.ForEach(item => item.LinkToIndustry(this));
            this._transmissionFamilies.ForEach(item => item.LinkToIndustry(this));

            // lastly, since we don't want anyone (other than us) to add/remove items to our lists,
            // create read-only wrappers
            this.Manufacturers = this._manufacturers.AsReadOnly();
            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();
        }

        #endregion

        #region <<< Variables >>>

        private readonly List<Manufacturer> _manufacturers = new List<Manufacturer>();
        private readonly List<Vehicle> _vehicles = new List<Vehicle>();

        // components
        private readonly List<Platform> _platforms = new List<Platform>();
        private readonly List<Engine> _engines = new List<Engine>();
        private readonly List<Transmission> _transmissions = new List<Transmission>();

        // component families
        private readonly List<PlatformFamily> _platformFamilies = new List<PlatformFamily>();
        private readonly List<EngineFamily> _engineFamilies = new List<EngineFamily>();
        private readonly List<TransmissionFamily> _transmissionFamilies = new List<TransmissionFamily>();

        #endregion

        #region <<< Properties >>>

        public IReadOnlyList<IManufacturer> Manufacturers { get; }

        public IReadOnlyList<IVehicle> Vehicles { get; }

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

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

        public int ManufacturerCount => this._manufacturers.Count;

        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 AnnualValue<double> MdpcStandard { get; private set; }

        [field: NonSerialized]
        public int MinYear { get; private set; }
        [field: NonSerialized]
        public int MaxYear { get; private set; }

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

        #endregion

        #region <<< Methods >>>

        /// <summary>
        /// Returns the string representation of this <see cref="Industry"/> instance.
        /// </summary>
        /// <returns>The string representation of the <see cref="Industry"/> instance.</returns>
        public override string ToString()
        {
            return $"{{{this.ManufacturerCount} mfrs, {this.VehicleCount} vehicles, " +
                   $"{this.PlatformCount} platforms ({this.PlatformFamilyCount} families), " +
                   $"{this.EngineCount} engines ({this.EngineFamilyCount} families), " +
                   $"{this.TransmissionCount} transmissions ({this.TransmissionFamilyCount} families)}}";
        }

        public void WriteSummaryLog(TextWriter writer)
        {
            if (writer == null) {
                return;
            }

            writer.WriteLine("---------- Modeling Data ----------");
            writer.WriteLine("--- Valid Manufacturers ---");
            for (int i = 0; i < this._manufacturers.Count; i++) {
                var mfr = this._manufacturers[i];
                writer.WriteLine($"  {i}. Code={mfr.Code}, Name={mfr.Name}");
                writer.WriteLine($"      Vehicles={mfr.VehicleCount} ({GetVehCountByClassString(mfr.Vehicles)})");
                writer.WriteLine($"      Platforms={mfr.PlatformCount} ({mfr.PlatformFamilyCount} families)");
                writer.WriteLine($"      Engines={mfr.EngineCount} ({mfr.EngineFamilyCount} families)");
                writer.WriteLine($"      Transmissions={mfr.TransmissionCount} ({mfr.TransmissionFamilyCount} families)");
            }
            writer.WriteLine();

            writer.WriteLine("--- Industry Summary ---");
            writer.WriteLine($"  Total Manufacturers Analyzed: {this.ManufacturerCount}");
            writer.WriteLine($"  Total Vehicles Analyzed: {this.VehicleCount} ({GetVehCountByClassString(this._vehicles)})");
            writer.WriteLine($"  Total Platforms Analyzed: {this.PlatformCount} ({this.PlatformFamilyCount} families)");
            writer.WriteLine($"  Total Engines Analyzed: {this.EngineCount} ({this.EngineFamilyCount} families)");
            writer.WriteLine($"  Total Transmissions Analyzed: {this.TransmissionCount} ({this.TransmissionFamilyCount} families)");
            writer.WriteLine();

            // local helper function
            string GetVehCountByClassString(IEnumerable<IVehicle> vehs)
            {
                string s = string.Empty;
                for (int j = 0; j < VehicleClasses.AltList.Length; j++) {
                    VehicleClass vehClass = VehicleClasses.AltList[j];
                    int vehCountByClass = vehs.Count(veh => vehClass.Contains(veh.BaselineInfo.VehicleClass));
                    s += $"{(j == 0 ? string.Empty : ", ")}{VehicleClasses.AltNames[j]}={vehCountByClass}";
                }
                return s;
            }
        }

        public IIndustry CreateNew()
        {
            var bMfrs = this._manufacturers.ConvertAll(
                item => (item.BaselineInfo, item.BaselineCreditBank, item.CreditsAndAdjustments));

            var bVehs = this._vehicles.ConvertAll(item => item.BaselineInfo);

            var bPlts = this._platforms.ConvertAll(item => item.BaselineInfo);
            var bEngs = this._engines.ConvertAll(item => item.BaselineInfo);
            var bTrns = this._transmissions.ConvertAll(item => item.BaselineInfo);

            var bPltFams = this._platformFamilies.ConvertAll(item => item.BaselineInfo);
            var bEngFams = this._engineFamilies.ConvertAll(item => item.BaselineInfo);
            var bTrnFams = this._transmissionFamilies.ConvertAll(item => item.BaselineInfo);

            return new Industry(bMfrs, bVehs, bPlts, bEngs, bTrns, bPltFams, bEngFams, bTrnFams);
        }

        public void InitializeForAnalysis(int minYear, int maxYear)
        {
            this.MinYear = minYear;
            this.MaxYear = maxYear;
            this.MdpcStandard = new AnnualValue<double>(minYear, maxYear, null, -1);
            this._manufacturers.ForEach(item => item.InitializeForAnalysis(minYear, maxYear));

            this.IsInitializedForAnalysis = true;
        }

        public IManufacturer FindManufacturer(string mfrName)
        {
            return this._manufacturers.Find(mfr => StringHelper.Equals(mfrName, mfr.Name));
        }

        //----- filtering for OEMProduct(s) by mfr-name -----
        public List<IVehicle> FindVehiclesByMfrName(string mfrName)
        {
            return this._vehicles
                        .FindAll(item => StringHelper.Equals(item.BaselineInfo.ManufacturerName, mfrName))
                        .ConvertAll(item => (IVehicle)item);
        }

        public List<IPlatform> FindPlatformsByMfrName(string mfrName)
        {
            return this._platforms
                        .FindAll(item => StringHelper.Equals(item.BaselineInfo.ManufacturerName, mfrName))
                        .ConvertAll(item => (IPlatform)item);
        }
        public List<IEngine> FindEnginesByMfrName(string mfrName)
        {
            return this._engines
                        .FindAll(item => StringHelper.Equals(item.BaselineInfo.ManufacturerName, mfrName))
                        .ConvertAll(item => (IEngine)item);
        }
        public List<ITransmission> FindTransmissionsByMfrName(string mfrName)
        {
            return this._transmissions
                        .FindAll(item => StringHelper.Equals(item.BaselineInfo.ManufacturerName, mfrName))
                        .ConvertAll(item => (ITransmission)item);
        }

        public List<IPlatformFamily> FindPlatformFamiliesByMfrName(string mfrName)
        {
            return this._platformFamilies
                        .FindAll(item => StringHelper.Equals(item.BaselineInfo.ManufacturerName, mfrName))
                        .ConvertAll(item => (IPlatformFamily)item);
        }
        public List<IEngineFamily> FindEngineFamiliesByMfrName(string mfrName)
        {
            return this._engineFamilies
                        .FindAll(item => StringHelper.Equals(item.BaselineInfo.ManufacturerName, mfrName))
                        .ConvertAll(item => (IEngineFamily)item);
        }
        public List<ITransmissionFamily> FindTransmissionFamiliesByMfrName(string mfrName)
        {
            return this._transmissionFamilies
                        .FindAll(item => StringHelper.Equals(item.BaselineInfo.ManufacturerName, mfrName))
                        .ConvertAll(item => (ITransmissionFamily)item);
        }

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

        //----- 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))
                                  .ConvertAll(item => (IPlatform)item);
        }
        public List<IEngine> FindEnginesByFamilyName(string familyName)
        {
            return this._engines.FindAll(item => StringHelper.Equals(item.BaselineInfo.FamilyName, familyName))
                                .ConvertAll(item => (IEngine)item);
        }
        public List<ITransmission> FindTransmissionsByFamilyName(string familyName)
        {
            return this._transmissions.FindAll(item => StringHelper.Equals(item.BaselineInfo.FamilyName, familyName))
                                      .ConvertAll(item => (ITransmission)item);
        }

        //----- 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
    }
}
