#include <stdint.h>
#include "rsp.hpp"

#include "Rsp_#1.1.h"
#include "m64p_plugin.h"

#define RSP_PARALLEL_VERSION 0x0101
#define RSP_PLUGIN_API_VERSION 0x020000

namespace RSP
{
   RSP_INFO rsp;
   CPU cpu;
}

extern "C" {

#ifdef INTENSE_DEBUG
// Need super-fast hash here.
static uint64_t hash_imem(const uint8_t *data, size_t size)
{
   uint64_t h = 0xcbf29ce484222325ull;
   size_t i;
   for (i = 0; i < size; i++)
      h = (h * 0x100000001b3ull) ^ data[i];
   return h;
}

void log_rsp_mem_parallel(void)
{
    fprintf(stderr, "IMEM HASH: 0x%016llx\n", hash_imem(RSP::rsp.IMEM, 0x1000));
    fprintf(stderr, "DMEM HASH: 0x%016llx\n", hash_imem(RSP::rsp.DMEM, 0x1000));
}
#endif

EXPORT unsigned int CALL parallelRSPDoRspCycles(unsigned int cycles)
{
   if (*RSP::rsp.SP_STATUS_REG & (SP_STATUS_HALT | SP_STATUS_BROKE))
      return 0;

   // We don't know if Mupen from the outside invalidated our IMEM.
   RSP::cpu.invalidate_imem();

   // Run CPU until we either break or we need to fire an IRQ.
   RSP::cpu.get_state().pc = *RSP::rsp.SP_PC_REG & 0xfff;

#ifdef INTENSE_DEBUG
   fprintf(stderr, "RUN TASK: %u\n", RSP::cpu.get_state().pc);
   log_rsp_mem_parallel();
#endif

   while (!(*RSP::rsp.SP_STATUS_REG & SP_STATUS_HALT))
   {
      auto mode = RSP::cpu.run();
      if (mode == RSP::MODE_CHECK_FLAGS && (*RSP::cpu.get_state().cp0.irq & 1))
         break;
   }

   *RSP::rsp.SP_PC_REG = 0x04001000 | RSP::cpu.get_state().pc;
   if (*RSP::cpu.get_state().cp0.irq & 1)
   {
#ifdef INTENSE_DEBUG
      fprintf(stderr, "CHECK IRQ\n");
#endif
      RSP::rsp.CheckInterrupts();
   }

   // Clear halt flag unless we broke, is this right?
   if (!(*RSP::rsp.SP_STATUS_REG & SP_STATUS_BROKE))
      *RSP::rsp.SP_STATUS_REG &= ~SP_STATUS_HALT;

   return cycles;
}

EXPORT m64p_error CALL parallelRSPPluginGetVersion(m64p_plugin_type *PluginType, int *PluginVersion, int *APIVersion, const char **PluginNamePtr, int *Capabilities)
{
    /* set version info */
    if (PluginType != NULL)
        *PluginType = M64PLUGIN_RSP;

    if (PluginVersion != NULL)
        *PluginVersion = RSP_PARALLEL_VERSION;

    if (APIVersion != NULL)
        *APIVersion = RSP_PLUGIN_API_VERSION;

    if (Capabilities != NULL)
        *Capabilities = 0;

    return M64ERR_SUCCESS;
}

EXPORT void CALL parallelRSPRomClosed(void)
{
   *RSP::rsp.SP_PC_REG = 0x00000000;
}

EXPORT void CALL parallelRSPInitiateRSP(RSP_INFO Rsp_Info, unsigned int *CycleCount)
{
   if (CycleCount)
      *CycleCount = 0;

   if (Rsp_Info.DMEM == Rsp_Info.IMEM) /* usually dummy RSP data for testing */
      return; /* DMA is not executed just because plugin initiates. */

   RSP::rsp = Rsp_Info;
   *RSP::rsp.SP_PC_REG = 0x04001000 & 0x00000FFF; /* task init bug on Mupen64 */

   auto **cr = RSP::cpu.get_state().cp0.cr;
   cr[0x0] = RSP::rsp.SP_MEM_ADDR_REG;
   cr[0x1] = RSP::rsp.SP_DRAM_ADDR_REG;
   cr[0x2] = RSP::rsp.SP_RD_LEN_REG;
   cr[0x3] = RSP::rsp.SP_WR_LEN_REG;
   cr[0x4] = RSP::rsp.SP_STATUS_REG;
   cr[0x5] = RSP::rsp.SP_DMA_FULL_REG;
   cr[0x6] = RSP::rsp.SP_DMA_BUSY_REG;
   cr[0x7] = RSP::rsp.SP_SEMAPHORE_REG;
   cr[0x8] = RSP::rsp.DPC_START_REG;
   cr[0x9] = RSP::rsp.DPC_END_REG;
   cr[0xA] = RSP::rsp.DPC_CURRENT_REG;
   cr[0xB] = RSP::rsp.DPC_STATUS_REG;
   cr[0xC] = RSP::rsp.DPC_CLOCK_REG;
   cr[0xD] = RSP::rsp.DPC_BUFBUSY_REG;
   cr[0xE] = RSP::rsp.DPC_PIPEBUSY_REG;
   cr[0xF] = RSP::rsp.DPC_TMEM_REG;

   *cr[RSP::CP0_REGISTER_SP_STATUS] = SP_STATUS_HALT;
   RSP::cpu.get_state().cp0.irq = RSP::rsp.MI_INTR_REG;

   RSP::cpu.set_dmem(reinterpret_cast<uint32_t*>(Rsp_Info.DMEM));
   RSP::cpu.set_imem(reinterpret_cast<uint32_t*>(Rsp_Info.IMEM));
   RSP::cpu.set_rdram(reinterpret_cast<uint32_t*>(Rsp_Info.RDRAM));
}

}

