#include <gba/gba.hpp>

//pixel:      4 cycles

//hdraw:    240 pixels ( 960 cycles)
//hblank:    68 pixels ( 272 cycles)
//scanline: 308 pixels (1232 cycles)

//vdraw:    160 scanlines (197120 cycles)
//vblank:    68 scanlines ( 83776 cycles)
//frame:    228 scanlines (280896 cycles)

namespace GameBoyAdvance {

PPU ppu;
#include "background.cpp"
#include "object.cpp"
#include "window.cpp"
#include "screen.cpp"
#include "io.cpp"
#include "memory.cpp"
#include "serialization.cpp"

auto PPU::blank() -> bool {
  return io.forceBlank || cpu.stopped();
}

PPU::PPU() {
  output = new uint32[240 * 160];
}

PPU::~PPU() {
  delete[] output;
}

auto PPU::Enter() -> void {
  while(true) scheduler.synchronize(), ppu.main();
}

auto PPU::step(uint clocks) -> void {
  Thread::step(clocks);
  synchronize(cpu);
}

auto PPU::main() -> void {
  cpu.keypad.run();

  io.vblank = io.vcounter >= 160 && io.vcounter <= 226;
  io.vcoincidence = io.vcounter == io.vcompare;

  if(io.vcounter == 0) {
    frame();

    bg2.io.lx = bg2.io.x;
    bg2.io.ly = bg2.io.y;

    bg3.io.lx = bg3.io.x;
    bg3.io.ly = bg3.io.y;
  }

  if(io.vcounter == 160) {
    if(io.irqvblank) cpu.irq.flag |= CPU::Interrupt::VBlank;
    cpu.dmaVblank();
  }

  if(io.irqvcoincidence) {
    if(io.vcoincidence) cpu.irq.flag |= CPU::Interrupt::VCoincidence;
  }

  if(io.vcounter < 160) {
    uint y = io.vcounter;
    bg0.scanline(y);
    bg1.scanline(y);
    bg2.scanline(y);
    bg3.scanline(y);
    objects.scanline(y);
    for(uint x : range(240)) {
      bg0.run(x, y);
      bg1.run(x, y);
      bg2.run(x, y);
      bg3.run(x, y);
      objects.run(x, y);
      window0.run(x, y);
      window1.run(x, y);
      window2.output = objects.output.window;
      window3.output = true;
      uint15 color = screen.run(x, y);
      output[y * 240 + x] = color;
      step(4);
    }
  } else {
    step(960);
  }

  io.hblank = 1;
  if(io.irqhblank) cpu.irq.flag |= CPU::Interrupt::HBlank;
  if(io.vcounter < 160) cpu.dmaHblank();

  step(240);
  io.hblank = 0;
  if(io.vcounter < 160) cpu.dmaHDMA();

  step(32);
  if(++io.vcounter == 228) io.vcounter = 0;
}

auto PPU::frame() -> void {
  player.frame();
  scheduler.exit(Scheduler::Event::Frame);
}

auto PPU::refresh() -> void {
  Emulator::video.refresh(output, 240 * sizeof(uint32), 240, 160);
}

auto PPU::power() -> void {
  create(PPU::Enter, system.frequency());

  for(uint n = 0x000; n <= 0x055; n++) bus.io[n] = this;

  for(uint n = 0; n < 240 * 160; n++) output[n] = 0;

  for(uint n = 0; n < 96 * 1024; n++) vram[n] = 0x00;
  for(uint n = 0; n < 1024; n += 2) writePRAM(n, Half, 0x0000);
  for(uint n = 0; n < 1024; n += 2) writeOAM(n, Half, 0x0000);

  memory::fill(&io, sizeof(IO));
  for(auto& object : this->object) object = {};
  for(auto& param : this->objectParam) param = {};

  bg0.power(BG0);
  bg1.power(BG1);
  bg2.power(BG2);
  bg3.power(BG3);
  objects.power();
  window0.power(IN0);
  window1.power(IN1);
  window2.power(IN2);
  window3.power(OUT);
  screen.power();
}

}
