using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Scheduler.Backgrounding
{
    public class BGTaskLoop
    {
        public Guid Guid { get; } = Guid.NewGuid();
        private int _intervalID = 0;

        private readonly CancellationTokenSource _cts = new();
        private readonly IBGTaskLoopable _bgTaskDelegate;
        private readonly PeriodicTimer _pTimer;
        private Task? _bgTask;

        public event EventHandler<BGTaskStartEvent>? OnExecuteStart;
        public event EventHandler<BGTaskEndEvent>? OnExecuteEnd;

        public BGTaskLoop(IBGTaskLoopable task, TimeSpan interval)
        {
            _bgTaskDelegate = task;
            _pTimer = new PeriodicTimer(interval);
        }

        public void Start()
        {
            _bgTask = RunBGTaskOnInterval();
        }

        private async Task RunBGTaskOnInterval()
        {
            try
            {
                while (await _pTimer.WaitForNextTickAsync(_cts.Token)) //TODO: Maybe inform the scheduling core when 
                                                                       //task time > interval
                                                                       //or automatically update interval?
                                                                       //"BackgroundTaskScheduler that will call "update interval" etc.
                {
                    int id = ++_intervalID;
                    OnExecuteStart?.Invoke(this, new(Guid, id, DateTime.Now));
                    await _bgTaskDelegate.OnBGTaskLoopAsync();
                    OnExecuteEnd?.Invoke(this, new(Guid, id, DateTime.Now));
                }
            }
            catch
            {

            }
        }

        public async Task StopAsync()
        {
            if (_bgTask == null)
                return;

            _cts.Cancel();
            await _bgTask; //ensure completion
            _pTimer.Dispose();
            _cts.Dispose();
        }
    }

    public class BGTaskLoop<TResult>
    {
        public Guid Guid { get; } = Guid.NewGuid();
        private int _intervalID = 0;

        private readonly CancellationTokenSource _cts = new();
        private readonly IBGTaskLoopable<TResult> _bgTaskDelegate;
        private PeriodicTimer _pTimer;
        private TimeSpan _currentIntrvl;
        private Task? _bgTask;

        public event EventHandler<BGTaskStartEvent>? OnExecuteStart;
        public event EventHandler<BGTaskEndEvent<TResult>>? OnExecuteEnd;

        public BGTaskLoop(IBGTaskLoopable<TResult> task, TimeSpan interval)
        {
            _bgTaskDelegate = task;
            _currentIntrvl = interval;
            _pTimer = new PeriodicTimer(interval);
        }

        public void Start()
        {
            _bgTask = RunBGTaskOnIntervalAsync();
        }

        internal void Start(TimeSpan startAfter)
        {
            _bgTask = RunBGTaskOnIntervalAsync(startAfter);
        }

        private async Task RunBGTaskOnIntervalAsync(TimeSpan? startAfter = null)
        {
            if (startAfter.HasValue)
                await Task.Delay((int)startAfter.Value.TotalMilliseconds);

            try
            {
                while (await _pTimer.WaitForNextTickAsync(_cts.Token)) //TODO: Maybe inform the scheduling core when 
                                                                       //task time > interval
                                                                       //or automatically update interval?
                                                                       //"BackgroundTaskScheduler that will call "update interval" etc.
                {
                    bool update = _updateRequested;
                    if (update)
                    {
                        _pTimer.Dispose(); //Dispose directly after call,
                        //.WaitForNextTickAsync(..) will now return false
                    }

                    int id = ++_intervalID;
                    OnExecuteStart?.Invoke(this, new(Guid, id, DateTime.Now));
                    TResult result = await _bgTaskDelegate.OnBGTaskLoopAsync();
                    OnExecuteEnd?.Invoke(this, new(Guid, id, DateTime.Now, result));

                    if (update)
                    {
                        UpdateRunBGTaskOnInterval();
                        return;
                    }
                }
            }
            catch (Exception ex)
            {
                var asd = ex;
                //TODO: Invoke OnError?
            }
        }

        private bool _updateRequested = false;
        //TODO: make it instantly act upon timer,
        //eg. if timer would trigger 2 sec after .UpdateInterval(..)
        //and newInterval is lower than 2 sec
        //make the call earlier somehow :-)
        //opt: extract while business logic to method and call it earlier than while loop wouldve done
        /// <summary> Updates the current timer loop to return on a new interval. This will take effect after any current iteration is in process</summary>
        public void UpdateInterval(TimeSpan newInterval)
        {
            _currentIntrvl = newInterval;
            _updateRequested = true;
        }

        private void UpdateRunBGTaskOnInterval()
        {
            _updateRequested = false;
            _pTimer = new(_currentIntrvl);
            _bgTask = RunBGTaskOnIntervalAsync();
        }

        public async Task StopAsync()
        {
            if (_bgTask == null)
                return;

            _cts.Cancel();
            await _bgTask; //ensure completion
            _pTimer.Dispose();
            _cts.Dispose();
        }
    }

}
