const expect = require('chai').expect;
const MOS6502 = require('../src/MOS/6502');
const Mem = require('../src/memory');


describe("Testing MOS6502 CPU...", function(){
  var asm = new MOS6502.Assembler();
  var cpu = new MOS6502.CPU();
  var tick = cpu.clk();
  cpu.memory = new Mem.RAM(256);
  cpu.memory.load(0xFFFC, [0x00, 0x00]);
 
  it("Resetting (IRQ Disabled flag must be on", function(){
    cpu.reset = true;
    tick(); // One tick to handle resets.
    expect(cpu.I).to.equal(1);
    expect(cpu.PC).to.equal(0); // Test program counter set to the vector stored at 0xFFFC - 0xFFFD
  });

  describe("Testing flag set/clear calls...", function(){
    it("CLC / SEC", function(){
      cpu.memory.clearPage(0);
      cpu.memory.load(0, [0x18, 0x38]);
      cpu.reset = true;
      tick(); // reset.
      tick(); tick(); // Two ticks to process opcode.
      expect(cpu.C).to.equal(0);
      tick(); tick();
      expect(cpu.C).to.equal(1);
    });

    it("CLI / SEI", function(){
      cpu.memory.clearPage(0);
      cpu.memory.load(0, [0x58, 0x78]);
      cpu.reset = true;
      tick();
      tick(); tick();
      expect(cpu.I).to.equal(0);
      tick(); tick();
      expect(cpu.I).to.equal(1);
    });

    it("CLD / SED", function(){
      cpu.memory.clearPage(0);
      cpu.memory.load(0, [0xD8, 0xF8]);
      cpu.reset = true;
      tick();
      tick(); tick();
      expect(cpu.D).to.equal(0);
      tick(); tick();
      expect(cpu.D).to.equal(1);
    });

    it("CLV", function(){
      cpu.memory.clearPage(0);
      cpu.memory.load(0, [0xB8]);
      cpu.reset = true;
      tick();
      tick(); tick();
      expect(cpu.V).to.equal(0);
    });
  });

  describe("Testing LDA...", function(){
    it("LDA Immediate", function(){
      let prg = "LDA #$01\n";
      prg += "LDA #$BB";
      asm.reset().compile(prg);
      cpu.memory.clearPage(0);
      cpu.memory.load(0, asm.result());
      cpu.hardReset();
      //cpu.reset = true;
      //tick();
      tick();
      tick();
      expect(cpu.A).to.equal(0x01);
      tick();
      expect(cpu.A).to.equal(0x01);
      tick();
      expect(cpu.A).to.equal(0xBB);
    });

    it("LDA Zero Page");
    it("LDA Zero Page, X");
    it("LDA Absolute");
    it("LDA Absolute, X");
    it("LDA Absolute, Y");
    it("LDA Indirect, X");
    it("LDA Indirect, Y");
  });

  describe("Testing ADC...", function(){
    describe("ADC Binary Mode...", function(){
      it("ADC Immediate", function(){
        let prg = "CLC\n";
        prg += "CLV\n";
        prg += "CLD\n";
        prg += "CLI\n";
        prg += "LDA #$00\n";
        prg += "ADC #$01\n";
        prg += "ADC #$7F\n";
        prg += "ADC #$80";
        asm.reset().compile(prg);
        cpu.memory.clearPage(0);
        cpu.memory.load(0, asm.result());
        cpu.hardReset();
        //cpu.reset = true;
        //tick(); // To reset;
        while (cpu.PC !== 13){
          switch(cpu.PC){
            case 6:
              expect(cpu.A).to.equal(0x00); break;
            case 8:
              expect(cpu.A).to.equal(0x01);
              expect(cpu.Z).to.equal(0);
              expect(cpu.C).to.equal(0);
              expect(cpu.N).to.equal(0);
              break;
            case 10:
              expect(cpu.A).to.equal(0x80);
              expect(cpu.Z).to.equal(0);
              expect(cpu.C).to.equal(0);
              expect(cpu.N).to.equal(1);
              break;
            case 12:
              expect(cpu.A).to.equal(0x00);
              expect(cpu.Z).to.equal(1);
              expect(cpu.C).to.equal(1);
              expect(cpu.N).to.equal(0);
          }
          tick();
        } 
      });

      it("ADC Zero Page");
      it("ADC Zero Page, X");
      it("ADC Absolute");
      it("ADC Absolute, X");
      it("ADC Absolute, Y");
      it("ADC Indirect, X");
      it("ADC Indirect, Y"); 
    });

    describe("ADC Decimal (BCD) Mode...", function(){
      it("ADC Immediate", function(){
        let prg = "CLC\n";
        prg += "CLV\n";
        prg += "SED\n";
        prg += "CLI\n";
        prg += "LDA #$00\n";
        prg += "ADC #$01\n";
        prg += "ADC #$09\n";
        prg += "ADC #$89\n";
        prg += "ADC #$01\n";
        prg += "LDA #$0A\n"; // 0A is an invalid BCD number
        prg += "CLC\n"; // Need to reset the clear flag from previous add.
        prg += "ADC #$01";
        asm.reset().compile(prg);
        cpu.memory.clearPage(0);
        cpu.memory.load(0, asm.result());
        cpu.hardReset();
        //cpu.reset = true;
        //tick(); // To reset;
        while (cpu.PC !== 20){
          switch(cpu.PC){
            case 6:
              expect(cpu.A).to.equal(0x00); break;
            case 8:
              expect(cpu.A).to.equal(0x01);
              expect(cpu.Z).to.equal(0);
              expect(cpu.C).to.equal(0);
              expect(cpu.N).to.equal(0);
              break;
            case 10:
              expect(cpu.A).to.equal(0x10);
              expect(cpu.Z).to.equal(0);
              expect(cpu.C).to.equal(0);
              expect(cpu.N).to.equal(0);
              break;
            case 12:
              expect(cpu.A).to.equal(0x99);
              expect(cpu.Z).to.equal(0);
              expect(cpu.C).to.equal(0);
              expect(cpu.N).to.equal(1);
              break;
            case 14:
              expect(cpu.A).to.equal(0x00);
              expect(cpu.Z).to.equal(0);
              expect(cpu.C).to.equal(1);
              expect(cpu.N).to.equal(0);
              break;
            case 19:
              //console.log(cpu.A);
              expect(cpu.A).to.equal(0x11);
              break;
          }
          tick();
        }
      });
      it("ADC Zero Page");
      it("ADC Zero Page, X");
      it("ADC Absolute");
      it("ADC Absolute, X");
      it("ADC Absolute, Y");
      it("ADC Indirect, X");
      it("ADC Indirect, Y"); 
    });
  });

  describe("Testing SBC...", function(){
    describe("SBC Binary Mode...", function(){
      it("SBC Immediate", function(){
        let prg = "SEC\n";
        prg += "CLV\n";
        prg += "CLD\n";
        prg += "CLI\n";
        prg += "LDA #$01\n";
        prg += "SBC #$01\n";
        prg += "SBC #$01";
        asm.reset().compile(prg);
        cpu.memory.clearPage(0);
        cpu.memory.load(0, asm.result());
        cpu.hardReset();
        //cpu.reset = true;
        //tick(); // To reset;
        while(cpu.PC !== 11){
          // NOTE TO SELF: Depending on the OP code, these tests could be
          // checked multiple times, as the cpu may sit at a program cntr
          // for a couple cycles depending on the OP.
          switch(cpu.PC){
            case 1:
              expect(cpu.C).to.be.equal(1); break;
            case 6:
              expect(cpu.A).to.be.equal(0x01); break;
            case 8:
              expect(cpu.A).to.be.equal(0x00);
              expect(cpu.C).to.be.equal(1);
              expect(cpu.Z).to.be.equal(1);
              expect(cpu.N).to.be.equal(0);
              break;
            case 9:
              expect(cpu.A).to.be.equal(0xFF);
              expect(cpu.C).to.be.equal(0);
              expect(cpu.Z).to.be.equal(0);
              expect(cpu.N).to.be.equal(1);
              break;
          }
          tick();
        } 
      });

      it("SBC Zero Page");
      it("SBC Zero Page, X");
      it("SBC Absolute");
      it("SBC Absolute, X");
      it("SBC Absolute, Y");
      it("SBC Indirect, X");
      it("SBC Indirect, Y");
    });

    describe("SBC Decimal (BCD) Mode...", function(){
      it("SBC Immediate");
      it("SBC Zero Page");
      it("SBC Zero Page, X");
      it("SBC Absolute");
      it("SBC Absolute, X");
      it("SBC Absolute, Y");
      it("SBC Indirect, X");
      it("SBC Indirect, Y");
    });
  });
});