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

#include "Common/Assert.h"
#include "Common/ChunkFile.h"
#include "Common/CommonTypes.h"
#include "Common/FPURoundMode.h"
#include "Common/Logging/Log.h"
#include "Common/MathUtil.h"

#include "Core/ConfigManager.h"
#include "Core/HW/CPU.h"
#include "Core/HW/Memmap.h"
#include "Core/HW/SystemTimers.h"
#include "Core/Host.h"
#include "Core/PowerPC/CPUCoreBase.h"
#include "Core/PowerPC/Interpreter/Interpreter.h"
#include "Core/PowerPC/JitInterface.h"
#include "Core/PowerPC/PPCTables.h"
#include "Core/PowerPC/PowerPC.h"

namespace PowerPC
{
// STATE_TO_SAVE
PowerPCState ppcState;

static CPUCoreBase* s_cpu_core_base = nullptr;
static bool s_cpu_core_base_is_injected = false;
Interpreter* const s_interpreter = Interpreter::getInstance();
static CoreMode s_mode = MODE_INTERPRETER;

Watches watches;
BreakPoints breakpoints;
MemChecks memchecks;
PPCDebugInterface debug_interface;

u32 CompactCR()
{
  u32 new_cr = 0;
  for (int i = 0; i < 8; i++)
  {
    new_cr |= GetCRField(i) << (28 - i * 4);
  }
  return new_cr;
}

void ExpandCR(u32 cr)
{
  for (int i = 0; i < 8; i++)
  {
    SetCRField(i, (cr >> (28 - i * 4)) & 0xF);
  }
}

void DoState(PointerWrap& p)
{
  // some of this code has been disabled, because
  // it changes registers even in MODE_MEASURE (which is suspicious and seems like it could cause
  // desyncs)
  // and because the values it's changing have been added to CoreTiming::DoState, so it might
  // conflict to mess with them here.

  // rSPR(SPR_DEC) = SystemTimers::GetFakeDecrementer();
  // *((u64 *)&TL) = SystemTimers::GetFakeTimeBase(); //works since we are little endian and TL
  // comes first :)

  p.DoPOD(ppcState);

  // SystemTimers::DecrementerSet();
  // SystemTimers::TimeBaseSet();

  JitInterface::DoState(p);
}

static void ResetRegisters()
{
  memset(ppcState.ps, 0, sizeof(ppcState.ps));
  memset(ppcState.gpr, 0, sizeof(ppcState.gpr));
  memset(ppcState.spr, 0, sizeof(ppcState.spr));
  /*
  0x00080200 = lonestar 2.0
  0x00088202 = lonestar 2.2
  0x70000100 = gekko 1.0
  0x00080100 = gekko 2.0
  0x00083203 = gekko 2.3a
  0x00083213 = gekko 2.3b
  0x00083204 = gekko 2.4
  0x00083214 = gekko 2.4e (8SE) - retail HW2
  */
  ppcState.spr[SPR_PVR] = 0x00083214;
  ppcState.spr[SPR_HID1] = 0x80000000;  // We're running at 3x the bus clock
  ppcState.spr[SPR_ECID_U] = 0x0d96e200;
  ppcState.spr[SPR_ECID_M] = 0x1840c00d;
  ppcState.spr[SPR_ECID_L] = 0x82bb08e8;

  ppcState.fpscr = 0;
  ppcState.pc = 0;
  ppcState.npc = 0;
  ppcState.Exceptions = 0;
  for (auto& v : ppcState.cr_val)
    v = 0x8000000000000001;

  TL = 0;
  TU = 0;
  SystemTimers::TimeBaseSet();

  // MSR should be 0x40, but we don't emulate BS1, so it would never be turned off :}
  ppcState.msr = 0;
  rDEC = 0xFFFFFFFF;
  SystemTimers::DecrementerSet();
}

void Init(int cpu_core)
{
  // NOTE: This function runs on EmuThread, not the CPU Thread.
  //   Changing the rounding mode has a limited effect.
  FPURoundMode::SetPrecisionMode(FPURoundMode::PREC_53);

  memset(ppcState.sr, 0, sizeof(ppcState.sr));
  ppcState.pagetable_base = 0;
  ppcState.pagetable_hashmask = 0;

  for (int tlb = 0; tlb < 2; tlb++)
  {
    for (int set = 0; set < 64; set++)
    {
      ppcState.tlb[tlb][set].recent = 0;
      for (int way = 0; way < 2; way++)
      {
        ppcState.tlb[tlb][set].paddr[way] = 0;
        ppcState.tlb[tlb][set].pte[way] = 0;
        ppcState.tlb[tlb][set].tag[way] = TLB_TAG_INVALID;
      }
    }
  }

  ResetRegisters();
  PPCTables::InitTables(cpu_core);

  // We initialize the interpreter because
  // it is used on boot and code window independently.
  s_interpreter->Init();

  switch (cpu_core)
  {
  case PowerPC::CORE_INTERPRETER:
    s_cpu_core_base = s_interpreter;
    break;

  default:
    s_cpu_core_base = JitInterface::InitJitCore(cpu_core);
    if (!s_cpu_core_base)  // Handle Situations where JIT core isn't available
    {
      WARN_LOG(POWERPC, "Jit core %d not available. Defaulting to interpreter.", cpu_core);
      s_cpu_core_base = s_interpreter;
    }
    break;
  }

  if (s_cpu_core_base != s_interpreter)
  {
    s_mode = MODE_JIT;
  }
  else
  {
    s_mode = MODE_INTERPRETER;
  }

  ppcState.iCache.Init();

  if (SConfig::GetInstance().bEnableDebugging)
    breakpoints.ClearAllTemporary();
}

void Shutdown()
{
  InjectExternalCPUCore(nullptr);
  JitInterface::Shutdown();
  s_interpreter->Shutdown();
  s_cpu_core_base = nullptr;
}

CoreMode GetMode()
{
  return !s_cpu_core_base_is_injected ? s_mode : MODE_INTERPRETER;
}

static void ApplyMode()
{
  switch (s_mode)
  {
  case MODE_INTERPRETER:  // Switching from JIT to interpreter
    s_cpu_core_base = s_interpreter;
    break;

  case MODE_JIT:  // Switching from interpreter to JIT.
    // Don't really need to do much. It'll work, the cache will refill itself.
    s_cpu_core_base = JitInterface::GetCore();
    if (!s_cpu_core_base)  // Has a chance to not get a working JIT core if one isn't active on host
      s_cpu_core_base = s_interpreter;
    break;
  }
}

void SetMode(CoreMode new_mode)
{
  if (new_mode == s_mode)
    return;  // We don't need to do anything.

  s_mode = new_mode;

  // If we're using an external CPU core implementation then don't do anything.
  if (s_cpu_core_base_is_injected)
    return;

  ApplyMode();
}

const char* GetCPUName()
{
  return s_cpu_core_base->GetName();
}

void InjectExternalCPUCore(CPUCoreBase* new_cpu)
{
  // Previously injected.
  if (s_cpu_core_base_is_injected)
    s_cpu_core_base->Shutdown();

  // nullptr means just remove
  if (!new_cpu)
  {
    if (s_cpu_core_base_is_injected)
    {
      s_cpu_core_base_is_injected = false;
      ApplyMode();
    }
    return;
  }

  new_cpu->Init();
  s_cpu_core_base = new_cpu;
  s_cpu_core_base_is_injected = true;
}

void SingleStep()
{
  s_cpu_core_base->SingleStep();
}

void RunLoop()
{
  Host_UpdateDisasmDialog();
  s_cpu_core_base->Run();
  Host_UpdateDisasmDialog();
}

void UpdatePerformanceMonitor(u32 cycles, u32 num_load_stores, u32 num_fp_inst)
{
  switch (MMCR0.PMC1SELECT)
  {
  case 0:  // No change
    break;
  case 1:  // Processor cycles
    PowerPC::ppcState.spr[SPR_PMC1] += cycles;
    break;
  default:
    break;
  }

  switch (MMCR0.PMC2SELECT)
  {
  case 0:  // No change
    break;
  case 1:  // Processor cycles
    PowerPC::ppcState.spr[SPR_PMC2] += cycles;
    break;
  case 11:  // Number of loads and stores completed
    PowerPC::ppcState.spr[SPR_PMC2] += num_load_stores;
    break;
  default:
    break;
  }

  switch (MMCR1.PMC3SELECT)
  {
  case 0:  // No change
    break;
  case 1:  // Processor cycles
    PowerPC::ppcState.spr[SPR_PMC3] += cycles;
    break;
  case 11:  // Number of FPU instructions completed
    PowerPC::ppcState.spr[SPR_PMC3] += num_fp_inst;
    break;
  default:
    break;
  }

  switch (MMCR1.PMC4SELECT)
  {
  case 0:  // No change
    break;
  case 1:  // Processor cycles
    PowerPC::ppcState.spr[SPR_PMC4] += cycles;
    break;
  default:
    break;
  }

  if ((MMCR0.PMC1INTCONTROL && (PowerPC::ppcState.spr[SPR_PMC1] & 0x80000000) != 0) ||
      (MMCR0.PMCINTCONTROL && (PowerPC::ppcState.spr[SPR_PMC2] & 0x80000000) != 0) ||
      (MMCR0.PMCINTCONTROL && (PowerPC::ppcState.spr[SPR_PMC3] & 0x80000000) != 0) ||
      (MMCR0.PMCINTCONTROL && (PowerPC::ppcState.spr[SPR_PMC4] & 0x80000000) != 0))
    PowerPC::ppcState.Exceptions |= EXCEPTION_PERFORMANCE_MONITOR;
}

void CheckExceptions()
{
  u32 exceptions = ppcState.Exceptions;

  // Example procedure:
  // set SRR0 to either PC or NPC
  // SRR0 = NPC;
  // save specified MSR bits
  // SRR1 = MSR & 0x87C0FFFF;
  // copy ILE bit to LE
  // MSR |= (MSR >> 16) & 1;
  // clear MSR as specified
  // MSR &= ~0x04EF36; // 0x04FF36 also clears ME (only for machine check exception)
  // set to exception type entry point
  // NPC = 0x00000x00;

  // TODO(delroth): Exception priority is completely wrong here: depending on
  // the instruction class, exceptions should be executed in a given order,
  // which is very different from the one arbitrarily chosen here. See §6.1.5
  // in 6xx_pem.pdf.

  if (exceptions & EXCEPTION_ISI)
  {
    SRR0 = NPC;
    // Page fault occurred
    SRR1 = (MSR & 0x87C0FFFF) | (1 << 30);
    MSR |= (MSR >> 16) & 1;
    MSR &= ~0x04EF36;
    PC = NPC = 0x00000400;

    INFO_LOG(POWERPC, "EXCEPTION_ISI");
    ppcState.Exceptions &= ~EXCEPTION_ISI;
  }
  else if (exceptions & EXCEPTION_PROGRAM)
  {
    SRR0 = PC;
    // say that it's a trap exception
    SRR1 = (MSR & 0x87C0FFFF) | 0x20000;
    MSR |= (MSR >> 16) & 1;
    MSR &= ~0x04EF36;
    PC = NPC = 0x00000700;

    INFO_LOG(POWERPC, "EXCEPTION_PROGRAM");
    ppcState.Exceptions &= ~EXCEPTION_PROGRAM;
  }
  else if (exceptions & EXCEPTION_SYSCALL)
  {
    SRR0 = NPC;
    SRR1 = MSR & 0x87C0FFFF;
    MSR |= (MSR >> 16) & 1;
    MSR &= ~0x04EF36;
    PC = NPC = 0x00000C00;

    INFO_LOG(POWERPC, "EXCEPTION_SYSCALL (PC=%08x)", PC);
    ppcState.Exceptions &= ~EXCEPTION_SYSCALL;
  }
  else if (exceptions & EXCEPTION_FPU_UNAVAILABLE)
  {
    // This happens a lot - GameCube OS uses deferred FPU context switching
    SRR0 = PC;  // re-execute the instruction
    SRR1 = MSR & 0x87C0FFFF;
    MSR |= (MSR >> 16) & 1;
    MSR &= ~0x04EF36;
    PC = NPC = 0x00000800;

    INFO_LOG(POWERPC, "EXCEPTION_FPU_UNAVAILABLE");
    ppcState.Exceptions &= ~EXCEPTION_FPU_UNAVAILABLE;
  }
#ifdef ENABLE_MEM_CHECK
  else if (exceptions & EXCEPTION_FAKE_MEMCHECK_HIT)
  {
    ppcState.Exceptions &= ~EXCEPTION_DSI & ~EXCEPTION_FAKE_MEMCHECK_HIT;
  }
#endif
  else if (exceptions & EXCEPTION_DSI)
  {
    SRR0 = PC;
    SRR1 = MSR & 0x87C0FFFF;
    MSR |= (MSR >> 16) & 1;
    MSR &= ~0x04EF36;
    PC = NPC = 0x00000300;
    // DSISR and DAR regs are changed in GenerateDSIException()

    INFO_LOG(POWERPC, "EXCEPTION_DSI");
    ppcState.Exceptions &= ~EXCEPTION_DSI;
  }
  else if (exceptions & EXCEPTION_ALIGNMENT)
  {
    // This never happens ATM
    // perhaps we can get dcb* instructions to use this :p
    SRR0 = PC;
    SRR1 = MSR & 0x87C0FFFF;
    MSR |= (MSR >> 16) & 1;
    MSR &= ~0x04EF36;
    PC = NPC = 0x00000600;

    // TODO crazy amount of DSISR options to check out

    INFO_LOG(POWERPC, "EXCEPTION_ALIGNMENT");
    ppcState.Exceptions &= ~EXCEPTION_ALIGNMENT;
  }

  // EXTERNAL INTERRUPT
  else
  {
    CheckExternalExceptions();
  }
}

void CheckExternalExceptions()
{
  u32 exceptions = ppcState.Exceptions;

  // EXTERNAL INTERRUPT
  if (exceptions && (MSR & 0x0008000))  // Handling is delayed until MSR.EE=1.
  {
    if (exceptions & EXCEPTION_EXTERNAL_INT)
    {
      // Pokemon gets this "too early", it hasn't a handler yet
      SRR0 = NPC;
      SRR1 = MSR & 0x87C0FFFF;
      MSR |= (MSR >> 16) & 1;
      MSR &= ~0x04EF36;
      PC = NPC = 0x00000500;

      INFO_LOG(POWERPC, "EXCEPTION_EXTERNAL_INT");
      ppcState.Exceptions &= ~EXCEPTION_EXTERNAL_INT;

      _dbg_assert_msg_(POWERPC, (SRR1 & 0x02) != 0, "EXTERNAL_INT unrecoverable???");
    }
    else if (exceptions & EXCEPTION_PERFORMANCE_MONITOR)
    {
      SRR0 = NPC;
      SRR1 = MSR & 0x87C0FFFF;
      MSR |= (MSR >> 16) & 1;
      MSR &= ~0x04EF36;
      PC = NPC = 0x00000F00;

      INFO_LOG(POWERPC, "EXCEPTION_PERFORMANCE_MONITOR");
      ppcState.Exceptions &= ~EXCEPTION_PERFORMANCE_MONITOR;
    }
    else if (exceptions & EXCEPTION_DECREMENTER)
    {
      SRR0 = NPC;
      SRR1 = MSR & 0x87C0FFFF;
      MSR |= (MSR >> 16) & 1;
      MSR &= ~0x04EF36;
      PC = NPC = 0x00000900;

      INFO_LOG(POWERPC, "EXCEPTION_DECREMENTER");
      ppcState.Exceptions &= ~EXCEPTION_DECREMENTER;
    }
    else
    {
      _dbg_assert_msg_(POWERPC, 0, "Unknown EXT interrupt: Exceptions == %08x", exceptions);
      ERROR_LOG(POWERPC, "Unknown EXTERNAL INTERRUPT exception: Exceptions == %08x", exceptions);
    }
  }
}

void CheckBreakPoints()
{
  if (PowerPC::breakpoints.IsAddressBreakPoint(PC))
  {
    CPU::Break();
    if (PowerPC::breakpoints.IsTempBreakPoint(PC))
      PowerPC::breakpoints.Remove(PC);
  }
}

}  // namespace

// FPSCR update functions

void UpdateFPRF(double dvalue)
{
  FPSCR.FPRF = MathUtil::ClassifyDouble(dvalue);
  // if (FPSCR.FPRF == 0x11)
  //	PanicAlert("QNAN alert");
}
