#include <cpu/cpu.h>
#include <cartridge/cartridge.h>
#include <cartridge/mbc/mbc1.h>
#include <memory/ram.h>
#include <memory/bus.h>
#include <memory/register.h>
#include <lcd/lcd.h>
#include <timer/timer.h>
#include <fstream>
#include <thread>
#include <chrono>
#include <cstring>
#include <iostream>
#include <iomanip>

int main(int argc, char** argv)
{
  if(argc < 2)
    return 1;

  std::ifstream file(argv[1]);
  Cartridge cart(file);

  if(cart.type() != CT_MBC1 && cart.type() != CT_ROM_ONLY)
    {
      std::cerr << "Wrong cart type (" << (int)cart.type() << ")" << std::endl;
      return 2;
    }

  sf::RenderWindow window(sf::VideoMode(256,256), "VGBC BG view");

  RAM wram1(0x1000);
  RAM wram2(0x1000);
  RAM hram(0x100);
  RAM oam(0x100);

  MBC1 mbc1(cart);

  Bus b;
  Cpu cpu(&b);

  LCD lcd(cpu);
  TimerDiv timer;

  b.map_device(0x0000, 0x7FFF, &mbc1);
  b.map_device(0x8000, 0x9FFF, &lcd, 0x8000);
  b.map_device(0xA000, 0xBFFF, &mbc1, 0xA000);
  b.map_device(0xC000, 0xCFFF, &wram1);
  b.map_device(0xD000, 0xDFFF, &wram2);
  b.map_device(0xE000, 0xFDFF, &wram1);
  b.map_device(0xFE00, 0xFE9F, &oam);
  b.map_device(0xFF04, 0xFF07, &timer);
  b.map_device(0xFF40, 0xFF4F, &lcd, 0xFF40);
  b.map_device(0xFF80, 0xFFFE, &hram);


  BoundRegister ie_mapped(cpu.state.IE);
  BoundRegister if_mapped(cpu.state.IF);
  b.map_device(0xFFFF, 0xFFFF, &ie_mapped);
  b.map_device(0xFF0F, 0xFF0F, &if_mapped);

  cpu.state.A = 0x1;
  cpu.state.carry = true;
  cpu.state.halfcarry = true;
  cpu.state.subtract = false;
  cpu.state.zero = true;

  cpu.state.B = 0x00;
  cpu.state.C = 0x13;
  cpu.state.D = 0x00;
  cpu.state.E = 0xD8;
  cpu.state.H = 0x01;
  cpu.state.L = 0x4D;

  cpu.state.SP = 0xFFFE;
  cpu.state.PC = 0x100;

  const std::chrono::milliseconds delay(1);

  try {
    while(!cpu.state.stopped)
      {

        cpu.run(4194304/4000);

        window.clear(sf::Color::Black);
        lcd.render(window);
        window.display();

        std::this_thread::sleep_for(delay);
      }
  } catch(CpuException& e) {
    std::cerr << e.what() << std::endl;
  }

}