/* Copyright(C) 2007-2019 VoIPobjects (voipobjects.com)
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef __HL_SYNC_H
#define __HL_SYNC_H

#include <condition_variable>
#include <mutex>
#include <thread>
#include <vector>
#include <queue>
#include <functional>
#include <assert.h>

#if defined(TARGET_WIN)
# include <WinSock2.h>
# include <Windows.h>
#endif

typedef std::recursive_mutex Mutex;
typedef std::unique_lock<std::recursive_mutex> Lock;

class SyncHelper
{
public:
    static void delay(unsigned microseconds);
};

class Semaphore
{
public:
    Semaphore(unsigned int count = 0);

    void notify();
    void wait();
    bool waitFor(std::chrono::milliseconds timeout);

private:
    std::mutex m_mtx;
    std::condition_variable m_cv;
    unsigned int m_count;
};

class ThreadHelper
{
public:
    static void setName(const std::string& name);
    static uint64_t getCurrentId();
};

class chronox
{
public:
    // Returns current timestamp in milliseconds
    static std::chrono::milliseconds getTimestamp();

    // Returns uptime (of calling process) in milliseconds
    static std::chrono::milliseconds getUptime();

    // Finds time delta between 'later' and 'earlier' time points.
    // Handles cases when clock is wrapped.
    static uint32_t getDelta(uint32_t later, uint32_t earlier);

    // Converts number of milliseconds starting from Epoch begin to timespec.
    static timespec toTimespec(uint64_t milliseconds);
    static uint64_t toTimestamp(const timeval& ts);

    // Returns difference between timestamps in milliseconds
    static int64_t getDelta(const timespec& a, const timespec& b);
    static int64_t getDelta(const timeval& a, const timeval& b);

    class ExecutionTime
    {
    public:
        ExecutionTime();
        std::chrono::milliseconds getSpentTime() const;
    protected:
        std::chrono::milliseconds mStart;
    };
};


class BufferQueue
{
public:
    BufferQueue();
    ~BufferQueue();

    typedef std::vector<uint8_t> Block;
    typedef std::shared_ptr<Block> PBlock;

    void push(const void* data, int bytes);
    PBlock pull(int milliseconds);

protected:
    std::mutex mMutex;
    std::condition_variable mSignal;
    std::deque<PBlock> mBlockList;
};


// Timer Queue
//
// Allows execution of handlers at a specified time in the future
// Guarantees:
//  - All handlers are executed ONCE, even if canceled (aborted parameter will
//be set to true)
//      - If TimerQueue is destroyed, it will cancel all handlers.
//  - Handlers are ALWAYS executed in the Timer Queue worker thread.
//  - Handlers execution order is NOT guaranteed
//
class TimerQueue
{
public:
    TimerQueue();
    ~TimerQueue();

    //! Adds a new timer
    // \return
    //  Returns the ID of the new timer. You can use this ID to cancel the
    // timer
    uint64_t add(std::chrono::milliseconds seconds, std::function<void(bool)> handler);

    //! Cancels the specified timer
    // \return
    //  1 if the timer was cancelled.
    //  0 if you were too late to cancel (or the timer ID was never valid to
    // start with)
    size_t cancel(uint64_t id);

    //! Cancels all timers
    // \return
    //  The number of timers cancelled
    size_t cancelAll();

private:
    using Clock = std::chrono::steady_clock;
    TimerQueue(const TimerQueue&) = delete;
    TimerQueue& operator=(const TimerQueue&) = delete;

    void run();
    std::pair<bool, Clock::time_point> calcWaitTime();
    void checkWork();
    Semaphore m_checkWork;
    std::thread m_th;
    bool m_finish = false;
    uint64_t m_idcounter = 0;

    struct WorkItem
    {
        Clock::time_point end;
        uint64_t id;  // id==0 means it was cancelled
        std::function<void(bool)> handler;
        bool operator > (const WorkItem& other) const;
    };

    std::mutex m_mtx;
    // Inheriting from priority_queue, so we can access the internal container
    class Queue : public std::priority_queue<WorkItem, std::vector<WorkItem>,
            std::greater<WorkItem>>
    {
    public:
        std::vector<WorkItem>& getContainer();
    } m_items;
};

#endif
