// Copyright 2021 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.

#include <vector>

#include "core/arm/exclusive_monitor.h"
#include "core/core.h"
#include "core/hle/kernel/k_condition_variable.h"
#include "core/hle/kernel/k_scheduler.h"
#include "core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h"
#include "core/hle/kernel/k_synchronization_object.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/svc_common.h"
#include "core/hle/kernel/svc_results.h"
#include "core/hle/kernel/thread.h"
#include "core/memory.h"

namespace Kernel {

namespace {

bool ReadFromUser(Core::System& system, u32* out, VAddr address) {
    *out = system.Memory().Read32(address);
    return true;
}

bool WriteToUser(Core::System& system, VAddr address, const u32* p) {
    system.Memory().Write32(address, *p);
    return true;
}

bool UpdateLockAtomic(Core::System& system, u32* out, VAddr address, u32 if_zero,
                      u32 new_orr_mask) {
    auto& monitor = system.Monitor();
    const auto current_core = system.CurrentCoreIndex();

    // Load the value from the address.
    const auto expected = monitor.ExclusiveRead32(current_core, address);

    // Orr in the new mask.
    u32 value = expected | new_orr_mask;

    // If the value is zero, use the if_zero value, otherwise use the newly orr'd value.
    if (!expected) {
        value = if_zero;
    }

    // Try to store.
    if (!monitor.ExclusiveWrite32(current_core, address, value)) {
        // If we failed to store, try again.
        return UpdateLockAtomic(system, out, address, if_zero, new_orr_mask);
    }

    // We're done.
    *out = expected;
    return true;
}

} // namespace

KConditionVariable::KConditionVariable(Core::System& system_)
    : system{system_}, kernel{system.Kernel()} {}

KConditionVariable::~KConditionVariable() = default;

ResultCode KConditionVariable::SignalToAddress(VAddr addr) {
    Thread* owner_thread = kernel.CurrentScheduler()->GetCurrentThread();

    // Signal the address.
    {
        KScopedSchedulerLock sl(kernel);

        // Remove waiter thread.
        s32 num_waiters{};
        Thread* next_owner_thread =
            owner_thread->RemoveWaiterByKey(std::addressof(num_waiters), addr);

        // Determine the next tag.
        u32 next_value{};
        if (next_owner_thread) {
            next_value = next_owner_thread->GetAddressKeyValue();
            if (num_waiters > 1) {
                next_value |= Svc::HandleWaitMask;
            }

            next_owner_thread->SetSyncedObject(nullptr, RESULT_SUCCESS);
            next_owner_thread->Wakeup();
        }

        // Write the value to userspace.
        if (!WriteToUser(system, addr, std::addressof(next_value))) {
            if (next_owner_thread) {
                next_owner_thread->SetSyncedObject(nullptr, Svc::ResultInvalidCurrentMemory);
            }

            return Svc::ResultInvalidCurrentMemory;
        }
    }

    return RESULT_SUCCESS;
}

ResultCode KConditionVariable::WaitForAddress(Handle handle, VAddr addr, u32 value) {
    Thread* cur_thread = kernel.CurrentScheduler()->GetCurrentThread();

    // Wait for the address.
    {
        std::shared_ptr<Thread> owner_thread;
        ASSERT(!owner_thread);
        {
            KScopedSchedulerLock sl(kernel);
            cur_thread->SetSyncedObject(nullptr, RESULT_SUCCESS);

            // Check if the thread should terminate.
            R_UNLESS(!cur_thread->IsTerminationRequested(), Svc::ResultTerminationRequested);

            {
                // Read the tag from userspace.
                u32 test_tag{};
                R_UNLESS(ReadFromUser(system, std::addressof(test_tag), addr),
                         Svc::ResultInvalidCurrentMemory);

                // If the tag isn't the handle (with wait mask), we're done.
                R_UNLESS(test_tag == (handle | Svc::HandleWaitMask), RESULT_SUCCESS);

                // Get the lock owner thread.
                owner_thread = kernel.CurrentProcess()->GetHandleTable().Get<Thread>(handle);
                R_UNLESS(owner_thread, Svc::ResultInvalidHandle);

                // Update the lock.
                cur_thread->SetAddressKey(addr, value);
                owner_thread->AddWaiter(cur_thread);
                cur_thread->SetState(ThreadState::Waiting);
                cur_thread->SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::ConditionVar);
                cur_thread->SetMutexWaitAddressForDebugging(addr);
            }
        }
        ASSERT(owner_thread);
    }

    // Remove the thread as a waiter from the lock owner.
    {
        KScopedSchedulerLock sl(kernel);
        Thread* owner_thread = cur_thread->GetLockOwner();
        if (owner_thread != nullptr) {
            owner_thread->RemoveWaiter(cur_thread);
        }
    }

    // Get the wait result.
    KSynchronizationObject* dummy{};
    return cur_thread->GetWaitResult(std::addressof(dummy));
}

Thread* KConditionVariable::SignalImpl(Thread* thread) {
    // Check pre-conditions.
    ASSERT(kernel.GlobalSchedulerContext().IsLocked());

    // Update the tag.
    VAddr address = thread->GetAddressKey();
    u32 own_tag = thread->GetAddressKeyValue();

    u32 prev_tag{};
    bool can_access{};
    {
        // TODO(bunnei): We should disable interrupts here via KScopedInterruptDisable.
        // TODO(bunnei): We should call CanAccessAtomic(..) here.
        can_access = true;
        if (can_access) {
            UpdateLockAtomic(system, std::addressof(prev_tag), address, own_tag,
                             Svc::HandleWaitMask);
        }
    }

    Thread* thread_to_close = nullptr;
    if (can_access) {
        if (prev_tag == InvalidHandle) {
            // If nobody held the lock previously, we're all good.
            thread->SetSyncedObject(nullptr, RESULT_SUCCESS);
            thread->Wakeup();
        } else {
            // Get the previous owner.
            auto owner_thread = kernel.CurrentProcess()->GetHandleTable().Get<Thread>(
                prev_tag & ~Svc::HandleWaitMask);

            if (owner_thread) {
                // Add the thread as a waiter on the owner.
                owner_thread->AddWaiter(thread);
                thread_to_close = owner_thread.get();
            } else {
                // The lock was tagged with a thread that doesn't exist.
                thread->SetSyncedObject(nullptr, Svc::ResultInvalidState);
                thread->Wakeup();
            }
        }
    } else {
        // If the address wasn't accessible, note so.
        thread->SetSyncedObject(nullptr, Svc::ResultInvalidCurrentMemory);
        thread->Wakeup();
    }

    return thread_to_close;
}

void KConditionVariable::Signal(u64 cv_key, s32 count) {
    // Prepare for signaling.
    constexpr int MaxThreads = 16;

    // TODO(bunnei): This should just be Thread once we implement KAutoObject instead of using
    // std::shared_ptr.
    std::vector<std::shared_ptr<Thread>> thread_list;
    std::array<Thread*, MaxThreads> thread_array;
    s32 num_to_close{};

    // Perform signaling.
    s32 num_waiters{};
    {
        KScopedSchedulerLock sl(kernel);

        auto it = thread_tree.nfind_light({cv_key, -1});
        while ((it != thread_tree.end()) && (count <= 0 || num_waiters < count) &&
               (it->GetConditionVariableKey() == cv_key)) {
            Thread* target_thread = std::addressof(*it);

            if (Thread* thread = SignalImpl(target_thread); thread != nullptr) {
                if (num_to_close < MaxThreads) {
                    thread_array[num_to_close++] = thread;
                } else {
                    thread_list.push_back(SharedFrom(thread));
                }
            }

            it = thread_tree.erase(it);
            target_thread->ClearConditionVariable();
            ++num_waiters;
        }

        // If we have no waiters, clear the has waiter flag.
        if (it == thread_tree.end() || it->GetConditionVariableKey() != cv_key) {
            const u32 has_waiter_flag{};
            WriteToUser(system, cv_key, std::addressof(has_waiter_flag));
        }
    }

    // Close threads in the array.
    for (auto i = 0; i < num_to_close; ++i) {
        thread_array[i]->Close();
    }

    // Close threads in the list.
    for (auto it = thread_list.begin(); it != thread_list.end(); it = thread_list.erase(it)) {
        (*it)->Close();
    }
}

ResultCode KConditionVariable::Wait(VAddr addr, u64 key, u32 value, s64 timeout) {
    // Prepare to wait.
    Thread* cur_thread = kernel.CurrentScheduler()->GetCurrentThread();
    Handle timer = InvalidHandle;

    {
        KScopedSchedulerLockAndSleep slp(kernel, timer, cur_thread, timeout);

        // Set the synced object.
        cur_thread->SetSyncedObject(nullptr, Svc::ResultTimedOut);

        // Check that the thread isn't terminating.
        if (cur_thread->IsTerminationRequested()) {
            slp.CancelSleep();
            return Svc::ResultTerminationRequested;
        }

        // Update the value and process for the next owner.
        {
            // Remove waiter thread.
            s32 num_waiters{};
            Thread* next_owner_thread =
                cur_thread->RemoveWaiterByKey(std::addressof(num_waiters), addr);

            // Update for the next owner thread.
            u32 next_value{};
            if (next_owner_thread != nullptr) {
                // Get the next tag value.
                next_value = next_owner_thread->GetAddressKeyValue();
                if (num_waiters > 1) {
                    next_value |= Svc::HandleWaitMask;
                }

                // Wake up the next owner.
                next_owner_thread->SetSyncedObject(nullptr, RESULT_SUCCESS);
                next_owner_thread->Wakeup();
            }

            // Write to the cv key.
            {
                const u32 has_waiter_flag = 1;
                WriteToUser(system, key, std::addressof(has_waiter_flag));
                // TODO(bunnei): We should call DataMemoryBarrier(..) here.
            }

            // Write the value to userspace.
            if (!WriteToUser(system, addr, std::addressof(next_value))) {
                slp.CancelSleep();
                return Svc::ResultInvalidCurrentMemory;
            }
        }

        // Update condition variable tracking.
        {
            cur_thread->SetConditionVariable(std::addressof(thread_tree), addr, key, value);
            thread_tree.insert(*cur_thread);
        }

        // If the timeout is non-zero, set the thread as waiting.
        if (timeout != 0) {
            cur_thread->SetState(ThreadState::Waiting);
            cur_thread->SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::ConditionVar);
            cur_thread->SetMutexWaitAddressForDebugging(addr);
        }
    }

    // Cancel the timer wait.
    if (timer != InvalidHandle) {
        auto& time_manager = kernel.TimeManager();
        time_manager.UnscheduleTimeEvent(timer);
    }

    // Remove from the condition variable.
    {
        KScopedSchedulerLock sl(kernel);

        if (Thread* owner = cur_thread->GetLockOwner(); owner != nullptr) {
            owner->RemoveWaiter(cur_thread);
        }

        if (cur_thread->IsWaitingForConditionVariable()) {
            thread_tree.erase(thread_tree.iterator_to(*cur_thread));
            cur_thread->ClearConditionVariable();
        }
    }

    // Get the result.
    KSynchronizationObject* dummy{};
    return cur_thread->GetWaitResult(std::addressof(dummy));
}

} // namespace Kernel
