#region <<< Using Directives >>>
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

using Volpe.Cafe.MarketData.Design;
using Volpe.Cafe.ModelSettings;
using Volpe.Cafe.Utils;

using TI = Volpe.Cafe.TechnologyIndexes;
#endregion

namespace Volpe.Cafe.Model
{
    /// <summary>
    /// Provides a linear, brute-force approach for generating compliance finding solutions.
    /// </summary>
    [Serializable]
    public class LinearComplianceFinder
    {

        #region <<< Constructors >>>

        /// <summary>
        /// Initializes a new instance of the <see cref="LinearComplianceFinder"/> class.
        /// </summary>
        /// <param name="compliance">The compliance model that is the parent of this compliance finder.</param>
        /// <param name="scen">The current scenario being analyzed.</param>
        public LinearComplianceFinder(ICompliance compliance, Scenario scen)
        {
            // init global variables
            this._compliance = compliance;
            this._scen = scen;
            this._techList = compliance.Settings.Technologies.TechnologyList;

            // init compliance finding variables
            this._cfCount = new int[this._techList.Count];
            InitCFArray(ref this._cfsEff);
            InitCFArray(ref this._cfsEval);

            // local helper method
            void InitCFArray(ref ComplianceFinding[] cfs)
            {
                cfs = new ComplianceFinding[this._techList.Count];
                for (int i = 0; i < cfs.Length; i++) {
                    if (!TI.IsBaselineOnly(i)) {
                        cfs[i] = new ComplianceFinding(this._compliance);
                    }
                }
            }
        }

        #endregion

        #region <<< Methods >>>

        /// <summary>
        /// Returns the next "most effective" <see cref="ComplianceFinding"/> for the scenario, model year, and
        /// manufacturer being evaluated by the model.
        /// </summary>
        /// <param name="iteration">The convergence iteration being evaluated by the model.</param>
        /// <param name="year">The model year being evaluated by the model.</param>
        /// <param name="mfr">The manufacturer being evaluated by the model.</param>
        /// <returns>
        /// The next valid <see cref="ComplianceFinding"/> using a linear, brute-force approach, or null, if the next
        /// finding is not valid or not efficient.
        /// </returns>
        public ComplianceFinding GetNextFinding(int iteration, ModelYear year, IManufacturer mfr)
        {
            if (Threading.WorkerCount == 1)
            {
                // evaluate each technology
                for (int i = 0; i < this._techList.Count; i++)
                {
                    this.ExamineTechnology(iteration, year, mfr, this._techList[i]);
                }
            }
            else
            {
                Parallel.For(0, this._techList.Count, Threading.ParallelOptions,
                    i => this.ExamineTechnology(iteration, year, mfr, this._techList[i]));
            }

            // determine the most efficient finding across all technologies
            ComplianceFinding eff = this.GetMostEfficient(this._cfsEff);

            // if the finding is valid, return it;
            // otherwise, return null to signal to the compliance model that a CF could not be generated
            return (eff == null || !eff.IsValid) ? null : eff;
        }

        void ExamineTechnology(int iteration, ModelYear year, IManufacturer mfr, TechnologyInfo tech)
        {
            if (TI.IsBaselineOnly(tech.Index)) {
                return;
            }

            // we recyle CF objects for some minor performance gains --
            // so we need to reset previous solution's data before use
            this._cfsEff[tech.Index].Reset();

            // generate product list based on the tech type --
            // the product list is a list of engines, transmissions, platforms, or vehicles
            var productList = this.GetProductList(mfr, tech);

            // generate compliance findings for each component
            // after each component, store the more efficient to _cfsEff
            foreach (IOEMProduct product in productList)
            {
                if (product is IOEMComponent c) {
                    if (c.Vehicles == null || c.Vehicles.Count == 0) {
                        continue;
                    }
                }

                // generate new finding for the technology/product combination
                //this._cfsEval[tech.Index] = new ComplianceFinding(this._compliance);
                this._cfsEval[tech.Index].ExamineFinding(
                    this._scen, iteration, year, mfr, tech, product, ref this._cfCount[tech.Index]);

                // obtain the more efficient CF from cfsEval (last evaluated) and cfsEff (most efficient thus far)
                var eff = this.GetMostEfficient(this._cfsEval[tech.Index], this._cfsEff[tech.Index]);
                if (eff == this._cfsEval[tech.Index]) {
                    this._cfsEval[tech.Index] = this._cfsEff[tech.Index];
                    this._cfsEff [tech.Index] = eff;
                }
            }
        }

        private IReadOnlyList<IOEMProduct> GetProductList(IManufacturer mfr, TechnologyInfo tech)
        {
            IReadOnlyList<IOEMProduct> componentList = null;

            if      (TI.IsEngineLevel      (tech.Index)) { componentList = mfr.Engines; }
            else if (TI.IsTransmissionLevel(tech.Index)) { componentList = mfr.Transmissions; }
            else if (TI.IsPlatformLevel    (tech.Index)) { componentList = mfr.Platforms; }
            else if (TI.IsVehicleLevel     (tech.Index)) { componentList = mfr.Vehicles; }
            else
            {
                ThrowHelper.InternalModelError();
            }

            return componentList;
        }

        //----- helper methods -----
        ComplianceFinding GetMostEfficient(ComplianceFinding cf1, ComplianceFinding cf2)
        {
            if (cf2 == null || !cf2.IsValid) {
                return cf1;
            }
            else if (cf1 == null || !cf1.IsValid) {
                return cf2;
            }
            else {
                // log loosing CF and return the more efficient one
                double cf1Eff = Math.Round(cf1.Efficiency, 4);
                double cf2Eff = Math.Round(cf2.Efficiency, 4);
                if (cf2Eff <= cf1Eff) {
                    cf1.LogFinding();
                    return cf2;
                }
                else {
                    cf2.LogFinding();
                    return cf1;
                }
            }
        }
        ComplianceFinding GetMostEfficient(ComplianceFinding[] cfs)
        {
            ComplianceFinding eff = null;
            foreach (var cf in cfs) {
                eff = this.GetMostEfficient(eff, cf);
            }
            return eff;
        }

        #endregion

        #region <<< Variables >>>

        //----- input variables -----
        ICompliance _compliance;
        Scenario _scen;
        IReadOnlyList<TechnologyInfo> _techList;

        //----- compliance finding handling variables -----
        /// <summary>
        /// Represents the total number of compliance findings that have been analyzed for each technology thus far.
        /// </summary>
        int[] _cfCount;
        /// <summary>
        /// Represents the most efficient compliance findings that have been generated for each technology thus far.
        /// If a given element is null, no finding has been generated for that technology.
        /// </summary>
        ComplianceFinding[] _cfsEff;
        /// <summary>
        /// Represents the most recent compliance findings that have been evaluated for each technology.
        /// </summary>
        ComplianceFinding[] _cfsEval;

        #endregion

    }
}
