#include <iostream>
#include <thread>
#include <queue>
#include <mutex>
#include <condition_variable>
#include <atomic>
#include <chrono>

#ifdef _WIN32
    #include <conio.h>
    #include <windows.h>
#else
    #include <termios.h>
    #include <unistd.h>
#endif

class Keyboard {
public:
    Keyboard() : stop_(false) {
#ifdef _WIN32
        // Save console mode
        GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &orig_mode_);
        DWORD new_mode = orig_mode_ & ~(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT);
        SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), new_mode);
#else
        // Save and set terminal to raw mode
        tcgetattr(STDIN_FILENO, &orig_termios_);
        struct termios raw = orig_termios_;
        raw.c_lflag &= ~(ICANON | ECHO);
        tcsetattr(STDIN_FILENO, TCSANOW, &raw);
#endif
        // Start capture thread
        thread_ = std::thread(&Keyboard::run, this);
    }

    ~Keyboard() {
        stop_ = true;
        cond_.notify_all();
        if (thread_.joinable())
            thread_.join();

#ifdef _WIN32
        SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), orig_mode_);
#else
        tcsetattr(STDIN_FILENO, TCSANOW, &orig_termios_);
#endif
    }

    // Non-blocking: returns immediately (like polling)
    bool getEvent(char &c) {
        std::lock_guard<std::mutex> lock(mutex_);
        if (queue_.empty())
            return false;
        c = queue_.front();
        queue_.pop();
        return true;
    }

    // Blocking: waits until event or timeout
    bool waitEvent(char &c, std::chrono::milliseconds timeout) {
        std::unique_lock<std::mutex> lock(mutex_);
        if (!cond_.wait_for(lock, timeout, [this] { return !queue_.empty() || stop_; }))
            return false; // timeout
        if (stop_)
            return false;
        c = queue_.front();
        queue_.pop();
        return true;
    }

private:
    void run() {
        while (!stop_) {
            char c = 0;
#ifdef _WIN32
            if (_kbhit()) {
                c = static_cast<char>(_getch());
#else
            ssize_t n = read(STDIN_FILENO, &c, 1);
            if (n > 0) {
#endif
                // Ctrl+C
                if (c == 0x03) {
                    std::cout << "\nCtrl+C detected, stopping...\n";
                    stop_ = true;
                    cond_.notify_all();
                    break;
                }

                {
                    std::lock_guard<std::mutex> lock(mutex_);
                    queue_.push(c);
                }
                cond_.notify_one();
            }

#ifdef _WIN32
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
#endif
        }
    }

#ifdef _WIN32
    DWORD orig_mode_;
#else
    struct termios orig_termios_;
#endif
    std::thread thread_;
    std::queue<char> queue_;
    std::mutex mutex_;
    std::condition_variable cond_;
    std::atomic<bool> stop_;
};
