using System;
using System.Text;
using System.Threading;
using Quadarax.Foundation.Core.Object;
// ReSharper disable InconsistentNaming

namespace Quadarax.Foundation.Core.Console
{
    public class ProgressBar : DisposableObject, IProgress<double>
    {

        #region *** Constants ***
        private const int CN_BLOCK_COUNT = 10;
        private const string CS_ANIMATION = @"|/-\";
        #endregion
        #region *** Private fields ***
        private readonly TimeSpan _animationInterval = TimeSpan.FromSeconds(1.0 / 8);
        private readonly Timer _timer;
        private double _currentProgress;
        private string _currentText = string.Empty;
        private int _animationIndex;
        private ConsoleWriter? _writer;
        #endregion

        #region *** Constructors ***
        public ProgressBar(ConsoleWriter writer)
        {
            _writer = writer ?? throw new ArgumentNullException(nameof(writer));
            _timer = new Timer(TimerHandler);

            // A progress bar is only for temporary display in a console window.
            // If the console output is redirected to a file, draw nothing.
            // Otherwise, we'll end up with a lot of garbage in the target file.
            if (!System.Console.IsOutputRedirected)
            {
                ResetTimer();
            }
        }
        #endregion

        #region *** Public Operations ***
        /// <summary>
        /// Update progress status.
        /// </summary>
        /// <param name="value">Status value between 0 - 1. Represents percentage.</param>
        public void Report(double value)
        {
            // Make sure value is in [0..1] range
            value = Math.Max(0, Math.Min(1, value));
            Interlocked.Exchange(ref _currentProgress, value);
        }
        #endregion

        #region *** Private Operations ***
        private void TimerHandler(object? state)
        {
            lock (_timer)
            {
                var progressBlockCount = (int)(_currentProgress * CN_BLOCK_COUNT);
                var percent = (int)(_currentProgress * 100);
                var text = $"[{new string('#', progressBlockCount)}{new string('-', CN_BLOCK_COUNT - progressBlockCount)}] {percent,3}% {CS_ANIMATION[_animationIndex++ % CS_ANIMATION.Length]}";
                UpdateText(text);
                ResetTimer();
            }
        }

        private void UpdateText(string text)
        {
            // Get length of common portion
            var commonPrefixLength = 0;
            var commonLength = Math.Min(_currentText.Length, text.Length);
            while (commonPrefixLength < commonLength && text[commonPrefixLength] == _currentText[commonPrefixLength])
            {
                commonPrefixLength++;
            }

            // Backtrack to the first differing character
            var outputBuilder = new StringBuilder();
            outputBuilder.Append('\b', _currentText.Length - commonPrefixLength);

            // Output new suffix
            outputBuilder.Append(text.Substring(commonPrefixLength));

            // If the new text is shorter than the old one: delete overlapping characters
            var overlapCount = _currentText.Length - text.Length;
            if (overlapCount > 0)
            {
                outputBuilder.Append(' ', overlapCount);
                outputBuilder.Append('\b', overlapCount);
            }

            _writer?.Write(outputBuilder.ToString());
            _currentText = text;
        }

        private void ResetTimer()
        {
            _timer.Change(_animationInterval, TimeSpan.FromMilliseconds(-1));
        }
        #endregion

        #region *** Private Overrides ***
        protected override void OnDisposing()
        {
            lock (_timer)
            {
                UpdateText(string.Empty);
                _writer = null;
            }
        }
        #endregion
    }
}
