#include "bus.h"
#include "cdrom.h"
#include "common/align.h"
#include "common/assert.h"
#include "common/log.h"
#include "common/state_wrapper.h"
#include "cpu_code_cache.h"
#include "cpu_core.h"
#include "cpu_disasm.h"
#include "dma.h"
#include "gpu.h"
#include "interrupt_controller.h"
#include "mdec.h"
#include "pad.h"
#include "sio.h"
#include "spu.h"
#include "timers.h"
#include <cstdio>
Log_SetChannel(Bus);

#define FIXUP_WORD_READ_OFFSET(offset) ((offset) & ~u32(3))
#define FIXUP_WORD_READ_VALUE(offset, value) ((value) >> (((offset)&u32(3)) * 8))

// Offset and value remapping for (w32) registers from nocash docs.
void FixupUnalignedWordAccessW32(u32& offset, u32& value)
{
  const u32 byte_offset = offset & u32(3);
  offset &= ~u32(3);
  value <<= byte_offset * 8;
}

Bus::Bus() = default;

Bus::~Bus() = default;

void Bus::Initialize(CPU::Core* cpu, CPU::CodeCache* cpu_code_cache, DMA* dma,
                     InterruptController* interrupt_controller, GPU* gpu, CDROM* cdrom, Pad* pad, Timers* timers,
                     SPU* spu, MDEC* mdec, SIO* sio)
{
  m_cpu = cpu;
  m_cpu_code_cache = cpu_code_cache;
  m_dma = dma;
  m_interrupt_controller = interrupt_controller;
  m_gpu = gpu;
  m_cdrom = cdrom;
  m_pad = pad;
  m_timers = timers;
  m_spu = spu;
  m_mdec = mdec;
  m_sio = sio;
}

void Bus::Reset()
{
  m_ram.fill(static_cast<u8>(0));
  m_MEMCTRL.exp1_base = 0x1F000000;
  m_MEMCTRL.exp2_base = 0x1F802000;
  m_MEMCTRL.exp1_delay_size.bits = 0x0013243F;
  m_MEMCTRL.exp3_delay_size.bits = 0x00003022;
  m_MEMCTRL.bios_delay_size.bits = 0x0013243F;
  m_MEMCTRL.spu_delay_size.bits = 0x200931E1;
  m_MEMCTRL.cdrom_delay_size.bits = 0x00020843;
  m_MEMCTRL.exp2_delay_size.bits = 0x00070777;
  m_MEMCTRL.common_delay.bits = 0x00031125;
  m_ram_size_reg = UINT32_C(0x00000B88);
  RecalculateMemoryTimings();
}

bool Bus::DoState(StateWrapper& sw)
{
  sw.Do(&m_exp1_access_time);
  sw.Do(&m_exp2_access_time);
  sw.Do(&m_bios_access_time);
  sw.Do(&m_cdrom_access_time);
  sw.Do(&m_spu_access_time);
  sw.DoBytes(m_ram.data(), m_ram.size());
  sw.DoBytes(m_bios.data(), m_bios.size());
  sw.DoArray(m_MEMCTRL.regs, countof(m_MEMCTRL.regs));
  sw.Do(&m_ram_size_reg);
  sw.Do(&m_tty_line_buffer);
  return !sw.HasError();
}

bool Bus::ReadByte(PhysicalMemoryAddress address, u8* value)
{
  u32 temp = 0;
  const bool result = DispatchAccess<MemoryAccessType::Read, MemoryAccessSize::Byte>(address, temp);
  *value = Truncate8(temp);
  return result;
}

bool Bus::ReadHalfWord(PhysicalMemoryAddress address, u16* value)
{
  u32 temp = 0;
  const bool result = DispatchAccess<MemoryAccessType::Read, MemoryAccessSize::HalfWord>(address, temp);
  *value = Truncate16(temp);
  return result;
}

bool Bus::ReadWord(PhysicalMemoryAddress address, u32* value)
{
  return DispatchAccess<MemoryAccessType::Read, MemoryAccessSize::Word>(address, *value);
}

bool Bus::WriteByte(PhysicalMemoryAddress address, u8 value)
{
  u32 temp = ZeroExtend32(value);
  return DispatchAccess<MemoryAccessType::Read, MemoryAccessSize::Byte>(address, temp);
}

bool Bus::WriteHalfWord(PhysicalMemoryAddress address, u16 value)
{
  u32 temp = ZeroExtend32(value);
  return DispatchAccess<MemoryAccessType::Read, MemoryAccessSize::HalfWord>(address, temp);
}

bool Bus::WriteWord(PhysicalMemoryAddress address, u32 value)
{
  return DispatchAccess<MemoryAccessType::Write, MemoryAccessSize::Word>(address, value);
}

TickCount Bus::ReadWords(PhysicalMemoryAddress address, u32* words, u32 word_count)
{
  if (address + (word_count * sizeof(u32)) > (RAM_BASE + RAM_SIZE))
  {
    // Not RAM, or RAM mirrors.
    TickCount total_ticks = 0;
    for (u32 i = 0; i < word_count; i++)
    {
      const TickCount ticks = DispatchAccess<MemoryAccessType::Read, MemoryAccessSize::Word>(address, words[i]);
      if (ticks < 0)
        return -1;

      total_ticks += ticks;
      address += sizeof(u32);
    }

    return total_ticks;
  }

  // DMA is using DRAM Hyper Page mode, allowing it to access DRAM rows at 1 clock cycle per word (effectively around 17
  // clks per 16 words, due to required row address loading, probably plus some further minimal overload due to refresh
  // cycles). This is making DMA much faster than CPU memory accesses (CPU DRAM access takes 1 opcode cycle plus 6
  // waitstates, ie. 7 cycles in total).
  std::memcpy(words, &m_ram[address], sizeof(u32) * word_count);
  return static_cast<TickCount>(word_count + ((word_count + 15) / 16));
}

TickCount Bus::WriteWords(PhysicalMemoryAddress address, const u32* words, u32 word_count)
{
  if (address + (word_count * sizeof(u32)) > (RAM_BASE + RAM_SIZE))
  {
    // Not RAM, or RAM mirrors.
    TickCount total_ticks = 0;
    for (u32 i = 0; i < word_count; i++)
    {
      u32 value = words[i];
      const TickCount ticks = DispatchAccess<MemoryAccessType::Write, MemoryAccessSize::Word>(address, value);
      if (ticks < 0)
        return -1;

      total_ticks += ticks;
      address += sizeof(u32);
    }

    return total_ticks;
  }

  const u32 start_page = address / CPU_CODE_CACHE_PAGE_SIZE;
  const u32 end_page = (address + word_count * sizeof(u32)) / CPU_CODE_CACHE_PAGE_SIZE;
  for (u32 page = start_page; page <= end_page; page++)
  {
    if (m_ram_code_bits[page])
      DoInvalidateCodeCache(page);
  }

  std::memcpy(&m_ram[address], words, sizeof(u32) * word_count);
  return static_cast<TickCount>(word_count + ((word_count + 15) / 16));
}

void Bus::SetExpansionROM(std::vector<u8> data)
{
  m_exp1_rom = std::move(data);
}

void Bus::SetBIOS(const std::vector<u8>& image)
{
  if (image.size() != static_cast<u32>(BIOS_SIZE))
  {
    Panic("Incorrect BIOS image size");
    return;
  }

  std::copy(image.cbegin(), image.cend(), m_bios.begin());
}

std::tuple<TickCount, TickCount, TickCount> Bus::CalculateMemoryTiming(MEMDELAY mem_delay, COMDELAY common_delay)
{
  // from nocash spec
  s32 first = 0, seq = 0, min = 0;
  if (mem_delay.use_com0_time)
  {
    first += s32(common_delay.com0) - 1;
    seq += s32(common_delay.com0) - 1;
  }
  if (mem_delay.use_com2_time)
  {
    first += s32(common_delay.com2);
    seq += s32(common_delay.com2);
  }
  if (mem_delay.use_com3_time)
  {
    min = s32(common_delay.com3);
  }
  if (first < 6)
    first++;

  first = first + s32(mem_delay.access_time) + 2;
  seq = seq + s32(mem_delay.access_time) + 2;

  if (first < (min + 6))
    first = min + 6;
  if (seq < (min + 2))
    seq = min + 2;

  const TickCount byte_access_time = first;
  const TickCount halfword_access_time = mem_delay.data_bus_16bit ? first : (first + seq);
  const TickCount word_access_time = mem_delay.data_bus_16bit ? (first + seq) : (first + seq + seq + seq);
  return std::tie(std::max(byte_access_time - 1, 0), std::max(halfword_access_time - 1, 0),
                  std::max(word_access_time - 1, 0));
}

void Bus::RecalculateMemoryTimings()
{
  std::tie(m_bios_access_time[0], m_bios_access_time[1], m_bios_access_time[2]) =
    CalculateMemoryTiming(m_MEMCTRL.bios_delay_size, m_MEMCTRL.common_delay);
  std::tie(m_cdrom_access_time[0], m_cdrom_access_time[1], m_cdrom_access_time[2]) =
    CalculateMemoryTiming(m_MEMCTRL.cdrom_delay_size, m_MEMCTRL.common_delay);
  std::tie(m_spu_access_time[0], m_spu_access_time[1], m_spu_access_time[2]) =
    CalculateMemoryTiming(m_MEMCTRL.spu_delay_size, m_MEMCTRL.common_delay);

  Log_TracePrintf("BIOS Memory Timing: %u bit bus, byte=%d, halfword=%d, word=%d",
                  m_MEMCTRL.bios_delay_size.data_bus_16bit ? 16 : 8, m_bios_access_time[0] + 1,
                  m_bios_access_time[1] + 1, m_bios_access_time[2] + 1);
  Log_TracePrintf("CDROM Memory Timing: %u bit bus, byte=%d, halfword=%d, word=%d",
                  m_MEMCTRL.cdrom_delay_size.data_bus_16bit ? 16 : 8, m_cdrom_access_time[0] + 1,
                  m_cdrom_access_time[1] + 1, m_cdrom_access_time[2] + 1);
  Log_TracePrintf("SPU Memory Timing: %u bit bus, byte=%d, halfword=%d, word=%d",
                  m_MEMCTRL.spu_delay_size.data_bus_16bit ? 16 : 8, m_spu_access_time[0] + 1, m_spu_access_time[1] + 1,
                  m_spu_access_time[2] + 1);
}

TickCount Bus::DoInvalidAccess(MemoryAccessType type, MemoryAccessSize size, PhysicalMemoryAddress address, u32& value)
{
  SmallString str;
  str.AppendString("Invalid bus ");
  if (size == MemoryAccessSize::Byte)
    str.AppendString("byte");
  if (size == MemoryAccessSize::HalfWord)
    str.AppendString("word");
  if (size == MemoryAccessSize::Word)
    str.AppendString("dword");
  str.AppendCharacter(' ');
  if (type == MemoryAccessType::Read)
    str.AppendString("read");
  else
    str.AppendString("write");

  str.AppendFormattedString(" at address 0x%08X", address);
  if (type == MemoryAccessType::Write)
    str.AppendFormattedString(" (value 0x%08X)", value);

  Log_ErrorPrint(str);
  if (type == MemoryAccessType::Read)
    value = UINT32_C(0xFFFFFFFF);

  return 1;
}

u32 Bus::DoReadEXP1(MemoryAccessSize size, u32 offset)
{
  if (m_exp1_rom.empty())
  {
    // EXP1 not present.
    return UINT32_C(0xFFFFFFFF);
  }

  if (offset == 0x20018)
  {
    // Bit 0 - Action Replay On/Off
    return UINT32_C(1);
  }

  const u32 transfer_size = u32(1) << static_cast<u32>(size);
  if ((offset + transfer_size) > m_exp1_rom.size())
  {
    return UINT32_C(0);
  }

  u32 value;
  if (size == MemoryAccessSize::Byte)
  {
    value = ZeroExtend32(m_exp1_rom[offset]);
  }
  else if (size == MemoryAccessSize::HalfWord)
  {
    u16 halfword;
    std::memcpy(&halfword, &m_exp1_rom[offset], sizeof(halfword));
    value = ZeroExtend32(halfword);
  }
  else
  {
    std::memcpy(&value, &m_exp1_rom[offset], sizeof(value));
  }

  // Log_DevPrintf("EXP1 read: 0x%08X -> 0x%08X", EXP1_BASE | offset, value);
  return value;
}

void Bus::DoWriteEXP1(MemoryAccessSize size, u32 offset, u32 value)
{
  Log_WarningPrintf("EXP1 write: 0x%08X <- 0x%08X", EXP1_BASE | offset, value);
}

u32 Bus::DoReadEXP2(MemoryAccessSize size, u32 offset)
{
  // rx/tx buffer empty
  if (offset == 0x21)
  {
    return 0x04 | 0x08;
  }

  Log_WarningPrintf("EXP2 read: 0x%08X", EXP2_BASE | offset);
  return UINT32_C(0xFFFFFFFF);
}

void Bus::DoWriteEXP2(MemoryAccessSize size, u32 offset, u32 value)
{
  if (offset == 0x23)
  {
    if (value == '\r')
      return;

    if (value == '\n')
    {
      if (!m_tty_line_buffer.empty())
      {
        Log_InfoPrintf("TTY: %s", m_tty_line_buffer.c_str());
#ifdef _DEBUG
        if (CPU::LOG_EXECUTION)
          CPU::WriteToExecutionLog("TTY: %s\n", m_tty_line_buffer.c_str());
#endif
      }
      m_tty_line_buffer.clear();
    }
    else
    {
      m_tty_line_buffer += static_cast<char>(Truncate8(value));
    }

    return;
  }

  if (offset == 0x41)
  {
    Log_WarningPrintf("BIOS POST status: %02X", value & UINT32_C(0x0F));
    return;
  }

  Log_WarningPrintf("EXP2 write: 0x%08X <- 0x%08X", EXP2_BASE | offset, value);
}

u32 Bus::DoReadMemoryControl(MemoryAccessSize size, u32 offset)
{
  u32 value = m_MEMCTRL.regs[offset / 4];
  FixupUnalignedWordAccessW32(offset, value);
  return value;
}

void Bus::DoWriteMemoryControl(MemoryAccessSize size, u32 offset, u32 value)
{
  FixupUnalignedWordAccessW32(offset, value);

  const u32 index = offset / 4;
  const u32 write_mask = (index == 8) ? COMDELAY::WRITE_MASK : MEMDELAY::WRITE_MASK;
  const u32 new_value = (m_MEMCTRL.regs[index] & ~write_mask) | (value & write_mask);
  if (m_MEMCTRL.regs[index] != new_value)
  {
    m_MEMCTRL.regs[index] = new_value;
    RecalculateMemoryTimings();
  }
}

u32 Bus::DoReadMemoryControl2(MemoryAccessSize size, u32 offset)
{
  if (offset == 0x00)
    return m_ram_size_reg;

  u32 value = 0;
  DoInvalidAccess(MemoryAccessType::Read, size, MEMCTRL2_BASE | offset, value);
  return value;
}

void Bus::DoWriteMemoryControl2(MemoryAccessSize size, u32 offset, u32 value)
{
  if (offset == 0x00)
  {
    m_ram_size_reg = value;
    return;
  }

  DoInvalidAccess(MemoryAccessType::Write, size, MEMCTRL2_BASE | offset, value);
}

u32 Bus::DoReadPad(MemoryAccessSize size, u32 offset)
{
  return m_pad->ReadRegister(offset);
}

void Bus::DoWritePad(MemoryAccessSize size, u32 offset, u32 value)
{
  m_pad->WriteRegister(offset, value);
}

u32 Bus::DoReadSIO(MemoryAccessSize size, u32 offset)
{
  return m_sio->ReadRegister(offset);
}

void Bus::DoWriteSIO(MemoryAccessSize size, u32 offset, u32 value)
{
  m_sio->WriteRegister(offset, value);
}

u32 Bus::DoReadCDROM(MemoryAccessSize size, u32 offset)
{
  // TODO: Splitting of half/word reads.
  Assert(size == MemoryAccessSize::Byte);
  return ZeroExtend32(m_cdrom->ReadRegister(offset));
}

void Bus::DoWriteCDROM(MemoryAccessSize size, u32 offset, u32 value)
{
  // TODO: Splitting of half/word reads.
  Assert(size == MemoryAccessSize::Byte);
  m_cdrom->WriteRegister(offset, Truncate8(value));
}

u32 Bus::DoReadGPU(MemoryAccessSize size, u32 offset)
{
  Assert(size == MemoryAccessSize::Word);
  return m_gpu->ReadRegister(offset);
}

void Bus::DoWriteGPU(MemoryAccessSize size, u32 offset, u32 value)
{
  Assert(size == MemoryAccessSize::Word);
  m_gpu->WriteRegister(offset, value);
}

u32 Bus::DoReadMDEC(MemoryAccessSize size, u32 offset)
{
  Assert(size == MemoryAccessSize::Word);
  return m_mdec->ReadRegister(offset);
}

void Bus::DoWriteMDEC(MemoryAccessSize size, u32 offset, u32 value)
{
  Assert(size == MemoryAccessSize::Word);
  m_mdec->WriteRegister(offset, value);
}

u32 Bus::DoReadInterruptController(MemoryAccessSize size, u32 offset)
{
  u32 value = m_interrupt_controller->ReadRegister(offset);
  FixupUnalignedWordAccessW32(offset, value);
  return value;
}

void Bus::DoWriteInterruptController(MemoryAccessSize size, u32 offset, u32 value)
{
  FixupUnalignedWordAccessW32(offset, value);
  m_interrupt_controller->WriteRegister(offset, value);
}

u32 Bus::DoReadTimers(MemoryAccessSize size, u32 offset)
{
  u32 value = m_timers->ReadRegister(offset);
  FixupUnalignedWordAccessW32(offset, value);
  return value;
}

void Bus::DoWriteTimers(MemoryAccessSize size, u32 offset, u32 value)
{
  FixupUnalignedWordAccessW32(offset, value);
  m_timers->WriteRegister(offset, value);
}

u32 Bus::DoReadSPU(MemoryAccessSize size, u32 offset)
{
  // 32-bit reads are read as two 16-bit accesses.
  if (size == MemoryAccessSize::Word)
  {
    const u16 lsb = m_spu->ReadRegister(offset);
    const u16 msb = m_spu->ReadRegister(offset + 2);
    return ZeroExtend32(lsb) | (ZeroExtend32(msb) << 16);
  }
  else
  {
    return ZeroExtend32(m_spu->ReadRegister(offset));
  }
}

void Bus::DoWriteSPU(MemoryAccessSize size, u32 offset, u32 value)
{
  // 32-bit writes are written as two 16-bit writes.
  // TODO: Ignore if address is not aligned.
  if (size == MemoryAccessSize::Word)
  {
    Assert(Common::IsAlignedPow2(offset, 2));
    m_spu->WriteRegister(offset, Truncate16(value));
    m_spu->WriteRegister(offset + 2, Truncate16(value >> 16));
    return;
  }

  Assert(Common::IsAlignedPow2(offset, 2));
  m_spu->WriteRegister(offset, Truncate16(value));
}

void Bus::DoInvalidateCodeCache(u32 page_index)
{
  m_cpu_code_cache->InvalidateBlocksWithPageIndex(page_index);
}

u32 Bus::DoReadDMA(MemoryAccessSize size, u32 offset)
{
  return FIXUP_WORD_READ_VALUE(offset, m_dma->ReadRegister(FIXUP_WORD_READ_OFFSET(offset)));
}

void Bus::DoWriteDMA(MemoryAccessSize size, u32 offset, u32 value)
{
  switch (size)
  {
    case MemoryAccessSize::Byte:
    case MemoryAccessSize::HalfWord:
    {
      // zero extend length register
      if ((offset & u32(0xF0)) < 7 && (offset & u32(0x0F)) == 0x4)
        value = ZeroExtend32(value);
      else
        FixupUnalignedWordAccessW32(offset, value);
    }

    default:
      break;
  }

  m_dma->WriteRegister(offset, value);
}
