#ifndef TILE_BASE_THREAD_SCOPED_LOCK_H
#define TILE_BASE_THREAD_SCOPED_LOCK_H

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

#include <algorithm>
#include <utility>

namespace tile {
class Mutex;

namespace detail {
namespace scoped_lock {
template<typename... MutexType>
void
AssertNoDuplicateMutex(MutexType &...mutex)
{
    // [{lock1_ptr, idx1}, {lock2_ptr, idx2}, {lock3_ptr, idx3}...]
    std::vector<::std::pair<void *, int>> mutex_ptrs = detail::thread::GetMutexPtrs(1, mutex...);

    std::sort(mutex_ptrs.begin(), mutex_ptrs.end());
    auto iter = std::adjacent_find(mutex_ptrs.begin(), mutex_ptrs.end(),
                                   [](const ::std::pair<void *, int> &lhs, const ::std::pair<void *, int> &rhs) {
                                       return lhs.first == rhs.first;
                                   });
    TILE_CHECK(iter == mutex_ptrs.end(), "You should not lock a lock twice idx1 [{}], idx2 [{}]", iter->second,
               (iter + 1)->second);
}

class ScopedBase {
public:
    template<typename... MutexType>
    ScopedBase(MutexType &...mutex)
        : lockers_(detail::thread::GetLockers(mutex...)),
          try_lockers_(detail::thread::GetTryLockers(mutex...)),
          unlockers_(detail::thread::GetUnlockers(mutex...))
    {
        detail::scoped_lock::AssertNoDuplicateMutex(mutex...);

        constexpr auto count = detail::thread::mutex_count<MutexType...>::value;
        TILE_CHECK(lockers_.size() == count, "You should not lock a unlockable object");
        TILE_CHECK(unlockers_.size() == count, "You should not unlock a unlockable object");
    }

protected:
    std::vector<std::function<void()>> lockers_;
    std::vector<std::function<bool()>> try_lockers_;
    std::vector<std::function<void()>> unlockers_;
};

void LockMultipleMutexByNextPermutation(std::vector<std::function<void()>> &lockers,
                                        std::vector<std::function<bool()>> &try_lockers,
                                        std::vector<std::function<void()>> &unlockers);
void UnlockMultipleMutex(std::vector<std::function<void()>> &unlockers);

// template <typename M, typename... MutexType>
// enable_if_t<std::is_same<::tile::Mutex, M>::value, Mutex *>
// GetFirstElementMutexPtr(M &m, MutexType &...) {
//   return &m;
// }
// template <typename M, typename... MutexType>
// enable_if_t<!std::is_same<::tile::Mutex, M>::value, Mutex *>
// GetFirstElementMutexPtr(M &, MutexType &...) {
//   return nullptr;
// }

}// namespace scoped_lock

}// namespace detail

class ScopedLock : private detail::scoped_lock::ScopedBase {
public:
    template<typename... MutexType>
    ScopedLock(MutexType &...mutex)
        : ScopedBase(mutex...),
          mutex_ptr_(nullptr)
    {
        detail::scoped_lock::LockMultipleMutexByNextPermutation(lockers_, try_lockers_, unlockers_);
    }

    explicit ScopedLock(Mutex &m);

    ~ScopedLock();

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

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

    Mutex *GetMutex()
    {
        TILE_CHECK(mutex_ptr_, "mutex_ptr_ is nullptr");
        return mutex_ptr_;
    }

private:
    // if first element is not a ::tile::Mutex, mutex_ptr_ is nullptr.
    Mutex *mutex_ptr_;
};

class ScopedUnlock : private detail::scoped_lock::ScopedBase {
public:
    template<typename... MutexType>
    ScopedUnlock(MutexType &...mutex) : ScopedBase(mutex...)
    {
        detail::scoped_lock::UnlockMultipleMutex(unlockers_);
    }

    inline ~ScopedUnlock()
    {
        detail::scoped_lock::LockMultipleMutexByNextPermutation(lockers_, try_lockers_, unlockers_);
    }

    ScopedUnlock(const ScopedUnlock &)            = delete;
    ScopedUnlock(ScopedUnlock &&)                 = delete;
    ScopedUnlock &operator=(const ScopedUnlock &) = delete;
    ScopedUnlock &operator=(ScopedUnlock &&)      = delete;
};

}// namespace tile
#endif// TILE_BASE_THREAD_SCOPED_LOCK_H
