#pragma once

#include <maps/libs/log8/include/log8.h>

#include <atomic>
#include <chrono>
#include <exception>
#include <thread>
#include <condition_variable>

namespace maps::wiki {

/// A CRTP base class for background tasks. The derived class must provide the
/// following functions:
/// * onStart() - executed once before the main loop of the thread.
/// * onStop() - executed once after the main loop of the thread.
/// * doWork() - executed repeatedly (with `waitTimeout` seconds period) till
///   the object destruction.
///
/// @note Any unhandled exception in the methods above will terminate the
/// thread.
template <class Worker>
class ThreadObserver
{
public:
    ThreadObserver(std::chrono::seconds waitTimeout)
        : waitTimeout_(waitTimeout)
    {
        REQUIRE(waitTimeout_.count(), "Invalid (zero) timeout");
    }

    ~ThreadObserver()
    {
        stop();
        thread_.join();
    }

    void start()
    {
        if (thread_.joinable()) {
            stop();
            thread_.join();
        }
        thread_ = std::thread(std::bind(&ThreadObserver<Worker>::run, this));
    }

    void awake()
    {
        if (canRun_) {
            std::lock_guard<std::mutex> lock(mutex_);
            condition_.notify_one();
        }
    }

private:
    void run()
    {
        auto worker = static_cast<Worker*>(this);

        try {
            worker->onStart();
            while (canRun_) {
                worker->doWork();
                std::unique_lock<std::mutex> lock(mutex_);
                if (!canRun_) {
                    break;
                }
                condition_.wait_for(lock, waitTimeout_);
            }
            worker->onStop();
        }
        catch (const std::exception& ex) {
            ERROR() << worker->name() << " exception:" << ex.what();
        }
        catch (...) {
            ERROR() << worker->name() << " unknown exception";
        }
    }

    void stop()
    {
        std::lock_guard<std::mutex> lock(mutex_);
        canRun_ = false;
        condition_.notify_one();
    }

    std::atomic<bool> canRun_{true};
    std::chrono::seconds waitTimeout_;
    std::condition_variable condition_;
    std::mutex mutex_;
    std::thread thread_;
};

} // namespace maps::wiki
