using System;
using System.Collections.Generic;
using System.Linq;
using DsvThrowHelper = Volpe.Cafe.IO.DSV.ThrowHelper;

namespace Volpe.Cafe.Logging
{
    /// <summary>
    /// Provides support for logging compliance findings to a text file. The buffered writer supports logging from
    /// multiple concurrent threads by, first, caching writes to an indexed buffer, then, flushing the buffers
    /// sequentially to the underlying stream whenever the <see cref="Flush"/> method is called or when the file is
    /// closed. This ensures that the log file always produces entries in a predictable order.
    /// </summary>
    [Serializable]
    public sealed class BufferedComplianceWriter : ComplianceWriter
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="BufferedComplianceWriter"/> class for the specified file,
        /// using the default encoding and buffer size. If the file exists, it can be either overwritten or appended
        /// to. If the file does not exist, this constructor creates a new file.
        /// </summary>
        /// <param name="path">The complete file path to write to.</param>
        /// <param name="bufferCount">The number of indexed buffers to create for logging.</param>
        /// <param name="append">
        ///     <inheritdoc cref="ComplianceWriter(string, bool)" path="/param[@name='append']"/>
        /// </param>
        /// <exception cref="ArgumentOutOfRangeException">buffer count less than one.</exception>
        /// <inheritdoc cref="ComplianceWriter(string, bool)" path="/exception"/>
        public BufferedComplianceWriter(string path, int bufferCount, bool append)
            : base(path, CheckBufferCount(bufferCount, append))
        {
            this._buffers = Enumerable.Range(0, bufferCount)
                                      .Select(_ => new List<string>(8))
                                      .ToArray();
        }

        // indexed buffers to populate before flushing to disk
        private List<string>[] _buffers;

        /// <summary>
        /// Gets the number of concurrent indexed buffers available to the writer.
        /// </summary>
        public int BufferCount => (this._buffers == null) ? 0 : this._buffers.Length;

        /// <summary>
        /// Writes the specified array of values to a default buffer (at index zero).
        /// </summary>
        /// <param name="values">An array of values to write to the buffer.</param>
        /// <inheritdoc cref="WriteValues(int, object[])" path="/exception"/>
        public override void WriteValues(params object[] values)
        {
            this.WriteValues(0, values);
        }

        /// <summary>
        /// Writes the specified array of values to a buffer associated with the given index.
        /// </summary>
        /// <param name="index">The index of the buffer to write to.</param>
        /// <param name="values">An array of values to write to the buffer.</param>
        /// <exception cref="ObjectDisposedException">The log file is closed.</exception>
        /// <exception cref="ArgumentNullException">values array is null.</exception>
        /// <exception cref="ArgumentException">
        ///     values array is empty.
        ///         -or-
        ///     values and headers length differs.
        /// </exception>
        public void WriteValues(int index, params object[] values)
        {
            DsvThrowHelper.ThrowIfDisposed(this);
            DsvThrowHelper.ThrowIfValuesNotValid(values, this.Headers.Length - 1);

            string value = string.Join(this.DelimiterString, values);
            var buffer = this._buffers[index];
            buffer.Add(value);
        }

        /// <inheritdoc/>
        public override void Flush()
        {
            DsvThrowHelper.ThrowIfDisposed(this);

            foreach (List<string> buffer in this._buffers) {
                if (buffer.Count == 0) {
                    continue;
                }

                foreach (string line in buffer) {
                    this.Stream.Write(++this.LineCount);
                    this.Stream.Write(this.Delimiter);
                    this.Stream.Write(line);
                    this.Stream.Write(this.NewLine);
                }
                buffer.Clear();
            }

            this.Stream.Flush();
        }

        /// <inheritdoc/>
        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);
            this._buffers = null;
        }

        private static bool CheckBufferCount(int bufferCount, bool append)
        {
            if (bufferCount < 1) {
                ThrowHelper.BufferCountTooSmall(nameof(bufferCount));
            }

            return append;
        }

    }
}
