// Copyright 2008 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.

#include <assert.h>
#include <cinttypes>
#include <string>

#include "Common/Assert.h"
#include "Common/CommonTypes.h"
#include "Common/GekkoDisassembler.h"
#include "Common/Logging/Log.h"
#include "Common/StringUtil.h"
#include "Core/ConfigManager.h"
#include "Core/CoreTiming.h"
#include "Core/Debugger/Debugger_SymbolMap.h"
#include "Core/HLE/HLE.h"
#include "Core/HW/CPU.h"
#include "Core/Host.h"
#include "Core/PowerPC/Interpreter/Interpreter.h"
#include "Core/PowerPC/PPCTables.h"
#include "Core/PowerPC/PowerPC.h"

#ifdef USE_GDBSTUB
#include "Core/PowerPC/GDBStub.h"
#endif

namespace
{
u32 last_pc;
}

bool Interpreter::m_EndBlock;

// function tables
Interpreter::Instruction Interpreter::m_opTable[64];
Interpreter::Instruction Interpreter::m_opTable4[1024];
Interpreter::Instruction Interpreter::m_opTable19[1024];
Interpreter::Instruction Interpreter::m_opTable31[1024];
Interpreter::Instruction Interpreter::m_opTable59[32];
Interpreter::Instruction Interpreter::m_opTable63[1024];

void Interpreter::RunTable4(UGeckoInstruction _inst)
{
  m_opTable4[_inst.SUBOP10](_inst);
}
void Interpreter::RunTable19(UGeckoInstruction _inst)
{
  m_opTable19[_inst.SUBOP10](_inst);
}
void Interpreter::RunTable31(UGeckoInstruction _inst)
{
  m_opTable31[_inst.SUBOP10](_inst);
}
void Interpreter::RunTable59(UGeckoInstruction _inst)
{
  m_opTable59[_inst.SUBOP5](_inst);
}
void Interpreter::RunTable63(UGeckoInstruction _inst)
{
  m_opTable63[_inst.SUBOP10](_inst);
}

void Interpreter::Init()
{
  g_bReserve = false;
  m_EndBlock = false;
}

void Interpreter::Shutdown()
{
}

static int startTrace = 0;

static void Trace(UGeckoInstruction& instCode)
{
  std::string regs = "";
  for (int i = 0; i < 32; i++)
  {
    regs += StringFromFormat("r%02d: %08x ", i, PowerPC::ppcState.gpr[i]);
  }

  std::string fregs = "";
  for (int i = 0; i < 32; i++)
  {
    fregs += StringFromFormat("f%02d: %08" PRIx64 " %08" PRIx64 " ", i, PowerPC::ppcState.ps[i][0],
                              PowerPC::ppcState.ps[i][1]);
  }

  std::string ppc_inst = GekkoDisassembler::Disassemble(instCode.hex, PC);
  DEBUG_LOG(POWERPC, "INTER PC: %08x SRR0: %08x SRR1: %08x CRval: %016lx FPSCR: %08x MSR: %08x LR: "
                     "%08x %s %08x %s",
            PC, SRR0, SRR1, (unsigned long)PowerPC::ppcState.cr_val[0], PowerPC::ppcState.fpscr,
            PowerPC::ppcState.msr, PowerPC::ppcState.spr[8], regs.c_str(), instCode.hex,
            ppc_inst.c_str());
}

int Interpreter::SingleStepInner()
{
  static UGeckoInstruction instCode;
  u32 function = HLE::GetFunctionIndex(PC);
  if (function != 0)
  {
    int type = HLE::GetFunctionTypeByIndex(function);
    if (type == HLE::HLE_HOOK_START || type == HLE::HLE_HOOK_REPLACE)
    {
      int flags = HLE::GetFunctionFlagsByIndex(function);
      if (HLE::IsEnabled(flags))
      {
        HLEFunction(function);
        if (type == HLE::HLE_HOOK_START)
        {
          // Run the original.
          function = 0;
        }
      }
      else
      {
        function = 0;
      }
    }
  }

  if (function == 0)
  {
#ifdef USE_GDBSTUB
    if (gdb_active() && gdb_bp_x(PC))
    {
      Host_UpdateDisasmDialog();

      gdb_signal(SIGTRAP);
      gdb_handle_exception();
    }
#endif

    NPC = PC + sizeof(UGeckoInstruction);
    instCode.hex = PowerPC::Read_Opcode(PC);

    // Uncomment to trace the interpreter
    // if ((PC & 0xffffff)>=0x0ab54c && (PC & 0xffffff)<=0x0ab624)
    //	startTrace = 1;
    // else
    //	startTrace = 0;

    if (startTrace)
    {
      Trace(instCode);
    }

    if (instCode.hex != 0)
    {
      UReg_MSR& msr = (UReg_MSR&)MSR;
      if (msr.FP)  // If FPU is enabled, just execute
      {
        m_opTable[instCode.OPCD](instCode);
        if (PowerPC::ppcState.Exceptions & EXCEPTION_DSI)
        {
          PowerPC::CheckExceptions();
          m_EndBlock = true;
        }
      }
      else
      {
        // check if we have to generate a FPU unavailable exception
        if (!PPCTables::UsesFPU(instCode))
        {
          m_opTable[instCode.OPCD](instCode);
          if (PowerPC::ppcState.Exceptions & EXCEPTION_DSI)
          {
            PowerPC::CheckExceptions();
            m_EndBlock = true;
          }
        }
        else
        {
          PowerPC::ppcState.Exceptions |= EXCEPTION_FPU_UNAVAILABLE;
          PowerPC::CheckExceptions();
          m_EndBlock = true;
        }
      }
    }
    else
    {
      // Memory exception on instruction fetch
      PowerPC::CheckExceptions();
      m_EndBlock = true;
    }
  }
  last_pc = PC;
  PC = NPC;

  GekkoOPInfo* opinfo = GetOpInfo(instCode);
  return opinfo->numCycles;
}

void Interpreter::SingleStep()
{
  SingleStepInner();

  CoreTiming::g_slicelength = 1;
  PowerPC::ppcState.downcount = 0;
  CoreTiming::Advance();

  if (PowerPC::ppcState.Exceptions)
  {
    PowerPC::CheckExceptions();
    PC = NPC;
  }
}

//#define SHOW_HISTORY
#ifdef SHOW_HISTORY
std::vector<int> PCVec;
std::vector<int> PCBlockVec;
int ShowBlocks = 30;
int ShowSteps = 300;
#endif

// FastRun - inspired by GCemu (to imitate the JIT so that they can be compared).
void Interpreter::Run()
{
  while (!CPU::GetState())
  {
    // we have to check exceptions at branches apparently (or maybe just rfi?)
    if (SConfig::GetInstance().bEnableDebugging)
    {
#ifdef SHOW_HISTORY
      PCBlockVec.push_back(PC);
      if (PCBlockVec.size() > ShowBlocks)
        PCBlockVec.erase(PCBlockVec.begin());
#endif

      // Debugging friendly version of inner loop. Tries to do the timing as similarly to the
      // JIT as possible. Does not take into account that some instructions take multiple cycles.
      while (PowerPC::ppcState.downcount > 0)
      {
        m_EndBlock = false;
        int i;
        for (i = 0; !m_EndBlock; i++)
        {
#ifdef SHOW_HISTORY
          PCVec.push_back(PC);
          if (PCVec.size() > ShowSteps)
            PCVec.erase(PCVec.begin());
#endif

          // 2: check for breakpoint
          if (PowerPC::breakpoints.IsAddressBreakPoint(PC))
          {
#ifdef SHOW_HISTORY
            NOTICE_LOG(POWERPC, "----------------------------");
            NOTICE_LOG(POWERPC, "Blocks:");
            for (int j = 0; j < PCBlockVec.size(); j++)
              NOTICE_LOG(POWERPC, "PC: 0x%08x", PCBlockVec.at(j));
            NOTICE_LOG(POWERPC, "----------------------------");
            NOTICE_LOG(POWERPC, "Steps:");
            for (int j = 0; j < PCVec.size(); j++)
            {
              // Write space
              if (j > 0)
              {
                if (PCVec.at(j) != PCVec.at(j - 1) + 4)
                  NOTICE_LOG(POWERPC, "");
              }

              NOTICE_LOG(POWERPC, "PC: 0x%08x", PCVec.at(j));
            }
#endif
            INFO_LOG(POWERPC, "Hit Breakpoint - %08x", PC);
            CPU::Break();
            if (PowerPC::breakpoints.IsTempBreakPoint(PC))
              PowerPC::breakpoints.Remove(PC);

            Host_UpdateDisasmDialog();
            return;
          }
          SingleStepInner();
        }
        PowerPC::ppcState.downcount -= i;
      }
    }
    else
    {
      // "fast" version of inner loop. well, it's not so fast.
      while (PowerPC::ppcState.downcount > 0)
      {
        m_EndBlock = false;

        int cycles = 0;
        while (!m_EndBlock)
        {
          cycles += SingleStepInner();
        }
        PowerPC::ppcState.downcount -= cycles;
      }
    }

    CoreTiming::Advance();
  }
}

void Interpreter::unknown_instruction(UGeckoInstruction _inst)
{
  std::string disasm = GekkoDisassembler::Disassemble(PowerPC::HostRead_U32(last_pc), last_pc);
  NOTICE_LOG(POWERPC, "Last PC = %08x : %s", last_pc, disasm.c_str());
  Dolphin_Debugger::PrintCallstack();
  NOTICE_LOG(POWERPC,
             "\nIntCPU: Unknown instruction %08x at PC = %08x  last_PC = %08x  LR = %08x\n",
             _inst.hex, PC, last_pc, LR);
  for (int i = 0; i < 32; i += 4)
    NOTICE_LOG(POWERPC, "r%d: 0x%08x r%d: 0x%08x r%d:0x%08x r%d: 0x%08x", i, rGPR[i], i + 1,
               rGPR[i + 1], i + 2, rGPR[i + 2], i + 3, rGPR[i + 3]);
  _assert_msg_(POWERPC, 0,
               "\nIntCPU: Unknown instruction %08x at PC = %08x  last_PC = %08x  LR = %08x\n",
               _inst.hex, PC, last_pc, LR);
}

void Interpreter::ClearCache()
{
  // Do nothing.
}

const char* Interpreter::GetName()
{
#ifdef _ARCH_64
  return "Interpreter64";
#else
  return "Interpreter32";
#endif
}

Interpreter* Interpreter::getInstance()
{
  static Interpreter instance;
  return &instance;
}
