/*
 * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or (at your
 * option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program. If not, see .
 */
#include "TaskScheduler.h"
#include "Errors.h"
TaskScheduler::TaskScheduler()
    : self_reference(this, [](TaskScheduler const*) { }),
    _now(clock_t::now()),
    _predicate(EmptyValidator)
{
}
TaskScheduler::~TaskScheduler() = default;
TaskScheduler& TaskScheduler::ClearValidator()
{
    _predicate = EmptyValidator;
    return *this;
}
TaskScheduler& TaskScheduler::Update(success_t const& callback/* = nullptr*/)
{
    _now = clock_t::now();
    Dispatch(callback);
    return *this;
}
TaskScheduler& TaskScheduler::Update(size_t milliseconds, success_t const& callback/* = nullptr*/)
{
    return Update(std::chrono::milliseconds(milliseconds), callback);
}
TaskScheduler& TaskScheduler::Update(duration_t difftime, success_t const& callback/* = nullptr*/)
{
    _now += difftime;
    Dispatch(callback);
    return *this;
}
TaskScheduler& TaskScheduler::Async(std::function callable)
{
    _asyncHolder.emplace(std::move(callable));
    return *this;
}
TaskScheduler& TaskScheduler::CancelAll()
{
    /// Clear the task holder
    _task_holder.Clear();
    _asyncHolder = AsyncHolder();
    return *this;
}
TaskScheduler& TaskScheduler::CancelGroup(group_t group)
{
    _task_holder.RemoveIf([group](TaskContainer const& task) -> bool
    {
        return task->IsInGroup(group);
    });
    return *this;
}
TaskScheduler& TaskScheduler::CancelGroupsOf(std::vector const& groups)
{
    for (group_t group : groups)
        CancelGroup(group);
    return *this;
}
TaskScheduler& TaskScheduler::DelayAll(duration_t duration)
{
    _task_holder.ModifyIf([&duration](TaskContainer const& task) -> bool
    {
        task->_end += duration;
        return true;
    });
    return *this;
}
TaskScheduler& TaskScheduler::DelayGroup(group_t const group, duration_t duration)
{
    _task_holder.ModifyIf([&duration, group](TaskContainer const& task) -> bool
    {
        if (task->IsInGroup(group))
        {
            task->_end += duration;
            return true;
        }
        else
            return false;
    });
    return *this;
}
TaskScheduler& TaskScheduler::RescheduleAll(duration_t duration)
{
    auto const end = _now + duration;
    _task_holder.ModifyIf([end](TaskContainer const& task) -> bool
    {
        task->_end = end;
        return true;
    });
    return *this;
}
TaskScheduler& TaskScheduler::RescheduleGroup(group_t const group, duration_t duration)
{
    auto const end = _now + duration;
    _task_holder.ModifyIf([end, group](TaskContainer const& task) -> bool
    {
        if (task->IsInGroup(group))
        {
            task->_end = end;
            return true;
        }
        else
            return false;
    });
    return *this;
}
TaskScheduler& TaskScheduler::InsertTask(TaskContainer task)
{
    _task_holder.Push(std::move(task));
    return *this;
}
TaskScheduler& TaskScheduler::ScheduleAt(timepoint_t end, duration_t time, task_handler_t task)
{
    return InsertTask(TaskContainer(new Task(end + time, time, std::move(task))));
}
TaskScheduler& TaskScheduler::ScheduleAt(timepoint_t end, duration_t time, group_t const group, task_handler_t task)
{
    static constexpr repeated_t DEFAULT_REPEATED = 0;
    return InsertTask(TaskContainer(new Task(end + time, time, group, DEFAULT_REPEATED, std::move(task))));
}
void TaskScheduler::Dispatch(success_t const& callback/* = nullptr*/)
{
    // If the validation failed abort the dispatching here.
    if (!_predicate())
        return;
    // Process all asyncs
    while (!_asyncHolder.empty())
    {
        _asyncHolder.front()();
        _asyncHolder.pop();
        // If the validation failed abort the dispatching here.
        if (!_predicate())
            return;
    }
    while (!_task_holder.IsEmpty())
    {
        if (_task_holder.First()->_end > _now)
            break;
        // Perfect forward the context to the handler
        // Use weak references to catch destruction before callbacks.
        TaskContext context(_task_holder.Pop(), std::weak_ptr(self_reference));
        // Invoke the context
        context.Invoke();
        // If the validation failed abort the dispatching here.
        if (!_predicate())
            return;
    }
    // On finish call the final callback
    if (callback)
        callback();
}
void TaskScheduler::TaskQueue::Push(TaskContainer&& task)
{
    container.emplace(std::move(task));
}
auto TaskScheduler::TaskQueue::Pop() -> TaskContainer
{
    TaskContainer result = *container.begin();
    container.erase(container.begin());
    return result;
}
auto TaskScheduler::TaskQueue::First() const -> TaskContainer const&
{
    return *container.begin();
}
void TaskScheduler::TaskQueue::Clear()
{
    container.clear();
}
void TaskScheduler::TaskQueue::RemoveIf(std::function const& filter)
{
    for (auto itr = container.begin(); itr != container.end();)
        if (filter(*itr))
            itr = container.erase(itr);
        else
            ++itr;
}
void TaskScheduler::TaskQueue::ModifyIf(std::function const& filter)
{
    std::vector cache;
    for (auto itr = container.begin(); itr != container.end();)
        if (filter(*itr))
        {
            cache.push_back(*itr);
            itr = container.erase(itr);
        }
        else
            ++itr;
    container.insert(cache.begin(), cache.end());
}
bool TaskScheduler::TaskQueue::IsEmpty() const
{
    return container.empty();
}
TaskContext& TaskContext::Dispatch(std::function const& apply)
{
    if (std::shared_ptr owner = _owner.lock())
        apply(*owner);
    return *this;
}
bool TaskContext::IsExpired() const
{
    return _owner.expired();
}
bool TaskContext::IsInGroup(TaskScheduler::group_t const group) const
{
    return _task->IsInGroup(group);
}
TaskContext& TaskContext::SetGroup(TaskScheduler::group_t const group)
{
    _task->_group = group;
    return *this;
}
TaskContext& TaskContext::ClearGroup()
{
    _task->_group = std::nullopt;
    return *this;
}
TaskScheduler::repeated_t TaskContext::GetRepeatCounter() const
{
    return _task->_repeated;
}
TaskContext& TaskContext::Repeat(TaskScheduler::duration_t duration)
{
    AssertOnConsumed();
    // Set new duration, in-context timing and increment repeat counter
    _task->_duration = duration;
    _task->_end += duration;
    _task->_repeated += 1;
    (*_consumed) = true;
    return this->Dispatch([this](TaskScheduler& scheduler) -> TaskScheduler&
    {
        return scheduler.InsertTask(_task);
    });
}
TaskContext& TaskContext::Async(std::function const& callable)
{
    return Dispatch([&](TaskScheduler& scheduler) -> TaskScheduler&
    {
        return scheduler.Async(callable);
    });
}
TaskContext& TaskContext::Schedule(TaskScheduler::duration_t time, TaskScheduler::task_handler_t task)
{
    auto const end = _task->_end;
    return this->Dispatch([end, time, task = std::move(task)](TaskScheduler& scheduler) mutable -> TaskScheduler&
    {
        return scheduler.ScheduleAt(end, time, std::move(task));
    });
}
TaskContext& TaskContext::Schedule(TaskScheduler::duration_t time, TaskScheduler::group_t const group, TaskScheduler::task_handler_t task)
{
    auto const end = _task->_end;
    return this->Dispatch([end, time, group, task = std::move(task)](TaskScheduler& scheduler) mutable -> TaskScheduler&
    {
        return scheduler.ScheduleAt(end, time, group, std::move(task));
    });
}
TaskContext& TaskContext::CancelAll()
{
    return Dispatch(&TaskScheduler::CancelAll);
}
TaskContext& TaskContext::CancelGroup(TaskScheduler::group_t const group)
{
    return Dispatch([=](TaskScheduler& scheduler) -> TaskScheduler&
    {
        return scheduler.CancelGroup(group);
    });
}
TaskContext& TaskContext::CancelGroupsOf(std::vector const& groups)
{
    return Dispatch([&](TaskScheduler& scheduler) -> TaskScheduler&
    {
        return scheduler.CancelGroupsOf(groups);
    });
}
TaskContext& TaskContext::DelayAll(TaskScheduler::duration_t duration)
{
    return this->Dispatch([=](TaskScheduler& scheduler) -> TaskScheduler&
    {
        return scheduler.DelayAll(duration);
    });
}
TaskContext& TaskContext::DelayGroup(TaskScheduler::group_t const group, TaskScheduler::duration_t duration)
{
    return this->Dispatch([=](TaskScheduler& scheduler) -> TaskScheduler&
    {
        return scheduler.DelayGroup(group, duration);
    });
}
TaskContext& TaskContext::RescheduleAll(TaskScheduler::duration_t duration)
{
    return this->Dispatch([=](TaskScheduler& scheduler) -> TaskScheduler&
    {
        return scheduler.RescheduleAll(duration);
    });
}
TaskContext& TaskContext::RescheduleGroup(TaskScheduler::group_t const group, TaskScheduler::duration_t duration)
{
    return this->Dispatch([=](TaskScheduler& scheduler) -> TaskScheduler&
    {
        return scheduler.RescheduleGroup(group, duration);
    });
}
void TaskContext::AssertOnConsumed() const
{
    // This was adapted to TC to prevent static analysis tools from complaining.
    // If you encounter this assertion check if you repeat a TaskContext more then 1 time!
    ASSERT(!(*_consumed) && "Bad task logic, task context was consumed already!");
}
void TaskContext::Invoke()
{
    _task->_task(*this);
}