#include <lcd/lcd.h>
#include <cstring>

#include <iostream>

LCD::LCD(Cpu& cpu)
  : screenbuffer(), regLY(90), regLYC(0),
    intHBlank(false), intVBlank(false), intOAM(false), intLYC(false),
    cpu(cpu), bgp(false), obp{Palette(true),Palette(true)},
    vram_dirty(true), currentMode(ModeVBlank)
{
  screenbuffer.create(160,144);
  screenbuffer.clear();

  std::memset(vram_raw, 0, 0x2000);

  for(int i = 0; i < 384; i++)
    {
      tiles[i].create(8,8,sf::Color::Red);
    }

  for(int l = 0; l < 2; l++)
    std::memset(&tilemap[l], 0, 32*32);
}

void LCD::generate_tile(int idx)
{
  for(int y = 0, addr = idx << 4; y < 8; y++, addr+=2)
    {
      u8 a = vram_raw[addr];
      u8 b = vram_raw[addr+1];
      for(int x = 7; x >= 0; x--, a>>=1, b>>=1)
        {
          tiles[idx].setPixel(x,y,bgp.getColorByIdx( ((a&0x1) << 1) | (b&0x1)));
        }
    }
}

void LCD::vram_write(u16 addr, u8 data)
{
  if (Range(0x8000, 0x97FF).contains(addr))
    {
      generate_tile((addr & 0x1FFF) >> 4);
    }
}

void LCD::write8(u16 addr, u8 data) {
  // VRAM access
  if (Range(0x8000,0x9FFF).contains(addr))
    {
      vram_raw[addr-0x8000] = data;
      vram_dirty = true;
      vram_write(addr, data);
      return;
    }

  switch(addr)
    {
    case 0xFF40: // LCDC
      // TODO
      break;
    case 0xFF41: // STAT
      intHBlank = data & IntSourceHBlank;
      intVBlank = data & IntSourceVBlank;
      intOAM = data & IntSourceOAM;
      intLYC = data & IntSourceLYC;
      break;
    case 0xFF42: // SCY
      // TODO
      break;
    case 0xFF43: // SCX
      // TODO
      break;
    case 0xFF44: // LY
      // Ignore
      break;
    case 0xFF45: // LYC
      regLYC = data;
      break;
    case 0xFF46: // DMA (OAM DMA source address)
      // TODO
      break;
    case 0xFF47: // BGP
      bgp.setRegValue(data);
      vram_dirty = true;
      break;
    case 0xFF48: // OBP0
      obp[0].setRegValue(data);
      vram_dirty = true;
      break;
    case 0xFF49: // OBP1
      obp[1].setRegValue(data);
      vram_dirty = true;
      break;
    case 0xFF4A: // WY
    case 0xFF4B: // WX
      // TODO
      break;
    }
}

u8 LCD::read8(u16 addr) {
  // VRAM access
  if (Range(0x8000,0x9FFF).contains(addr))
    return vram_raw[addr-0x8000];

  switch(addr)
    {
    case 0xFF40: // LCDC
      // TODO
      return 0x00;
    case 0xFF41: // STAT
      return (currentMode |
              (regLY == regLYC ? LycEqual : 0) |
              (intHBlank ? IntSourceHBlank : 0) |
              (intVBlank ? IntSourceVBlank : 0) |
              (intOAM ? IntSourceOAM : 0) |
              (intLYC ? IntSourceLYC : 0));
    case 0xFF42: // SCY
      // TODO
      return 0x00;
    case 0xFF43: // SCX
      // TODO
      return 0x00;
    case 0xFF44: // LY
      return regLY;
    case 0xFF45: // LYC
      return regLYC;
    case 0xFF46: // DMA (OAM DMA source address)
      // TODO
      return 0x00;
    case 0xFF47: // BGP
      return bgp.getRegValue();
    case 0xFF48: // OBP0
      return obp[0].getRegValue();
    case 0xFF49: // OBP1
      return obp[1].getRegValue();
    case 0xFF4A: // WY
    case 0xFF4B: // WY
      // TODO
      return 0x00;
    default:
      return 0xFF;
    }
}

void LCD::render(sf::RenderTarget& target)
{
  cpu.signalInterrupt(INT_VBlank);
  for(int row = 0; row < 32; row++)
    for(int col = 0; col < 32; col++)
      {
        unsigned int map_idx = row*32+col;
        sf::Texture txt;
        sf::Sprite spr;

        unsigned int tile_idx = tilemap[0][map_idx];

        txt.loadFromImage(tiles[tile_idx]);
        spr.setTexture(txt,true);
        spr.setPosition(col*8,row*8);
        target.draw(spr);
      }
}