#include "sled/system/thread_pool.h"
#include "sled/log/log.h"
#include "sled/system/location.h"
#include "sled/task_queue/task_queue_base.h"

namespace sled {

constexpr char ThreadPool::kTag[];

ThreadPool::ThreadPool(int num_threads) : delayed_thread_(sled::Thread::Create())
{
    if (num_threads <= 0) { num_threads = std::thread::hardware_concurrency(); }
    auto state = std::make_shared<State>();
    for (int i = 0; i < num_threads; i++) {
        threads_.emplace_back(std::thread([state] {
            state->idle.fetch_add(1, std::memory_order_relaxed);
            while (state->is_running) {
                std::function<void()> task;
                sled::Location loc = SLED_FROM_HERE;
                // fetch task
                {
                    sled::MutexLock lock(&state->mutex);
                    state->cv.Wait(lock, [state] { return !state->task_queue.empty() || !state->is_running; });
                    if (!state->task_queue.empty()) {
                        task = std::move(state->task_queue.front().first);
                        loc  = state->task_queue.front().second;
                        state->task_queue.pop();
                    }
                    // FIXME: can't exit if task add self, must check <is_running>
                    if (!state->is_running) {
                        state->idle.fetch_sub(1, std::memory_order_relaxed);
                        break;
                    }

                    if (!state->task_queue.empty()) { state->cv.NotifyOne(); }
                }
                if (task) {
                    state->idle.fetch_sub(1, std::memory_order_release);
                    try {
                        task();
                    } catch (const std::exception &e) {
                        LOGE(kTag, "ThreadPool::ThreadPool() task exception: {}, from={}", e.what(), loc.ToString());
                    } catch (...) {
                        LOGE(kTag, "ThreadPool::ThreadPool() task unknown exception, from={}", loc.ToString());
                    }
                    state->idle.fetch_add(1, std::memory_order_relaxed);
                }
            }
        }));
    }
    state_ = state;
    delayed_thread_->Start();
}

ThreadPool::~ThreadPool() { Delete(); }

void
ThreadPool::Delete()
{
    if (state_) {
        sled::MutexLock lock(&state_->mutex);
        state_->is_running = false;
        state_->cv.NotifyAll();
        state_ = nullptr;
    }
    delayed_thread_.reset();
    for (auto &thread : threads_) { thread.join(); }
}

int
ThreadPool::idle() const
{
    auto state = state_;
    SLED_ASSERT(state != nullptr, "ThreadPool::idle() state_ is nullptr");
    if (state->is_running) { return state->idle; }
    return 0;
}

void
ThreadPool::PostTaskImpl(std::function<void()> &&task, const PostTaskTraits &traits, const Location &location)
{
    auto state = state_;
    SLED_ASSERT(state != nullptr, "ThreadPool::PostTaskImpl() state_ is nullptr");
    if (!state->is_running) {
        LOGW(kTag, "ThreadPool::PostTaskImpl() state is not running");
        return;
    }
    sled::MutexLock lock(&state->mutex);
    state->task_queue.emplace(std::move(task), location);
    state->cv.NotifyOne();
}

void
ThreadPool::PostDelayedTaskImpl(std::function<void()> &&task,
                                TimeDelta delay,
                                const PostDelayedTaskTraits &traits,
                                const Location &location)
{
    auto weak_state = std::weak_ptr<State>(state_);
    auto delay_post = [task, location, weak_state]() {
        auto state = weak_state.lock();
        if (!state || !state->is_running) { return; }
        sled::MutexLock lock(&state->mutex);
        state->task_queue.emplace(std::move(task), location);
        state->cv.NotifyOne();
    };
    if (traits.high_precision) {
        delayed_thread_->PostDelayedTaskWithPrecision(
            TaskQueueBase::DelayPrecision::kHigh,
            std::move(delay_post),
            delay,
            location);
    } else {
        delayed_thread_
            ->PostDelayedTaskWithPrecision(TaskQueueBase::DelayPrecision::kLow, std::move(delay_post), delay, location);
    }
}

}// namespace sled
