#pragma once

#include <util/generic/noncopyable.h>
#include <util/system/defaults.h>

template <class T>
struct TCommonLockOps {
    static inline void Acquire(T* t) noexcept {
        t->Acquire();
    }

    static inline void Release(T* t) noexcept {
        t->Release();
    }
};

template <class T>
struct TTryLockOps: public TCommonLockOps<T> {
    static inline bool TryAcquire(T* t) noexcept {
        return t->TryAcquire();
    }
};

//must be used with great care
template <class TOps>
struct TInverseLockOps: public TOps {
    template <class T>
    static inline void Acquire(T* t) noexcept {
        TOps::Release(t);
    }

    template <class T>
    static inline void Release(T* t) noexcept {
        TOps::Acquire(t);
    }
};

template <class T, class TOps = TCommonLockOps<T>>
class TGuard: public TNonCopyable {
public:
    inline TGuard(const T& t) noexcept {
        Init(&t);
    }

    inline TGuard(const T* t) noexcept {
        Init(t);
    }

    inline TGuard(TGuard&& g) noexcept
        : T_(g.T_)
    {
        g.T_ = nullptr;
    }

    inline ~TGuard() {
        Release();
    }

    inline void Release() noexcept {
        if (WasAcquired()) {
            TOps::Release(T_);
            T_ = nullptr;
        }
    }

    explicit inline operator bool() const noexcept {
        return WasAcquired();
    }

    inline bool WasAcquired() const noexcept {
        return T_ != nullptr;
    }

    inline T* GetMutex() const noexcept {
        return T_;
    }

private:
    inline void Init(const T* t) noexcept {
        T_ = const_cast<T*>(t);
        TOps::Acquire(T_);
    }

private:
    T* T_;
};

/*
 * {
 *     auto guard = Guard(Lock_);
 *     some code under guard
 * }
 */
template <class T>
static inline TGuard<T> Guard(const T& t) {
    return {&t};
}

/*
 * with_lock (Lock_) {
 *     some code under guard
 * }
 */
#define with_lock(X)                                              \
    if (auto Y_GENERATE_UNIQUE_ID(__guard) = ::Guard(X); false) { \
    } else

/*
 * auto guard = Guard(Lock_);
 * ... some code under lock
 * {
 *     auto unguard = Unguard(guard);
 *     ... some code not under lock
 * }
 * ... some code under lock
 */
template <class T, class TOps = TCommonLockOps<T>>
using TInverseGuard = TGuard<T, TInverseLockOps<TOps>>;

template <class T, class TOps>
static inline TInverseGuard<T, TOps> Unguard(const TGuard<T, TOps>& guard) {
    return {guard.GetMutex()};
}

template <class T>
static inline TInverseGuard<T> Unguard(const T& mutex) {
    return {&mutex};
}

template <class T, class TOps = TTryLockOps<T>>
class TTryGuard: public TNonCopyable {
public:
    inline TTryGuard(const T& t) noexcept {
        Init(&t);
    }

    inline TTryGuard(const T* t) noexcept {
        Init(t);
    }

    inline TTryGuard(TTryGuard&& g) noexcept
        : T_(g.T_)
    {
        g.T_ = nullptr;
    }

    inline ~TTryGuard() {
        Release();
    }

    inline void Release() noexcept {
        if (WasAcquired()) {
            TOps::Release(T_);
            T_ = nullptr;
        }
    }

    inline bool WasAcquired() const noexcept {
        return T_ != nullptr;
    }

    explicit inline operator bool() const noexcept {
        return WasAcquired();
    }

private:
    inline void Init(const T* t) noexcept {
        T_ = nullptr;
        T* tMutable = const_cast<T*>(t);
        if (TOps::TryAcquire(tMutable)) {
            T_ = tMutable;
        }
    }

private:
    T* T_;
};