#include "mode7.cpp"
uint4 PPU::Background::Mosaic::size;

auto PPU::Background::hires() const -> bool {
  return ppu.io.bgMode == 5 || ppu.io.bgMode == 6;
}

auto PPU::Background::voffset() const -> uint16 {
  return mosaic.enable ? latch.voffset : io.voffset;
}

auto PPU::Background::hoffset() const -> uint16 {
  return mosaic.enable ? latch.hoffset : io.hoffset;
}

//V = 0, H = 0
auto PPU::Background::frame() -> void {
}

//H = 0
auto PPU::Background::scanline() -> void {
}

//H = 28
auto PPU::Background::begin() -> void {
  x = -7;
  y = ppu.vcounter();

  tileCounter = 7 - (io.hoffset & 7) << hires();
  for(auto& word : data) word = 0;

  if(y == 1) {
    mosaic.vcounter = mosaic.size + 1;
    mosaic.voffset = 1;
    latch.hoffset = io.hoffset;
    latch.voffset = io.voffset;
  } else if(--mosaic.vcounter == 0) {
    mosaic.vcounter = mosaic.size + 1;
    mosaic.voffset += mosaic.size + 1;
    latch.hoffset = io.hoffset;
    latch.voffset = io.voffset;
  }

  mosaic.hcounter = mosaic.size + 1;
  mosaic.hoffset = 0;

  if(io.mode == Mode::Mode7) return beginMode7();
  if(mosaic.size == 0) {
    latch.hoffset = io.hoffset;
    latch.voffset = io.voffset;
  }
}

auto PPU::Background::getTile() -> void {
  uint colorDepth = io.mode == Mode::BPP2 ? 0 : io.mode == Mode::BPP4 ? 1 : 2;
  uint paletteOffset = ppu.io.bgMode == 0 ? id << 5 : 0;
  uint paletteSize = 2 << colorDepth;
  uint tileMask = ppu.vram.mask >> 3 + colorDepth;
  uint tiledataIndex = io.tiledataAddress >> 3 + colorDepth;

  uint tileHeight = io.tileSize == TileSize::Size8x8 ? 3 : 4;
  uint tileWidth = !hires() ? tileHeight : 4;

  uint width = 256 << hires();

  uint hmask = tileHeight == 3 ? width : width << 1;
  uint vmask = hmask;
  if(io.screenSize & 1) hmask <<= 1;
  if(io.screenSize & 2) vmask <<= 1;
  hmask--;
  vmask--;

  uint px = x << hires();
  uint py = mosaic.enable ? (uint)mosaic.voffset : y;

  uint hscroll = hoffset();
  uint vscroll = voffset();
  if(hires()) {
    hscroll <<= 1;
    if(ppu.io.interlace) py = py << 1 | (ppu.field() & !mosaic.enable);  //todo: temporary vmosaic hack
  }

  uint hoffset = hscroll + px;
  uint voffset = vscroll + py;

  if(ppu.io.bgMode == 2 || ppu.io.bgMode == 4 || ppu.io.bgMode == 6) {
    uint16 offsetX = px + (hscroll & 7);

    if(offsetX >= 8) {
      uint hval = ppu.bg3.getTile((offsetX - 8) + (ppu.bg3.hoffset() & ~7), ppu.bg3.voffset() + 0);
      uint vval = ppu.bg3.getTile((offsetX - 8) + (ppu.bg3.hoffset() & ~7), ppu.bg3.voffset() + 8);
      uint validMask = id == ID::BG1 ? 0x2000 : 0x4000;

      if(ppu.io.bgMode == 4) {
        if(hval & validMask) {
          if((hval & 0x8000) == 0) {
            hoffset = offsetX + (hval & ~7);
          } else {
            voffset = py + hval;
          }
        }
      } else {
        if(hval & validMask) hoffset = offsetX + (hval & ~7);
        if(vval & validMask) voffset = py + vval;
      }
    }
  }

  hoffset &= hmask;
  voffset &= vmask;

  uint screenX = io.screenSize & 1 ? 32 << 5 : 0;
  uint screenY = io.screenSize & 2 ? 32 << 5 : 0;
  if(io.screenSize == 3) screenY <<= 1;

  uint tx = hoffset >> tileWidth;
  uint ty = voffset >> tileHeight;

  uint16 offset = (ty & 0x1f) << 5 | (tx & 0x1f);
  if(tx & 0x20) offset += screenX;
  if(ty & 0x20) offset += screenY;

  uint16 address = io.screenAddress + offset;
  tile = ppu.vram[address];
  bool mirrorY = tile.bit(15);
  bool mirrorX = tile.bit(14);
  priority = io.priority[tile.bit(13)];
  paletteNumber = tile.bits(10,12);
  paletteIndex = paletteOffset + (paletteNumber << paletteSize);

  if(tileWidth  == 4 && (bool)(hoffset & 8) != mirrorX) tile +=  1;
  if(tileHeight == 4 && (bool)(voffset & 8) != mirrorY) tile += 16;
  uint16 character = tile.bits(0,9) + tiledataIndex & tileMask;

  if(mirrorY) voffset ^= 7;
  offset = (character << 3 + colorDepth) + (voffset & 7);

  switch(io.mode) {
  case Mode::BPP8:
    data[1].bits(16,31) = ppu.vram[offset + 24];
    data[1].bits( 0,15) = ppu.vram[offset + 16];
  case Mode::BPP4:
    data[0].bits(16,31) = ppu.vram[offset +  8];
  case Mode::BPP2:
    data[0].bits( 0,15) = ppu.vram[offset +  0];
  }

  if(mirrorX) for(auto n : range(2)) {
    data[n] = (data[n] >> 4 & 0x0f0f0f0f) | (data[n] << 4 & 0xf0f0f0f0);
    data[n] = (data[n] >> 2 & 0x33333333) | (data[n] << 2 & 0xcccccccc);
    data[n] = (data[n] >> 1 & 0x55555555) | (data[n] << 1 & 0xaaaaaaaa);
  }
}

auto PPU::Background::run(bool screen) -> void {
  if(ppu.vcounter() == 0) return;

  if(screen == Screen::Below) {
    output.above.priority = 0;
    output.below.priority = 0;
    if(!hires()) return;
  }

  if(tileCounter-- == 0) {
    tileCounter = 7;
    getTile();
  }

  if(io.mode == Mode::Mode7) return runMode7();

  uint8 color = getTileColor();
  Pixel pixel;
  pixel.priority = priority;
  pixel.palette = color ? paletteIndex + color : 0;
  pixel.tile = tile;

  if(x == 0) {
    mosaic.hcounter = mosaic.size + 1;
    mosaic.pixel = pixel;
  } else if((!hires() || screen == Screen::Below) && --mosaic.hcounter == 0) {
    mosaic.hcounter = mosaic.size + 1;
    mosaic.pixel = pixel;
  } else if(mosaic.enable) {
    pixel = mosaic.pixel;
  }
  if(screen == Screen::Above) x++;
  if(pixel.palette == 0) return;

  if(!hires() || screen == Screen::Above) if(io.aboveEnable) output.above = pixel;
  if(!hires() || screen == Screen::Below) if(io.belowEnable) output.below = pixel;
}

auto PPU::Background::getTileColor() -> uint {
  uint color = 0;

  switch(io.mode) {
  case Mode::BPP8:
    color += data[1] >> 24 & 0x80;
    color += data[1] >> 17 & 0x40;
    color += data[1] >> 10 & 0x20;
    color += data[1] >>  3 & 0x10;
    data[1] <<= 1;
  case Mode::BPP4:
    color += data[0] >> 28 & 0x08;
    color += data[0] >> 21 & 0x04;
  case Mode::BPP2:
    color += data[0] >> 14 & 0x02;
    color += data[0] >>  7 & 0x01;
    data[0] <<= 1;
  }

  return color;
}

auto PPU::Background::power() -> void {
  io = {};
  io.tiledataAddress = (random() & 0x0f) << 12;
  io.screenAddress = (random() & 0xfc) << 8;
  io.screenSize = random();
  io.tileSize = random();
  io.aboveEnable = random();
  io.belowEnable = random();
  io.hoffset = random();
  io.voffset = random();

  latch = {};

  output.above = {};
  output.below = {};

  mosaic = {};
  mosaic.size = random();
  mosaic.enable = random();

  x = 0;
  y = 0;

  tileCounter = 0;
  tile = 0;
  priority = 0;
  paletteNumber = 0;
  paletteIndex = 0;
  for(auto& word : data) word = 0;
}

auto PPU::Background::getTile(uint x, uint y) -> uint {
  uint tileHeight = io.tileSize == TileSize::Size8x8 ? 3 : 4;
  uint tileWidth = !hires() ? tileHeight : 4;
  uint width = !hires() ? 256 : 512;
  uint maskX = tileHeight == 3 ? width : width << 1;
  uint maskY = maskX;
  if(io.screenSize & 1) maskX <<= 1;
  if(io.screenSize & 2) maskY <<= 1;
  maskX--;
  maskY--;

  uint screenX = io.screenSize & 1 ? 32 << 5 : 0;
  uint screenY = io.screenSize & 2 ? 32 << 5 : 0;
  if(io.screenSize == 3) screenY <<= 1;

  x = (x & maskX) >> tileWidth;
  y = (y & maskY) >> tileHeight;

  uint16 offset = (y & 0x1f) << 5 | (x & 0x1f);
  if(x & 0x20) offset += screenX;
  if(y & 0x20) offset += screenY;

  uint16 address = io.screenAddress + offset;
  return ppu.vram[address];
}
