#pragma once

#include <queue>
#include <robin_hood.h>
#include <mutex>
#include <condition_variable>

namespace data {
    /// Thread safe queue discarding duplicate values
    template <class T, class hash = robin_hood::hash<T>>
    class safe_unique_queue {
    private:
        std::queue<T> queue;
        robin_hood::unordered_flat_set<T, hash> set;
        std::mutex mutex;
        std::condition_variable cv;

    public:
        bool push(const T& in) {
            std::unique_lock<LockableBase(std::mutex)> lock(mutex);
            if(set.insert(in).second) {
                queue.push(in);
                cv.notify_one();
                return true;
            }
            return false;
        }

        bool pop(T& out) {
            std::unique_lock<LockableBase(std::mutex)> lock(mutex);
            if (queue.empty())
                return false;

            out = queue.front();
            set.erase(out);
            queue.pop();
            return true;
        }

        bool take(T& out) {
            std::unique_lock<LockableBase(std::mutex)> lock(mutex);
            if (queue.empty())
                return false;

            out = queue.front();
            queue.pop();
            return true;
        }

        void release(const T& taken) {
            std::unique_lock<LockableBase(std::mutex)> lock(mutex);
            set.erase(taken);
        }

        bool empty() {
            std::unique_lock<LockableBase(std::mutex)> lock(mutex);
            return queue.empty();
        }

        size_t size() {
            std::unique_lock<LockableBase(std::mutex)> lock(mutex);
            return set.size();
        }

        void notify_all() {
            cv.notify_all();
        }
        void notify_one() {
            cv.notify_one();
        }

        void wait() {
            std::unique_lock<LockableBase(std::mutex)> lock(mutex);
            if(queue.empty())
                cv.wait(lock);
        }
    };
}
