#ifndef TILE_BASE_INTERNAL_MUTEX_H
#define TILE_BASE_INTERNAL_MUTEX_H

#pragma once

#include "tile/base/internal/meta.h"

#include <vector>

namespace tile {
template<typename T, typename = void>
struct MutexTraits {
    static_assert(sizeof(T) == 0, "T is not lockable");
};

namespace detail {
namespace thread {
#define TILE_DETAIL_DEFINE_HAS_FUNCTOIN_TRAITS(FuncName, Ret)                                                          \
    template<typename C>                                                                                               \
    class has_##FuncName##_fn {                                                                                        \
        template<typename U, typename = enable_if_t<std::is_same<decltype(std::declval<U>().FuncName()), Ret>::value>> \
        static int Test(int);                                                                                          \
                                                                                                                       \
        template<typename>                                                                                             \
        static char Test(...);                                                                                         \
                                                                                                                       \
    public:                                                                                                            \
        static constexpr bool value = std::is_same<int, decltype(Test<C>(0))>::value;                                  \
    };

#define TILE_DETAIL_HAS_FUNCTON_VALUE(FuncName, T) has_##FuncName##_fn<T>::value

TILE_DETAIL_DEFINE_HAS_FUNCTOIN_TRAITS(Lock, void);
TILE_DETAIL_DEFINE_HAS_FUNCTOIN_TRAITS(Unlock, void);
TILE_DETAIL_DEFINE_HAS_FUNCTOIN_TRAITS(TryLock, bool);

TILE_DETAIL_DEFINE_HAS_FUNCTOIN_TRAITS(lock, void);
TILE_DETAIL_DEFINE_HAS_FUNCTOIN_TRAITS(unlock, void);
TILE_DETAIL_DEFINE_HAS_FUNCTOIN_TRAITS(try_lock, bool);

template<typename T>
struct Lockable {
    static constexpr bool value = TILE_DETAIL_HAS_FUNCTON_VALUE(Lock, T);
};

template<typename T>
struct lockable {
    static constexpr bool value = TILE_DETAIL_HAS_FUNCTON_VALUE(lock, T);
};

template<typename T>
struct Unlockable {
    static constexpr bool value = TILE_DETAIL_HAS_FUNCTON_VALUE(Unlock, T);
};

template<typename T>
struct unlockable {
    static constexpr bool value = TILE_DETAIL_HAS_FUNCTON_VALUE(unlock, T);
};

template<typename T>
struct TryLockable {
    static constexpr bool value = TILE_DETAIL_HAS_FUNCTON_VALUE(TryLock, T);
};

template<typename T>
struct try_lockable {
    static constexpr bool value = TILE_DETAIL_HAS_FUNCTON_VALUE(try_lock, T);
};

#undef TILE_DETAIL_DEFINE_HAS_FUNCTOIN_TRAITS
#undef TILE_DETAIL_HAS_FUNCTON_VALUE

template<typename T>
constexpr bool Lockable<T>::value;
template<typename T>
constexpr bool lockable<T>::value;
template<typename T>
constexpr bool Unlockable<T>::value;
template<typename T>
constexpr bool unlockable<T>::value;
template<typename T>
constexpr bool TryLockable<T>::value;
template<typename T>
constexpr bool try_lockable<T>::value;

template<typename M>
enable_if_t<Lockable<M>::value>
Lock(M *m)
{
    m->Lock();
}

template<typename M>
enable_if_t<!Lockable<M>::value && lockable<M>::value>
Lock(M *m)
{
    m->lock();
}

template<typename M>
enable_if_t<Unlockable<M>::value>
Unlock(M *m)
{
    m->Unlock();
}

template<typename M>
enable_if_t<!Unlockable<M>::value && unlockable<M>::value>
Unlock(M *m)
{
    m->unlock();
}

template<typename M>
enable_if_t<TryLockable<M>::value, bool>
TryLock(M *m)
{
    return m->TryLock();
}

template<typename M>
enable_if_t<!TryLockable<M>::value && try_lockable<M>::value, bool>
TryLock(M *m)
{
    return m->try_lock();
}

}// namespace thread
}// namespace detail

template<typename T>
struct MutexTraits<T> {
    static_assert((detail::thread::lockable<T>::value || detail::thread::Lockable<T>::value), "T is not lockable");
    static_assert((detail::thread::unlockable<T>::value || detail::thread::Unlockable<T>::value),
                  "T is not unlockable");

    static void Lock(T *m) { detail::thread::Lock(m); }

    static void Unlock(T *m) { detail::thread::Unlock(m); }

    static bool TryLock(T *m) { return detail::thread::TryLock(m); }
};

// 1. Lock + lock, use Lock
// 2. lock       , use lock
// 3. Lock       , use Lock
// * TryLock, Unlock the same *
template<typename T>
struct MutexOperator {
    static void Lock(T *m) { MutexTraits<T>::Lock(m); }

    static void Unlock(T *m) { MutexTraits<T>::Unlock(m); }

    static bool TryLock(T *m) { return MutexTraits<T>::TryLock(m); }
};

namespace detail {
namespace thread {

template<typename M>
std::vector<std::function<void()>>
GetLockers(M &m)
{
    return {[&]() { tile::MutexOperator<M>::Lock(&m); }};
}

template<typename M, typename... MutexType>
std::vector<std::function<void()>>
GetLockers(M &m, MutexType &...ms)
{
    std::vector<std::function<void()>> lockers = GetLockers(ms...);
    lockers.push_back([&m]() { tile::MutexOperator<M>::Lock(&m); });
    return lockers;
}

template<typename Vec, typename M>
enable_if_t<!detail::thread::TryLockable<M>::value && !detail::thread::try_lockable<M>::value>
GetTryLockersAppendTryLockOp(Vec &vec, M &m)
{ /* do nothing */
}

// `TryLockable` or `try_lockable`
template<typename Vec, typename M>
enable_if_t<detail::thread::TryLockable<M>::value || detail::thread::try_lockable<M>::value>
GetTryLockersAppendTryLockOp(Vec &vec, M &m)
{
    vec.push_back([&] { return tile::MutexOperator<M>::TryLock(&m); });
}

template<typename M>
std::vector<std::function<bool()>>
GetTryLockers(M &m)
{
    std::vector<std::function<bool()>> try_lockers;
    GetTryLockersAppendTryLockOp(try_lockers, m);
    return try_lockers;
}

template<typename M, typename... MutexType>
std::vector<std::function<bool()>>
GetTryLockers(M &m, MutexType &...ms)
{
    std::vector<std::function<bool()>> try_lockers = GetTryLockers(ms...);
    GetTryLockersAppendTryLockOp(try_lockers, m);
    // lockers.push_back([&m]() { return tile::MutexOperator<M>::TryLock(&m); });
    return try_lockers;
}

template<typename M>
std::vector<std::function<void()>>
GetUnlockers(M &m)
{
    return {[&m]() { tile::MutexOperator<M>::Unlock(&m); }};
}

template<typename M, typename... MutexType>
std::vector<std::function<void()>>
GetUnlockers(M &m, MutexType &...ms)
{
    std::vector<std::function<void()>> unlockers = GetUnlockers(ms...);
    unlockers.push_back([&m]() { tile::MutexOperator<M>::Unlock(&m); });
    return unlockers;
}

template<typename M, typename... MutexType>
struct mutex_count_impl;

template<typename M>
struct mutex_count_impl<M> {
    static constexpr std::size_t value = 1;
};

template<typename M, typename... MutexType>
struct mutex_count_impl {
    static constexpr std::size_t value = 1 + mutex_count_impl<MutexType...>::value;
};

template<typename... MutexType>
struct mutex_count {
    static constexpr std::size_t value = mutex_count_impl<MutexType...>::value;
};

template<typename M>
std::vector<std::pair<void *, int>>
GetMutexPtrs(int idx, M &m)
{
    return {
        {static_cast<void *>(&m), idx}
    };
}

template<typename M, typename... MutexType>
std::vector<std::pair<void *, int>>
GetMutexPtrs(int idx, M &m, MutexType &...ms)
{
    auto ptrs = GetMutexPtrs(idx + 1, ms...);
    ptrs.push_back({static_cast<void *>(&m), idx});
    return ptrs;
}

}// namespace thread
}// namespace detail

}// namespace tile

#endif// TILE_BASE_INTERNAL_MUTEX_H
