#ifndef TILE_BASE_THREAD_UNIQUE_LOCK_H
#define TILE_BASE_THREAD_UNIQUE_LOCK_H

#pragma once

#include "tile/base/internal/logging.h"
#include "tile/base/internal/mutex.h"
#include "tile/base/thread/cond_var.h"

#include <atomic>

namespace tile {
template<typename M, typename... Args>
class UniqueLock;

template<typename M>
class UniqueLock<M> final {
    static_assert((detail::thread::Lockable<M>::value || detail::thread::lockable<M>::value)
                      && (detail::thread::Unlockable<M>::value || detail::thread::unlockable<M>::value),
                  "M is not lockable, unlockable");

public:
    inline explicit UniqueLock(M &m) : m_(m), locked_(true) { MutexOperator<M>::Lock(&m_); }

    inline ~UniqueLock()
    {
        if (locked_.load(std::memory_order_acquire)) { MutexOperator<M>::Unlock(&m_); }
    }

    UniqueLock(const UniqueLock &)            = delete;
    UniqueLock(UniqueLock &&)                 = delete;
    UniqueLock &operator=(const UniqueLock &) = delete;
    UniqueLock &operator=(UniqueLock &&)      = delete;

    inline void Unlock()
    {
        bool expected = true;
        if (locked_.compare_exchange_strong(expected, false)) {
            MutexOperator<M>::Unlock(&m_);
        } else {
            TILE_CHECK(false, "You can't unlock a unlocked lock");
        }
    }

private:
    friend class ::tile::detail::cond_var::CondVar;

    M *GetMutex() { return &m_; }

private:
    M &m_;
    std::atomic<bool> locked_;
};

}// namespace tile

#endif// TILE_BASE_THREAD_UNIQUE_LOCK_H
