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

using System;
using System.Runtime.Serialization;

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

using TechKey = Volpe.Cafe.TechKeys.TechKey128;

namespace Volpe.Cafe.MarketData
{
    /// <summary>
    /// Represents any product 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="TBaselineInfo">
    ///     The type of the <see cref="BaselineInfo"/> object associated with this <see cref="OEMProduct{TB,TR}"/>.
    /// </typeparam>
    /// <typeparam name="TRevisionInfo">
    ///     The type of the <see cref="RevisionInfo"/> object associated with this <see cref="OEMProduct{TB,TR}"/>.
    /// </typeparam>
    /// <remarks>
    /// OEM products include vehicles, vehicle platforms, engines, and transmissions, and are capable of applying
    /// additional technology as well as reporting on their current technology applicability states and accumulated
    /// technology costs. Additionally, OEM products include component grouping entities, such as component families.
    /// </remarks>
    /// <seealso cref="IOEMProductInfo"/>
    /// <seealso cref="ProductRevision"/>
    [Serializable]
    public abstract class OEMProduct<TBaselineInfo, TRevisionInfo> : IOEMProduct, IDeserializationCallback
        where TBaselineInfo : class, IOEMProductInfo
        where TRevisionInfo : class, IOEMProductRevision
    {
        #region <<< Constructors >>>

        /// <summary>
        /// Initializes a new instance of the <see cref="OEMProduct{TB,TR}"/> class, using the specified baseline info.
        /// </summary>
        /// <param name="baselineInfo">
        ///     A description of baseline (or initial) configuration for this <see cref="OEMProduct{TB,TR}"/>.
        /// </param>
        protected OEMProduct(TBaselineInfo baselineInfo)
        {
            this.BaselineInfo = baselineInfo;
            this.RevisionInfo = this.CreateNewRevision(0);
        }

        #endregion

        #region <<< Properties >>>

        public abstract int[] TechnologyList { get; }

        /// <inheritdoc cref="IOEMProduct.BaselineInfo"/>
        public TBaselineInfo BaselineInfo { get; }

        /// <inheritdoc cref="IOEMProduct.RevisionInfo"/>
        [field: NonSerialized]
        public TRevisionInfo RevisionInfo { get; protected set; }

        public int RevisionYear => this.RevisionInfo.RevisionYear;
        public bool HasRevisions => (this.RevisionInfo?.RevisionYear > 0);

        /// <inheritdoc cref="IOEMProduct.Revisions"/>
        [field: NonSerialized]
        public AnnualValue<TRevisionInfo> Revisions { get; protected set; }

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

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

        public IIndustry Industry { get; private set; }
        public IManufacturer Manufacturer { get; private set; }

        public TechnologyUtilization[] TechUtilization { get; protected set; }

        //----- general product info -----
        public int Code => this.BaselineInfo.Code;
        public string ManufacturerName => this.BaselineInfo.ManufacturerName;

        public ModelYearKey RefreshYears => this.BaselineInfo.RefreshYears;
        public ModelYearKey RedesignYears => this.BaselineInfo.RedesignYears;

        //----- technology utilization -----
        public TechKey TechKey => this.RevisionInfo.TechKey;

        //----- explicit interface implementation -----
        IOEMProductInfo IOEMProduct.BaselineInfo => this.BaselineInfo;
        IOEMProductRevision IOEMProduct.RevisionInfo => this.RevisionInfo;
        IAnnualValue<IOEMProductRevision> IOEMProduct.Revisions => this.Revisions;

        #endregion

        #region <<< Methods >>>

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

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

            this.Industry = industry;
            this.Manufacturer = industry.FindManufacturer(this.ManufacturerName);
        }

        public virtual void InitializeForAnalysis(int minYear, int maxYear)
        {
            this.Revisions = new AnnualValue<TRevisionInfo>(minYear, maxYear, null, -1);
            this.IsInitializedForAnalysis = true;
        }

        /// <inheritdoc cref="IOEMProduct.CreateRevision"/>
        public virtual TRevisionInfo CreateRevision()
        {
            if (this.Revisions == null) {
                throw new InvalidOperationException(
                    "The product must be initialized for analysis before product revisions may be created.");
            }
            if (this.RevisionYear == this.Revisions.MaxYear) {
                throw new InvalidOperationException(
                    "A product 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 = this.CreateNewRevision(newYear);

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

            return newRevision;
        }

        /// <inheritdoc cref="IOEMProduct.GetRevision"/>
        public virtual TRevisionInfo GetRevision(int? year = null)
        {
            return (!this.HasRevisions) ? null
                 : (year == null) ? this.RevisionInfo
                 : this.Revisions[year.Value];
        }

        /// <summary>
        /// Creates and returns a new product revision for the specified <paramref name="newYear"/>.
        /// <br/>
        /// If the product does not have any revisions, the new revision will be derived from the product's baseline
        /// information. Otherwise, the new revision will be derived from the product's most recent revision.
        /// </summary>
        protected abstract TRevisionInfo CreateNewRevision(int newYear);

        public bool IsAtRedesign(int year)
        {
            return this.RedesignYears[year];
        }

        public bool IsAtRefresh(int year)
        {
            return this.RefreshYears[year];
        }

        //----- explicit interface implementation -----
        IOEMProductRevision IOEMProduct.CreateRevision()
        {
            return this.CreateRevision();
        }

        IOEMProductRevision IOEMProduct.GetRevision(int? year)
        {
            return this.GetRevision(year);
        }

        void IDeserializationCallback.OnDeserialization(object sender)
        {
            //------------------------------------------------------------------------------------
            // To save a bit on file size & load speed, we don't want to serialize RevisionInfo
            // object, which is just a copy of the BaselineInfo during market-data loading, or is
            // a transient object (wrt the model year being evaluated) assigned during actual
            // analysis. In either case, saving it is unecessary. However, when our class is
            // deserialized, we still want to initialize it to be a copy of the baseline info so
            // that we (and extending classes) can use it to route other properties through it.
            //------------------------------------------------------------------------------------

            this.RevisionInfo = this.CreateNewRevision(0);
        }

        #endregion
    }
}
