#include "tile/base/thread/scoped_lock.h"

#include "tile/base/internal/logging.h"
#include "tile/base/random.h"

#include <chrono>
#include <thread>

namespace tile {

namespace detail {
namespace scoped_lock {

namespace {
constexpr int kWarningTryCount  = 15;
constexpr auto kTryLockTimeCost = std::chrono::nanoseconds(20);

void
Lock(std::vector<std::function<void()>> &lockers)
{
    // use lockers_
    TILE_LOG_WARNING_IF(lockers.size() > 1,
                        "some mutex can't be `try_lock()`/`TryLock()`, use "
                        "`lock()`/`Lock()` instead of try_lockers_");

    for (auto &locker : lockers) { locker(); }
}

void
TryLock(std::vector<std::function<bool()>> &try_lockers, std::vector<std::function<void()>> &unlockers)
{
    TILE_CHECK(try_lockers.size() == unlockers.size(), "ensure try_lockers and unlockers have same size");
    std::vector<int> idxs(try_lockers.size(), 0);
    for (int i = 0; i < idxs.size(); i++) { idxs[i] = i; }

    int try_count = 0;
    while (true) {
        int i = 0;
        for (; i < idxs.size(); ++i) {
            if (!try_lockers[idxs[i]]()) { break; }
        }

        if (i == idxs.size()) {
            // all mutex are locked, exit.
            break;
        } else {
            ++try_count;
            TILE_LOG_WARNING_IF_EVERY_SECOND(
                try_count > kWarningTryCount, "try lock failed. try_count={}, locked_mutex={} times", try_count, i);
            while (--i >= 0) { unlockers[idxs[i]](); }
        }
        std::next_permutation(idxs.begin(), idxs.end());
        if (try_count > kWarningTryCount) {
            std::this_thread::sleep_for(Random(0, 5) * try_lockers.size() * kTryLockTimeCost);
        }
    }
}

}// namespace
}// namespace scoped_lock
}// namespace detail

namespace detail {
namespace scoped_lock {

void
LockMultipleMutexByNextPermutation(std::vector<std::function<void()>> &lockers,
                                   std::vector<std::function<bool()>> &try_lockers,
                                   std::vector<std::function<void()>> &unlockers)
{
    TILE_DCHECK(lockers.size() == unlockers.size());
    TILE_DCHECK_LE(try_lockers.size(), lockers.size());

    if (lockers.size() > 1 && try_lockers.size() == lockers.size()) {
        TryLock(try_lockers, unlockers);
    } else {
        Lock(lockers);
    }
}

void
UnlockMultipleMutex(std::vector<std::function<void()>> &unlockers)
{
    for (auto &unlocker : unlockers) { unlocker(); }
}

}// namespace scoped_lock

}// namespace detail

ScopedLock::ScopedLock(Mutex &m) : ScopedBase(m), mutex_ptr_(&m)
{
    detail::scoped_lock::LockMultipleMutexByNextPermutation(lockers_, try_lockers_, unlockers_);
}

ScopedLock::~ScopedLock() { detail::scoped_lock::UnlockMultipleMutex(unlockers_); }

}// namespace tile
