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

using System;
using System.Collections.Generic;

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

namespace Volpe.Cafe.MarketData
{
    /// <summary>
    /// Represents any component family produced by an original equipment manufacturer (OEM), that encapsulates a
    /// description of its engineering characteristics as well as provides an annual historic view of its revisions.
    /// </summary>
    /// <typeparam name="T">
    ///     The type of the <see cref="IOEMComponent"/> associated with this <see cref="OEMComponentFamily{T}"/>.
    /// </typeparam>
    /// <remarks>
    /// OEM component families provide extensions to the <see cref="OEMProduct{TB,TR}"/>s, allowing multiple components
    /// (e.g, platforms, engines, or transmissions) to be "grouped" for the purpose of technology evaluation and
    /// inheriting. Component families include platform families, engine families, and transmission families; however,
    /// they <b>may not</b> be used directly by a vehicle.
    /// </remarks>
    /// <seealso cref="BaselineComponentFamily"/>
    /// <seealso cref="ComponentFamilyRevision"/>
    /// <seealso cref="OEMComponent{T}"/>
    [Serializable]
    public abstract class OEMComponentFamily<T>
        : OEMProduct<BaselineComponentFamily, IOEMComponentFamilyRevision>, IOEMComponentFamily
        where T : IOEMComponent
    {
        #region <<< Constructors >>>

        /// <summary>
        /// Initializes a new instance of the <see cref="OEMComponentFamily{T}"/> class, using the specified baseline
        /// info.
        /// </summary>
        /// <param name="baselineInfo">
        ///     A description of baseline (or initial) configuration for this <see cref="OEMComponentFamily{T}"/>.
        /// </param>
        protected OEMComponentFamily(BaselineComponentFamily baselineInfo) : base(baselineInfo)
        {
            this.TechUtilization = new TechnologyUtilization[TechnologyIndexes.TechnologyCount];
        }

        #endregion

        #region <<< Variables >>>

        /// <summary>
        /// Specifies a list of <see cref="IOEMComponent"/>s that belong to this component family.
        /// </summary>
        protected List<T> _components;

        /// <summary>
        /// Specifies a list of <see cref="IVehicle"/>s that use any of the <see cref="_components"/> that belong to
        /// this component family.
        /// </summary>
        protected List<IVehicle> _vehicles;

        private T _leaderComponent = default;

        #endregion

        #region <<< Properties >>>

        /// <inheritdoc cref="IOEMComponentFamily.Components"/>
        public IReadOnlyList<T> Components { get; private set; }

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

        public string FamilyName => this.BaselineInfo.FamilyName;

        //----- explicit IOEMComponentFamily implementation -----
        IReadOnlyList<IOEMComponent> IOEMComponentFamily.Components => (IReadOnlyList<IOEMComponent>)this.Components;

        #endregion

        #region <<< Methods >>>

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

            this._components = this.FindComponentFamilyComponents();
            this._vehicles = this.FindComponentFamilyVehicles();

            this.ValidateComponentFamily();

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

        public void RemoveVehicle(IVehicle veh, int analysisYear, int applicationYear)
        {
            if (this._vehicles.Remove(veh)) {
                this._leaderComponent = default;
            }
        }

        /// <inheritdoc cref="IOEMComponentFamily.IsLeaderComponent"/>
        public bool IsLeaderComponent(T component)
        {
            return (component != null && component.Equals(this.GetLeaderComponent()));
        }

        /// <inheritdoc cref="IOEMComponentFamily.GetLeaderComponent"/>
        public T GetLeaderComponent()
        {
            if (this._components == null || this._components.Count == 0) {
                return default;
            }
            if (this._leaderComponent == null) {
                this._leaderComponent = LeaderSelection.SelectLeaderComponent(this.Components);
            }
            return this._leaderComponent;
        }

        /// <summary>
        /// Revisions are not supported for component families.
        /// </summary>
        public override IOEMComponentFamilyRevision CreateRevision()
        {
            throw this.ComponentFamilyRevisionsNotSupported();
        }

        /// <summary>
        /// Revisions are not supported for component families.<br/>
        /// This method always returns a wrapper object (of type <see cref="ComponentFamilyRevision"/>) around the
        /// component family's <see cref="OEMProduct{TB,TR}.BaselineInfo"/> object.
        /// </summary>
        protected override IOEMComponentFamilyRevision CreateNewRevision(int newYear)
        {
            return new ComponentFamilyRevision(newYear, this);
        }

        /// <summary>
        /// Searches the list of available components within the industry for all components that are identified as
        /// using this family's <see cref="FamilyName"/>.
        /// </summary>
        /// <returns>
        /// A list of components that are identified as using this family's <see cref="FamilyName"/>, or an empty list,
        /// if no components could be found.
        /// </returns>
        protected abstract List<T> FindComponentFamilyComponents();

        /// <summary>
        /// Searches the list of available vehicles within the industry for all elements that are identified as using
        /// the component codes of any components associated with this family.
        /// </summary>
        /// <returns>
        /// A list of vehicles that are identified as using the component codes of any components associated with this
        /// family, or an empty list, if no vehicles could be found.
        /// </returns>
        protected abstract List<IVehicle> FindComponentFamilyVehicles();

        /// <summary>
        /// Performs some basic validation on a component family.
        /// </summary>
        /// <remarks>
        /// Subclasses that override the <see cref="LinkToIndustry"/> method should also call the
        /// <see cref="ValidateComponentFamily"/> method after setting all required references, or implement their own
        /// validation.
        /// </remarks>
        protected virtual void ValidateComponentFamily()
        {
            // perform some validation
            string productText = this.GetType().Name;
            string errors = string.Empty;
            string errHeader = $"{productText} (Code:{this.Code},Name:{this.ManufacturerName}): ";

            if (this.Manufacturer == null) {
                errors += $"{errHeader}Manufacturer Name is not valid.\n";
            }
            if (this._components == null || this._components.Count == 0) {
                errors += $"{errHeader}does not contain any valid components.\n";
            }
            //if (family._vehicles == null || family._vehicles.Count == 0) {
            //    errors += $"{errHeader}does not contain any valid vehicles.\n";
            //}

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

        private NotSupportedException ComponentFamilyRevisionsNotSupported()
        {
            return new NotSupportedException("Component families do not support revisions.");
        }

        //----- explicit IOEMComponentFamily implementation -----
        bool IOEMComponentFamily.IsLeaderComponent(IOEMComponent component)
        {
            return (component is T c) && this.IsLeaderComponent(c);
        }
        IOEMComponent IOEMComponentFamily.GetLeaderComponent()
        {
            return this.GetLeaderComponent();
        }

        #endregion
    }
}
