// Copyright 2008 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "Core/HW/CPU.h"

#include <condition_variable>
#include <mutex>
#include <queue>

#include "AudioCommon/AudioCommon.h"
#include "Common/CommonTypes.h"
#include "Common/Event.h"
#include "Core/Core.h"
#include "Core/Host.h"
#include "Core/PowerPC/GDBStub.h"
#include "Core/PowerPC/PowerPC.h"
#include "VideoCommon/Fifo.h"

namespace CPU
{
// CPU Thread execution state.
// Requires s_state_change_lock to modify the value.
// Read access is unsynchronized.
static State s_state = State::PowerDown;

// Synchronizes EnableStepping and PauseAndLock so only one instance can be
// active at a time. Simplifies code by eliminating several edge cases where
// the EnableStepping(true)/PauseAndLock(true) case must release the state lock
// and wait for the CPU Thread which would otherwise require additional flags.
// NOTE: When using the stepping lock, it must always be acquired first. If
//   the lock is acquired after the state lock then that is guaranteed to
//   deadlock because of the order inversion. (A -> X,Y; B -> Y,X; A waits for
//   B, B waits for A)
static std::mutex s_stepping_lock;

// Primary lock. Protects changing s_state, requesting instruction stepping and
// pause-and-locking.
static std::mutex s_state_change_lock;
// When s_state_cpu_thread_active changes to false
static std::condition_variable s_state_cpu_idle_cvar;
// When s_state changes / s_state_paused_and_locked becomes false (for CPU Thread only)
static std::condition_variable s_state_cpu_cvar;
static bool s_state_cpu_thread_active = false;
static bool s_state_paused_and_locked = false;
static bool s_state_system_request_stepping = false;
static bool s_state_cpu_step_instruction = false;
static Common::Event* s_state_cpu_step_instruction_sync = nullptr;
static std::queue<std::function<void()>> s_pending_jobs;

void Init(PowerPC::CPUCore cpu_core)
{
  PowerPC::Init(cpu_core);
  s_state = State::Stepping;
}

void Shutdown()
{
  Stop();
  PowerPC::Shutdown();
}

// Requires holding s_state_change_lock
static void FlushStepSyncEventLocked()
{
  if (!s_state_cpu_step_instruction)
    return;

  if (s_state_cpu_step_instruction_sync)
  {
    s_state_cpu_step_instruction_sync->Set();
    s_state_cpu_step_instruction_sync = nullptr;
  }
  s_state_cpu_step_instruction = false;
}

static void ExecutePendingJobs(std::unique_lock<std::mutex>& state_lock)
{
  while (!s_pending_jobs.empty())
  {
    auto callback = s_pending_jobs.front();
    s_pending_jobs.pop();
    state_lock.unlock();
    callback();
    state_lock.lock();
  }
}

void Run()
{
  // Updating the host CPU's rounding mode must be done on the CPU thread.
  // We can't rely on PowerPC::Init doing it, since it's called from EmuThread.
  PowerPC::RoundingModeUpdated();

  std::unique_lock state_lock(s_state_change_lock);
  while (s_state != State::PowerDown)
  {
    s_state_cpu_cvar.wait(state_lock, [] { return !s_state_paused_and_locked; });
    ExecutePendingJobs(state_lock);

    Common::Event gdb_step_sync_event;
    switch (s_state)
    {
    case State::Running:
      s_state_cpu_thread_active = true;
      state_lock.unlock();

      // Adjust PC for JIT when debugging
      // SingleStep so that the "continue", "step over" and "step out" debugger functions
      // work when the PC is at a breakpoint at the beginning of the block
      // If watchpoints are enabled, any instruction could be a breakpoint.
      if (PowerPC::GetMode() != PowerPC::CoreMode::Interpreter)
      {
        if (PowerPC::breakpoints.IsAddressBreakPoint(PC) || PowerPC::memchecks.HasAny())
        {
          s_state = State::Stepping;
          PowerPC::CoreMode old_mode = PowerPC::GetMode();
          PowerPC::SetMode(PowerPC::CoreMode::Interpreter);
          PowerPC::SingleStep();
          PowerPC::SetMode(old_mode);
          s_state = State::Running;
        }
      }

      // Enter a fast runloop
      PowerPC::RunLoop();

      state_lock.lock();
      s_state_cpu_thread_active = false;
      s_state_cpu_idle_cvar.notify_all();
      break;

    case State::Stepping:
      // Wait for step command.
      s_state_cpu_cvar.wait(state_lock, [&state_lock, &gdb_step_sync_event] {
        ExecutePendingJobs(state_lock);
        state_lock.unlock();
        if (GDBStub::IsActive() && GDBStub::HasControl())
        {
          GDBStub::SendSignal(GDBStub::Signal::Sigtrap);
          GDBStub::ProcessCommands(true);
          // If we are still going to step, emulate the fact we just sent a step command
          if (GDBStub::HasControl())
          {
            // Make sure the previous step by gdb was serviced
            if (s_state_cpu_step_instruction_sync &&
                s_state_cpu_step_instruction_sync != &gdb_step_sync_event)
              s_state_cpu_step_instruction_sync->Set();

            s_state_cpu_step_instruction = true;
            s_state_cpu_step_instruction_sync = &gdb_step_sync_event;
          }
        }
        state_lock.lock();
        return s_state_cpu_step_instruction || !IsStepping();
      });
      if (!IsStepping())
      {
        // Signal event if the mode changes.
        FlushStepSyncEventLocked();
        continue;
      }
      if (s_state_paused_and_locked)
        continue;

      // Do step
      s_state_cpu_thread_active = true;
      state_lock.unlock();

      PowerPC::SingleStep();

      state_lock.lock();
      s_state_cpu_thread_active = false;
      s_state_cpu_idle_cvar.notify_all();

      // Update disasm dialog
      FlushStepSyncEventLocked();
      Host_UpdateDisasmDialog();
      break;

    case State::PowerDown:
      break;
    }
  }
  state_lock.unlock();
  Host_UpdateDisasmDialog();
}

// Requires holding s_state_change_lock
static void RunAdjacentSystems(bool running)
{
  // NOTE: We're assuming these will not try to call Break or EnableStepping.
  Fifo::EmulatorState(running);
  // Core is responsible for shutting down the sound stream.
  if (s_state != State::PowerDown)
    AudioCommon::SetSoundStreamRunning(running);
}

void Stop()
{
  // Change state and wait for it to be acknowledged.
  // We don't need the stepping lock because State::PowerDown is a priority state which
  // will stick permanently.
  std::unique_lock state_lock(s_state_change_lock);
  s_state = State::PowerDown;
  s_state_cpu_cvar.notify_one();

  while (s_state_cpu_thread_active)
  {
    s_state_cpu_idle_cvar.wait(state_lock);
  }

  RunAdjacentSystems(false);
  FlushStepSyncEventLocked();
}

bool IsStepping()
{
  return s_state == State::Stepping;
}

State GetState()
{
  return s_state;
}

const State* GetStatePtr()
{
  return &s_state;
}

void Reset()
{
}

void StepOpcode(Common::Event* event)
{
  std::lock_guard state_lock(s_state_change_lock);
  // If we're not stepping then this is pointless
  if (!IsStepping())
  {
    if (event)
      event->Set();
    return;
  }

  // Potential race where the previous step has not been serviced yet.
  if (s_state_cpu_step_instruction_sync && s_state_cpu_step_instruction_sync != event)
    s_state_cpu_step_instruction_sync->Set();

  s_state_cpu_step_instruction = true;
  s_state_cpu_step_instruction_sync = event;
  s_state_cpu_cvar.notify_one();
}

// Requires s_state_change_lock
static bool SetStateLocked(State s)
{
  if (s_state == State::PowerDown)
    return false;
  s_state = s;
  return true;
}

void EnableStepping(bool stepping)
{
  std::lock_guard stepping_lock(s_stepping_lock);
  std::unique_lock state_lock(s_state_change_lock);

  if (stepping)
  {
    SetStateLocked(State::Stepping);

    while (s_state_cpu_thread_active)
    {
      s_state_cpu_idle_cvar.wait(state_lock);
    }

    RunAdjacentSystems(false);
  }
  else if (SetStateLocked(State::Running))
  {
    s_state_cpu_cvar.notify_one();
    RunAdjacentSystems(true);
  }
}

void Break()
{
  std::lock_guard state_lock(s_state_change_lock);

  // If another thread is trying to PauseAndLock then we need to remember this
  // for later to ignore the unpause_on_unlock.
  if (s_state_paused_and_locked)
  {
    s_state_system_request_stepping = true;
    return;
  }

  // We'll deadlock if we synchronize, the CPU may block waiting for our caller to
  // finish resulting in the CPU loop never terminating.
  SetStateLocked(State::Stepping);
  RunAdjacentSystems(false);
}

void Continue()
{
  CPU::EnableStepping(false);
  Core::CallOnStateChangedCallbacks(Core::State::Running);
}

bool PauseAndLock(bool do_lock, bool unpause_on_unlock, bool control_adjacent)
{
  // NOTE: This is protected by s_stepping_lock.
  static bool s_have_fake_cpu_thread = false;
  bool was_unpaused = false;

  if (do_lock)
  {
    s_stepping_lock.lock();

    std::unique_lock state_lock(s_state_change_lock);
    s_state_paused_and_locked = true;

    was_unpaused = s_state == State::Running;
    SetStateLocked(State::Stepping);

    while (s_state_cpu_thread_active)
    {
      s_state_cpu_idle_cvar.wait(state_lock);
    }

    if (control_adjacent)
      RunAdjacentSystems(false);
    state_lock.unlock();

    // NOTE: It would make more sense for Core::DeclareAsCPUThread() to keep a
    //   depth counter instead of being a boolean.
    if (!Core::IsCPUThread())
    {
      s_have_fake_cpu_thread = true;
      Core::DeclareAsCPUThread();
    }
  }
  else
  {
    // Only need the stepping lock for this
    if (s_have_fake_cpu_thread)
    {
      s_have_fake_cpu_thread = false;
      Core::UndeclareAsCPUThread();
    }

    {
      std::lock_guard state_lock(s_state_change_lock);
      if (s_state_system_request_stepping)
      {
        s_state_system_request_stepping = false;
      }
      else if (unpause_on_unlock && SetStateLocked(State::Running))
      {
        was_unpaused = true;
      }
      s_state_paused_and_locked = false;
      s_state_cpu_cvar.notify_one();

      if (control_adjacent)
        RunAdjacentSystems(s_state == State::Running);
    }
    s_stepping_lock.unlock();
  }
  return was_unpaused;
}

void AddCPUThreadJob(std::function<void()> function)
{
  std::unique_lock state_lock(s_state_change_lock);
  s_pending_jobs.push(std::move(function));
}

}  // namespace CPU
