/*
 * Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
 * that can be found in the LICENSE file.
 */

#ifndef RUNTIME_MM_THREAD_STATE_H
#define RUNTIME_MM_THREAD_STATE_H

#include <Common.h>
#include <Utils.hpp>

#include "ThreadData.hpp"
#include "ThreadSuspension.hpp"

namespace kotlin {

namespace internal {

ALWAYS_INLINE inline bool isStateSwitchAllowed(ThreadState oldState, ThreadState newState, bool reentrant) noexcept  {
    return oldState != newState || reentrant;
}

std::string statesToString(std::initializer_list<ThreadState> states) noexcept;

} // namespace internal

const char* ThreadStateName(ThreadState state) noexcept;

// Switches the state of the given thread to `newState` and returns the previous thread state.
PERFORMANCE_INLINE inline ThreadState SwitchThreadState(mm::ThreadData* threadData, ThreadState newState, bool reentrant = false) noexcept {
    RuntimeAssert(threadData != nullptr, "threadData must not be nullptr");
    auto oldState = threadData->setState(newState);
    // TODO(perf): Mesaure the impact of this assert in debug and opt modes.
    RuntimeAssert(internal::isStateSwitchAllowed(oldState, newState, reentrant),
                  "Illegal thread state switch. Old state: %s. New state: %s.",
                  ThreadStateName(oldState), ThreadStateName(newState));
    return oldState;
}

// Asserts that the given thread is in the given state.
ALWAYS_INLINE inline void AssertThreadState(mm::ThreadData* threadData, ThreadState expected) noexcept {
    // The read of the thread state is atomic, thus the compiler cannot eliminate it
    // even if its result is unused due to disabled runtime asserts.
    // So we explicitly avoid the read if asserts are disabled.
    if (compiler::runtimeAssertsMode() != compiler::RuntimeAssertsMode::kIgnore) {
        RuntimeAssert(threadData != nullptr, "threadData must not be nullptr");
        auto actual = threadData->state();
        RuntimeAssert(
                actual == expected, "Unexpected thread state. Expected: %s. Actual: %s.", ThreadStateName(expected),
                ThreadStateName(actual));
    }
}

ALWAYS_INLINE inline void AssertThreadState(mm::ThreadData* threadData, std::initializer_list<ThreadState> expected) noexcept {
    // The read of the thread state is atomic, thus the compiler cannot eliminate it
    // even if its result is unused due to disabled runtime asserts.
    // So we explicitly avoid the read if asserts are disabled.
    if (compiler::runtimeAssertsMode() != compiler::RuntimeAssertsMode::kIgnore) {
        RuntimeAssert(threadData != nullptr, "threadData must not be nullptr");
        auto actual = threadData->state();
        RuntimeAssert(
                std::any_of(expected.begin(), expected.end(), [actual](ThreadState expected) { return expected == actual; }),
                "Unexpected thread state. Expected one of: %s. Actual: %s", internal::statesToString(expected).c_str(),
                ThreadStateName(actual));
    }
}

} // namespace kotlin

#endif // RUNTIME_MM_THREAD_STATE_H
