#include <sfc/sfc.hpp>

namespace SuperFamicom {

PPU& ppubase = ppu;
#define PPU PPUfast
#define ppu ppufast

PPU ppu;
#include "io.cpp"
#include "line.cpp"
#include "background.cpp"
#include "mode7.cpp"
#include "object.cpp"
#include "window.cpp"
#include "serialization.cpp"

auto PPU::interlace() const -> bool { return ppubase.display.interlace; }
auto PPU::overscan() const -> bool { return ppubase.display.overscan; }
auto PPU::vdisp() const -> uint { return ppubase.display.vdisp; }
auto PPU::hires() const -> bool { return latch.hires; }
//#HDmode7>
auto PPU::hdScale() const -> uint { return configuration.hacks.ppuFast.hdMode7Scale; }
auto PPU::hdEnabled() const -> bool { return PPU::hdScale() > 1; }
//#HDmode7<

PPU::PPU() {
  //#HDmode7>
  int lineLength = !hdEnabled() ? 512 : 256 * ppu.hdScale() * ppu.hdScale();
  output = !hdEnabled() ? new uint32[512 * 512]        + 16 * 512  //overscan offset
                        : new uint32[256 * lineLength] +  8 * lineLength;
  //#HDmode7<
  tilecache[TileMode::BPP2] = new uint8[4096 * 8 * 8];
  tilecache[TileMode::BPP4] = new uint8[2048 * 8 * 8];
  tilecache[TileMode::BPP8] = new uint8[1024 * 8 * 8];

  //#HDmode7>
  for(uint y : range(240)) {
    lines[y].y = y;
    lines[y].above = new Pixel[lineLength];
    lines[y].below = new Pixel[lineLength];
  }
  //#HDmode7<
}

PPU::~PPU() {
  //#HDmode7>
  delete[] (output - (!hdEnabled() ? 16 * 512  //overscan offset
                                   :  8 * 256 * ppu.hdScale() * ppu.hdScale()));
  //#HDmode7<
  delete[] tilecache[TileMode::BPP2];
  delete[] tilecache[TileMode::BPP4];
  delete[] tilecache[TileMode::BPP8];
}

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

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

auto PPU::main() -> void {
  scanline();
  uint y = vcounter();
  step(512);
  if(y >= 1 && y <= 239) {
    if(io.displayDisable || y >= vdisp()) {
      lines[y].io.displayDisable = true;
    } else {
      memcpy(&lines[y].io, &io, sizeof(io));
      memcpy(&lines[y].cgram, &cgram, sizeof(cgram));
    }
    if(!Line::count) Line::start = y;
    Line::count++;
  }
  step(lineclocks() - hcounter());
}

auto PPU::scanline() -> void {
  if(vcounter() == 0) {
    ppubase.display.interlace = io.interlace;
    ppubase.display.overscan = io.overscan;
    latch.hires = false;
    io.obj.timeOver = false;
    io.obj.rangeOver = false;
  }

  if(vcounter() > 0 && vcounter() < vdisp()) {
    latch.hires |= io.pseudoHires || io.bgMode == 5 || io.bgMode == 6;
    latch.hires |= io.bgMode == 7 && configuration.hacks.ppuFast.hiresMode7;
  }

  if(vcounter() == vdisp() && !io.displayDisable) {
    oamAddressReset();
  }

  if(vcounter() == 240) {
    Line::flush();
    scheduler.exit(Scheduler::Event::Frame);
  }
}

auto PPU::refresh() -> void {
  auto output = this->output;
  //#HDmode7>
  if(!overscan()) output -= 7 * (!hdEnabled() ? 2*512 : 256 * ppu.hdScale() * ppu.hdScale() );
  auto pitch  = !hdEnabled() ? 512 << !interlace() : 256 * ppu.hdScale();
  auto width  = !hdEnabled() ? 256 << hires()      : 256 * ppu.hdScale();
  auto height = !hdEnabled() ? 240 << interlace()  : 240 * ppu.hdScale();
  //#HDmode7<
  Emulator::video.setEffect(Emulator::Video::Effect::ColorBleed, configuration.video.blurEmulation && hires());
  Emulator::video.refresh(output, pitch * sizeof(uint32), width, height);
}

auto PPU::load() -> bool {
  return true;
}

auto PPU::power(bool reset) -> void {
  create(Enter, system.cpuFrequency());
  PPUcounter::reset();
  memory::fill<uint32>(output, 512 * 480);

  function<auto (uint24, uint8) -> uint8> reader{&PPU::readIO, this};
  function<auto (uint24, uint8) -> void> writer{&PPU::writeIO, this};
  bus.map(reader, writer, "00-3f,80-bf:2100-213f");

  if(!reset) {
    for(auto address : range(32768)) {
      vram[address] = 0x0000;
      updateTiledata(address);
    }
    for(auto& color : cgram) color = 0x0000;
    for(auto& object : objects) object = {};
  }

  latch = {};
  io = {};
  updateVideoMode();

  ItemLimit = !configuration.hacks.ppuFast.noSpriteLimit ? 32 : 128;
  TileLimit = !configuration.hacks.ppuFast.noSpriteLimit ? 34 : 128;

  Line::start = 0;
  Line::count = 0;
}

}
