using System;
using System.Collections.Generic;
using System.Linq;

namespace AsbCloudInfrastructure.Background
{

    /// <summary>
    /// <para>
    /// Очередь работ
    /// </para>
    /// Не периодические задачи будут возвращаться первыми, как самые приоритетные.
    /// </summary>
    class WorkQueue
    {
        private Queue<WorkBase> Primary = new(8);
        private readonly List<WorkPeriodic> Periodic = new(8);

        /// <summary>
        /// Добавление работы.
        /// </summary>
        /// <param name="work"></param>
        /// <exception cref="ArgumentException">Id mast be unique</exception>
        public void Push(WorkBase work)
        {
            if (Periodic.Any(w => w.Id == work.Id))
                throw new ArgumentException("work.Id is not unique", nameof(work));

            if (Primary.Any(w => w.Id == work.Id))
                throw new ArgumentException("work.Id is not unique", nameof(work));

            if (work is WorkPeriodic workPeriodic)
            {
                Periodic.Add(workPeriodic);
                return;
            }

            Primary.Enqueue(work);
        }

        /// <summary>
        /// Удаление работы по ID
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public bool Delete(string id)
        {
            var workPeriodic = Periodic.FirstOrDefault(w => w.Id == id);
            if (workPeriodic is not null)
            {
                Periodic.Remove(workPeriodic);
                return true;
            }

            var work = Primary.FirstOrDefault(w => w.Id == id);
            if (work is not null)
            {
                Primary = new Queue<WorkBase>(Primary.Where(w => w.Id != id));
                return true;
            }

            return false;
        }

        public bool Contains(string id)
        {
            var result = Periodic.Any(w => w.Id == id) || Primary.Any(w => w.Id == id);
            return result;
        }

        /// <summary>
        /// <para>
        /// Возвращает приоритетную задачу.
        /// </para>
        /// <para>
        /// Если приоритетные закончились, то ищет ближайшую периодическую.
        /// Если до старта ближайшей периодической работы меньше 20 сек,
        /// то этой задаче устанавливается время последнего запуска в now и она возвращается.
        /// Если больше 20 сек, то возвращается null.
        /// </para>
        /// </summary>
        /// <param name="maxTimeToNextWork"></param>
        /// <returns></returns>
        public WorkBase? Pop()
        {
            if (Primary.Any())
                return Primary.Dequeue();

            var work = GetNextPeriodic();
            if (work is null || work.NextStart > DateTime.Now)
                return null;

            work.LastStart = DateTime.Now;
            return work;
        }

        private WorkPeriodic? GetNextPeriodic()
        {
            var work = Periodic
                .OrderBy(w => w.NextStart)
                .ThenByDescending(w => w.Period)
                .FirstOrDefault();
            return work;
        }
    }

}