#ifndef COMMAND_QUEUE_HPP_
#define COMMAND_QUEUE_HPP_

#include <functional>
#include <mutex>
#include <queue>
#include <condition_variable>

/**
 * @brief Represents a queue of commands.
 * 
 * Commands are sent by many threads to a single thread for processing.
 */
template<typename TState>
class CommandQueue
{
public:
    typedef std::function<void(TState&)> Command;

private:
    std::mutex mutex;
    std::queue<Command> commands;
    std::condition_variable cond;
    bool active;

public:
    CommandQueue() : active(true) {}

    /**
     * @brief Process the next command.
     * 
     * @param state State reference to pass to commands.
     * @return true If a command was processed.
     * @return false If there were no commands left.
     */
    bool processNextCommand(TState &state)
    {
        std::unique_lock<std::mutex> lock(mutex);
        
        if (commands.size() == 0)
        {
            return false;
        }

        Command command = commands.front();
        commands.pop();

        lock.unlock();
        command(state);

        return true;
    }

    /**
     * @brief Process all remaining commands (if any).
     */
    void processAllCommands(TState &state)
    {
        while (processNextCommand(state));
    }

    /**
     * @brief Add a command to the queue.
     * 
     * @param command The command.
     */
    void add(Command command)
    {
        std::unique_lock<std::mutex> lock(mutex);
        commands.push(command);

        lock.unlock();
        cond.notify_one();
    }

    /**
     * @brief Block until commands might be available.
     */
    void waitForCommands()
    {
        std::unique_lock<std::mutex> lock(mutex);
        while (active && commands.size() == 0)
        {
            cond.wait(lock);
        }
    }

    /**
     * @brief Returns true if this queue is active (not terminated).
     */
    bool isActive()
    {
        return active;
    }

    /**
     * @brief Terminate the queue.
     */
    void terminate()
    {
        active = false;
        cond.notify_all();
    }
};

#endif