using System;
using System.Collections;
using System.Collections.Generic;
using Volpe.Cafe.ModelSettings;

namespace Volpe.Cafe.Reporting
{
    /// <summary>
    /// Defines an enumerable object used for generating rows of data to report on for a range of years.
    /// </summary>
    [Serializable]
    public abstract class ReportDataGenerator : IEnumerable<object[]>
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="ReportDataGenerator"/> class, for the specified scenario and
        /// reporting data.
        /// </summary>
        /// <param name="scen">The compliance scenario being analyzed.</param>
        /// <param name="data">The modeling and effects data to report on.</param>
        /// <param name="settings">The modeling settings used for analysis.</param>
        public ReportDataGenerator(Scenario scen, ReportingData data, ModelingSettings settings)
        {
            this.Scenario = scen;
            this.Data = data;
            this.Settings = settings;
            this.Flags = DataGeneratorFlags.None;
        }

        // internal buffer to keep track of values for each row/record
        private List<object> _valuesBuffer = new List<object>();

        /// <summary>
        /// Gets or sets the additional flags associated with this <see cref="ReportDataGenerator"/>.
        /// </summary>
        public DataGeneratorFlags Flags { get; set; }

        /// <summary>Gets the compliance scenario being analyzed.</summary>
        protected Scenario Scenario { get; private set; }
        /// <summary>Gets the modeling and effects data to report on.</summary>
        protected ReportingData Data { get; private set; }
        /// <summary>Gets the modeling settings used for analysis.</summary>
        protected ModelingSettings Settings { get; private set; }

        /// <summary>
        /// Gets whether to generate subtotals across all years.
        /// </summary>
        protected virtual bool WriteYearTotals => false;

        /// <summary>
        /// Returns an enumerator that iterates through each row of data to report on for a range of years.
        /// </summary>
        public virtual IEnumerator<object[]> GetEnumerator()
        {
            var yearRange = this.CreateYearRange();

            foreach (int? year in yearRange) {
                var yearEnumerator = this.GetYearEnumerator(year);

                while (yearEnumerator.MoveNext()) {
                    yield return yearEnumerator.Current;
                }
            }
        }

        /// <summary>
        /// Returns an enumerator that iterates through each row of data to report on for the specified
        /// <paramref name="year"/>.
        /// </summary>
        /// <param name="year">
        ///     The year (model year or calendar year) for which to generate report data. If this value is <c>null</c>,
        ///     the subtotals across all years should be generated instead.
        /// </param>
        protected abstract IEnumerator<object[]> GetYearEnumerator(int? year);

        /// <summary>
        /// Generates a range of years to iterate for report generating, optionally including an extra step for year
        /// subtotals.
        /// </summary>
        /// <returns>A <see cref="Range"/> object to use for iterating a range of years.</returns>
        /// <remarks>
        /// By default, this method creates a <see cref="Range"/> object using the <see cref="ReportingData.MinYear"/>
        /// and <see cref="ReportingData.MaxYear"/> values. Override this method to use a different range of years.
        /// </remarks>
        protected virtual Range CreateYearRange()
        {
            return new Range(this.Data.MinYear, this.Data.MaxYear, this.WriteYearTotals);
        }

        /// <summary>
        /// Clears the internal values buffer.<br/>
        /// This method should be called prior to starting to append values for a new row/record.
        /// </summary>
        protected void ClearBuffer()
        {
            this._valuesBuffer.Clear();
        }

        /// <summary>
        /// Appends the specified <paramref name="value"/> to the buffer.
        /// </summary>
        protected internal void AppendBuffer(object value)
        {
            this._valuesBuffer.Add(value);
        }

        /// <summary>
        /// Appends the specified <paramref name="value"/> to the buffer, the specified <paramref name="count"/> of
        /// times.
        /// </summary>
        protected internal void AppendBuffer(object value, int count)
        {
            for (int i = 0; i < count; i++) {
                this.AppendBuffer(value);
            }
        }

        /// <summary>
        /// Appends an empty value to the buffer, the specified <paramref name="count"/> of times.
        /// </summary>
        protected internal void AppendEmpty(int count)
        {
            for (int i = 0; i < count; i++) {
                this._valuesBuffer.Add(string.Empty);
            }
        }

        /// <summary>
        /// Returns a copy of the internal values buffer for the current row/record.
        /// </summary>
        protected object[] GetBuffer()
        {
            return this._valuesBuffer.ToArray();
        }

        //----- explicit interface implementation -----
        IEnumerator IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

    }
}
