/*
* Copyright (c) 2013-2020, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
* See full license text in LICENSE file at top of project tree
*/

#ifndef QUEUE_H__
#define QUEUE_H__

#include <queue>
#include <vector>
#include <mutex>
#include <thread>
#include <condition_variable>
#include <functional>
#include <utility>

namespace i2p {
    namespace util {
        template<typename Element>
        class Queue {
        public:

            void Put(Element e) {
                std::unique_lock <std::mutex> l(m_QueueMutex);
                m_Queue.push(std::move(e));
                m_NonEmpty.notify_one();
            }

            template<template<typename, typename...> class Container, typename... R>
            void Put(const Container<Element, R...> &vec) {
                if (!vec.empty()) {
                    std::unique_lock <std::mutex> l(m_QueueMutex);
                    for (const auto &it: vec)
                        m_Queue.push(std::move(it));
                    m_NonEmpty.notify_one();
                }
            }

            Element GetNext() {
                std::unique_lock <std::mutex> l(m_QueueMutex);
                auto el = GetNonThreadSafe();
                if (!el) {
                    m_NonEmpty.wait(l);
                    el = GetNonThreadSafe();
                }
                return el;
            }

            Element GetNextWithTimeout(int usec) {
                std::unique_lock <std::mutex> l(m_QueueMutex);
                auto el = GetNonThreadSafe();
                if (!el) {
                    m_NonEmpty.wait_for(l, std::chrono::milliseconds(usec));
                    el = GetNonThreadSafe();
                }
                return el;
            }

            void Wait() {
                std::unique_lock <std::mutex> l(m_QueueMutex);
                m_NonEmpty.wait(l);
            }

            bool Wait(int sec, int usec) {
                std::unique_lock <std::mutex> l(m_QueueMutex);
                return m_NonEmpty.wait_for(l, std::chrono::seconds(sec) + std::chrono::milliseconds(usec)) !=
                       std::cv_status::timeout;
            }

            bool IsEmpty() {
                std::unique_lock <std::mutex> l(m_QueueMutex);
                return m_Queue.empty();
            }

            int GetSize() {
                std::unique_lock <std::mutex> l(m_QueueMutex);
                return m_Queue.size();
            }

            void WakeUp() { m_NonEmpty.notify_all(); };

            Element Get() {
                std::unique_lock <std::mutex> l(m_QueueMutex);
                return GetNonThreadSafe();
            }

            Element Peek() {
                std::unique_lock <std::mutex> l(m_QueueMutex);
                return GetNonThreadSafe(true);
            }

        private:

            Element GetNonThreadSafe(bool peek = false) {
                if (!m_Queue.empty()) {
                    auto el = m_Queue.front();
                    if (!peek)
                        m_Queue.pop();
                    return el;
                }
                return nullptr;
            }

        private:

            std::queue <Element> m_Queue;
            std::mutex m_QueueMutex;
            std::condition_variable m_NonEmpty;
        };
    }
}

#endif