const expect = require('chai').expect;
const MOSCIA = require('../src/MOS/CIA.js');


describe("MOSCIA Tests...", function(){
  //var cia = new MOSCIA();
  
  it("R/W Pin", function(){
    let cia = new MOSCIA();
    // The RW pin is default low, so writting should be enabled.

    cia.RS = 0x02;
    expect(cia.DATA).to.be.equal(0x00);
    cia.DATA = 0xFF;
    expect(cia.DATA).to.be.equal(0xFF);

    // Set RW high to disable writting!
    cia.RW = 1;
    cia.DATA = 0x00;
    expect(cia.DATA).to.be.equal(0xFF);

    // Set it low once more
    cia.RW = 0;
    cia.DATA = 0x00;
    expect(cia.DATA).to.be.equal(0x00);
  });

  it("Interrupts (via FLAG)", function(){
    let cia = new MOSCIA();

    cia.FLAG = 1;
    cia.RS = 0x0D;
    let IC = cia.DATA;
    expect(IC & 0x10).to.be.equal(0x10);
    expect(IC & 0x80).to.be.equal(0x80);
    expect(cia.DATA).to.be.equal(0x00);

    // Setting data changes the interrupt mask. This should mask the
    // FLAG interrupt.
    cia.DATA = 0x90;
    cia.FLAG = 1;
    IC = cia.DATA;
    expect(IC).to.be.equal(0x00);
  });

  it("PD*/DD* Masking", function(){
    let cia = new MOSCIA();
    let pdaVal = 0x3C;
    let pdbVal = 0xC3;
    cia.onRead("PDA", (name, dda) => {
      if (dda < 255)
        cia.setPDA(pdaVal);
    });
    cia.onRead("PDB", (name, ddb) => {
      if (ddb < 255)
        cia.setPDB(pdbVal);
    });

    cia.RS = 0x02;
    cia.DATA = 0x00;
    cia.RS = 0x00;
    expect(cia.DATA).to.be.equal(pdaVal);
    cia.RS = 0x03;
    cia.DATA = 0x00;
    cia.RS = 0x01;
    expect(cia.DATA).to.be.equal(pdbVal);
    cia.RS = 0x02;
    cia.DATA = pdbVal;
    cia.RS = 0x00;
    cia.DATA = pdbVal;
    expect(cia.DATA).to.be.equal(0xFF);
    cia.RS = 0x03;
    cia.DATA = pdaVal;
    cia.RS = 0x01;
    cia.DATA = pdaVal;
    expect(cia.DATA).to.be.equal(0xFF);
  });

  it("Serial IO", function(){
    let cia = new MOSCIA();
    let Input = (i) => {
      cia.SP = i;
      cia.CNT = 1;
      cia.phi2 = 1;
    };

    var outval = 0;
    let Output = () => {
      cia.CNT = 1;
      cia.phi2 = 1;
      outval = (outval << 1) | cia.SP;
    }
    cia.RS = 0x0C;

    Input(1);
    expect(cia.DATA).to.be.equal(1);
    Input(1);
    expect(cia.DATA).to.be.equal(3);
    Input(0);
    expect(cia.DATA).to.be.equal(6);
    Input(0);
    Input(1);
    Input(1);
    Input(0);
    Input(1);
    expect(cia.DATA).to.be.equal(0xCD);
    cia.RS = 0x0D;
    let IC = cia.DATA;
    expect(IC & 0x08).to.be.equal(0x08);
    expect(IC & 0x80).to.be.equal(0x80);
    // Reading from RS = 0x0D should clear the interrupt values. Checking this
    expect(cia.DATA).to.be.equal(0);

    cia.RS = 0x0E;
    cia.DATA = 0x40; // Enable serial output.
    Output();
    expect(outval).to.be.equal(1);
    Output();
    expect(outval).to.be.equal(3);
    Output();
    expect(outval).to.be.equal(6);
    Output();
    Output();
    Output();
    Output();
    Output();
    expect(outval).to.be.equal(0xCD);
    cia.RS = 0x0C;
    expect(cia.DATA).to.be.equal(0);
    cia.RS = 0x0D;
    expect((cia.DATA & 0x08) >> 3).to.be.equal(1);
    expect(cia.DATA).to.be.equal(0);
  });

  it("TOD 60hz", function(){
    // NOTE: The TOD clock doesn't actually enforce the frequency.
    // The TOD 'pin' is expected to be attached to a clock pulsing at the
    // designated frequency.
    // As such, this test will run MUCH MUCH faster than the 1.1 seconds
    // actually being checked (because I'm not using a clock here).
    let cia = new MOSCIA();
    let tick60 = () => {
      for (let i=0; i < 6; i++)
        cia.TOD = 1;
    };
    // NOTE: The CIA defaults to 60hz TOD clock. Nothing needs to be set.

    // Check that seconds is at 0 (we'll test this again later)
    cia.RS = 0x09;
    expect(cia.DATA).to.be.equal(0);

    // Switch to 10ths of a second and go!
    cia.RS = 0x08;
    expect(cia.DATA).to.be.equal(0);
    tick60();
    expect(cia.DATA).to.be.equal(0x01);
    tick60();
    expect(cia.DATA).to.be.equal(0x02);
    tick60();
    expect(cia.DATA).to.be.equal(0x03);
    for (let i=0; i < 8; i++)
      tick60();
    // Should have looped back around a little at this point!
    expect(cia.DATA).to.be.equal(0x01);

    // Check seconds again. This should have gone up once!
    cia.RS = 0x09;
    expect(cia.DATA).to.be.equal(0x01);
  });

  it("TOD 50hz", function(){
    // NOTE: The TOD clock doesn't actually enforce the frequency.
    // The TOD 'pin' is expected to be attached to a clock pulsing at the
    // designated frequency.
    // As such, this test will run MUCH MUCH faster than the 1.1 seconds
    // actually being checked (because I'm not using a clock here).
    let cia = new MOSCIA();
    let tick50 = () => {
      for (let i=0; i < 5; i++)
        cia.TOD = 1;
    };
    // enabling 50 hz mode.
    cia.RS = 0x0E;
    cia.DATA = cia.DATA | 0x80;

    // Check that seconds is at 0 (we'll test this again later)
    cia.RS = 0x09;
    expect(cia.DATA).to.be.equal(0);

    // Switch to 10ths of a second and go!
    cia.RS = 0x08;
    expect(cia.DATA).to.be.equal(0);
    tick50();
    expect(cia.DATA).to.be.equal(0x01);
    tick50();
    expect(cia.DATA).to.be.equal(0x02);
    tick50();
    expect(cia.DATA).to.be.equal(0x03);
    for (let i=0; i < 8; i++)
      tick50();
    // Should have looped back around a little at this point!
    expect(cia.DATA).to.be.equal(0x01);

    // Check seconds again. This should have gone up once!
    cia.RS = 0x09;
    expect(cia.DATA).to.be.equal(0x01);
  });


  it("TOD Latching", function(){
    let cia = new MOSCIA();
    let tick = (cycles) => {
      let ccount = cycles * 6;
      for (let i=0; i < ccount; i++)
        cia.TOD = 1;
    };
    cia.setTOD(0x03, 0x59, 0x58, 0x00);

    // Copying current clock values for later reference.
    let TOD = [0,0,0,0];
    cia.RS = 0x08;
    TOD[0] = cia.DATA;
    cia.RS = 0x09;
    TOD[1] = cia.DATA;
    cia.RS = 0x0A;
    TOD[2] = cia.DATA;
    cia.RS = 0x0B;
    TOD[3] = cia.DATA; // This should latch the timer!!

    tick(11); // Simulate 1.1 second time passing.

    // TOD Latch shouldn't be released until next read from RS=0x08, so...
    expect(cia.DATA).to.be.equal(TOD[3]);
    cia.RS = 0x0A;
    expect(cia.DATA).to.be.equal(TOD[2]);
    cia.RS = 0x09;
    expect(cia.DATA).to.be.equal(TOD[1]);
    cia.RS = 0x08;
    expect(cia.DATA).to.be.equal(TOD[0]); // Should release the TOD latch

    // With latch released, we should be able to read the new time...
    expect(cia.DATA).to.be.equal(0x01);
    cia.RS = 0x09;
    expect(cia.DATA).to.be.equal(0x59);
  });


  it("TOD Time Rollover with AM/PM change", function(){
    let cia = new MOSCIA();
    let tick = () => {
      for (let i=0; i < 6; i++)
        cia.TOD = 1;
    };

    cia.setTOD(0x11, 0x59, 0x59, 0x09);
    tick();

    cia.RS = 0x0B;
    expect(cia.DATA).to.be.equal(0x80);
    cia.RS = 0x0A;
    expect(cia.DATA).to.be.equal(0x00);
    cia.RS = 0x09;
    expect(cia.DATA).to.be.equal(0x00);
    cia.RS = 0x08;
    expect(cia.DATA).to.be.equal(0x00);

    cia.setTOD(0x91, 0x59, 0x59, 0x09);
    tick();
    cia.RS = 0x0B;
    expect(cia.DATA).to.be.equal(0x00);
    cia.RS = 0x0A;
    expect(cia.DATA).to.be.equal(0x00);
    cia.RS = 0x09;
    expect(cia.DATA).to.be.equal(0x00);
    cia.RS = 0x08;
    expect(cia.DATA).to.be.equal(0x00);
  }); 

  it("TOD Write Time and Cycle Stop on Write", function(){
    let cia = new MOSCIA();
    let tick = (cycles) => {
      let ccount = cycles * 6;
      for (let i=0; i < ccount; i++)
        cia.TOD = 1;
    };

    cia.RS = 0x0B;
    cia.DATA = 0x11;
    cia.RS = 0x0A;
    cia.DATA = 0x30;
    cia.RS = 0x09;
    cia.DATA = 0x20;
    
    // The lock on the timer isn't released until a write to RS=0x08
    // so running a few ticks should NOT change the current values!
    tick(10);
    cia.RS = 0x0B;
    expect(cia.DATA).to.be.equal(0x11);
    cia.RS = 0x0A;
    expect(cia.DATA).to.be.equal(0x30);
    cia.RS = 0x09;
    expect(cia.DATA).to.be.equal(0x20);
    cia.RS = 0x08;
    expect(cia.DATA).to.be.equal(0x00);

    // Write to RS=0x08 and cycle again!
    cia.DATA = 0x08; // <= 8/10ths a second.
    tick(8);
    expect(cia.DATA).to.be.equal(0x06);
    cia.RS = 0x09;
    expect(cia.DATA).to.be.equal(0x21);

    // Check if writing is prevented unless Hours (0x0B) is written to first
    cia.DATA = 0x44;
    expect(cia.DATA).to.be.equal(0x21);
    cia.RS = 0x08;
    cia.DATA = 0x04;
    expect(cia.DATA).to.be.equal(0x06);
  });

  it("TOD Alarm Set and Trigger", function(){
    let cia = new MOSCIA();
    let tick = (cycles) => {
      let ccount = cycles * 6;
      for (let i=0; i < ccount; i++)
        cia.TOD = 1;
    };

    // Setting alarm values.
    cia.RS = 0x0F;
    cia.DATA = cia.DATA | 0x80;
    cia.RS = 0x0B;
    cia.DATA = 0x01;
    cia.RS = 0x0A;
    cia.DATA = 0x01;
    cia.RS = 0x09;
    cia.DATA = 0x01;
    cia.RS = 0x08;
    cia.DATA = 0x04;
 
    // Testing that all TOD values remain unchanged.
    expect(cia.DATA).to.be.equal(0x00);
    cia.RS = 0x09;
    expect(cia.DATA).to.be.equal(0x00);
    cia.RS = 0x0A;
    expect(cia.DATA).to.be.equal(0x00);
    cia.RS = 0x0B;
    expect(cia.DATA).to.be.equal(0x00);

    // Forcing TOD to be 4/10ths a second from triggering the alarm.
    cia.setTOD(0x01, 0x01, 0x01, 0x00);
    tick(4);
    cia.RS = 0x0D;
    // Hold the value as the IC clears after a read.
    let IC = cia.DATA;
    expect(IC & 0x04).to.be.equal(0x04);
    expect(IC & 0x80).to.be.equal(0x80);
  });

  it("Timer A, phi2 Triggered, Interrupt Verification", function(){
    let cia = new MOSCIA();
    let tick = (cycles) => {
      for (let i=0; i < cycles; i++)
        cia.phi2 = 1;
    };

    cia.RS = 0x04;
    cia.DATA = 0xFF;
    cia.RS = 0x05;
    cia.DATA = 0x01;

    cia.RS = 0x0E;
    // Force latch load into Timer A and activate Timer A
    cia.DATA = cia.DATA | 0x11;

    cia.RS = 0x04;
    expect(cia.DATA).to.be.equal(0xFF);
    tick(1);
    expect(cia.DATA).to.be.equal(0xFE);
    tick(1);
    expect(cia.DATA).to.be.equal(0xFD);
    tick(0x0200 - 3);
    expect(cia.DATA).to.be.equal(0x00);
    cia.RS = 0x05;
    expect(cia.DATA).to.be.equal(0x00);
    tick(1);
    expect(cia.DATA).to.be.equal(0x01);
    cia.RS = 0x04;
    expect(cia.DATA).to.be.equal(0xFF);

    cia.RS = 0x0D;
    let IC = cia.DATA;
    expect(IC & 0x01).to.be.equal(0x01);
    expect(IC & 0x80).to.be.equal(0x80);
  });


  it("Timer A, CNT Triggered, Underflow report to Port B", function(){
    let cia = new MOSCIA();
    let tick = (cycles, cnt) => {
      cnt = (cnt === true);
      for (let i=0; i < cycles; i++){
        if (cnt)
          cia.CNT = 1;
        cia.phi2 = 1;
        if (cnt)
          cia.CNT = 0;
      }
    };

    cia.RS = 0x04;
    cia.DATA = 0x08;
    cia.RS = 0x05;
    cia.DATA = 0x00;

    cia.RS = 0x0E;
    // Force latch load into Timer A,
    // enable underflow reporting on Port B bit 6,
    // set Timer A to trigger on CNT,
    // and activate Timer A
    cia.DATA = 0x33;

    cia.RS = 0x04;
    // First, test a few ticks where CNT is not high.
    expect(cia.DATA).to.be.equal(0x08); // Validate inital timer value.
    tick(1);
    tick(1);
    tick(1);
    tick(1);
    expect(cia.DATA).to.be.equal(0x08);

    // Now verify CNT triggers!
    tick(1, true);
    expect(cia.DATA).to.be.equal(0x07);
    tick(1, true);
    expect(cia.DATA).to.be.equal(0x06);
    tick(1, true);
    tick(1, true);
    tick(1, true);
    tick(1, true);
    tick(1, true);
    tick(1, true);
    expect(cia.DATA).to.be.equal(0x00);
    tick(1, true);

    // Double check that timer has reset to latch value...
    expect(cia.DATA).to.be.equal(0x08);

    // Verify Interrupt (again... but it doesn't hurt!)
    cia.RS = 0x0D;
    let IC = cia.DATA;
    expect(IC & 0x01).to.be.equal(0x01);
    expect(IC & 0x80).to.be.equal(0x80);

    // Checking Port B bit 6 which should only go high for 1 cycle!
    cia.RS = 0x01;
    expect(cia.DATA & 0x40).to.be.equal(0x40);
    tick(1, true);
    expect(cia.DATA & 0x40).to.be.equal(0x00);

    // Check Port B bit 6 toggling...
    // -------------------------------------------
    // Force latch load into Timer A,
    // enable underflow reporting on Port B bit 6,
    // setup Port B bit 6 to invert,
    // set Timer A to trigger on CNT,
    // and activate Timer A
    cia.RS = 0x0E;
    cia.DATA = 0x37;

    for (let i=0; i < 9; i++)
      tick(1, true);
    cia.RS = 0x04;
    expect(cia.DATA).to.be.equal(0x08);

    cia.RS = 0x01;
    expect(cia.DATA & 0x40).to.be.equal(0x40);
    tick(1, true);
    expect(cia.DATA & 0x40).to.be.equal(0x40);
    cia.RS = 0x04;
    expect(cia.DATA).to.be.equal(0x07);

    for (let i=0; i < 8; i++)
      tick(1, true);
    expect(cia.DATA).to.be.equal(0x08);
    cia.RS = 0x01;
    expect(cia.DATA & 0x40).to.be.equal(0x00);
  });


  it("Timer B, phi2 Triggered, Interrupt Verification", function(){
    let cia = new MOSCIA();
    let tick = (cycles) => {
      for (let i=0; i < cycles; i++)
        cia.phi2 = 1;
    };

    cia.RS = 0x06;
    cia.DATA = 0xFF;
    cia.RS = 0x07;
    cia.DATA = 0x01;

    cia.RS = 0x0F;
    // Force latch load into Timer B and activate Timer B
    cia.DATA = cia.DATA | 0x11;

    cia.RS = 0x06;
    expect(cia.DATA).to.be.equal(0xFF);
    tick(1);
    expect(cia.DATA).to.be.equal(0xFE);
    tick(1);
    expect(cia.DATA).to.be.equal(0xFD);
    tick(0x0200 - 3);
    expect(cia.DATA).to.be.equal(0x00);
    cia.RS = 0x07;
    expect(cia.DATA).to.be.equal(0x00);
    tick(1);
    expect(cia.DATA).to.be.equal(0x01);
    cia.RS = 0x06;
    expect(cia.DATA).to.be.equal(0xFF);

    cia.RS = 0x0D;
    let IC = cia.DATA;
    expect(IC & 0x02).to.be.equal(0x02);
    expect(IC & 0x80).to.be.equal(0x80);
  });


  it("Timer B, CNT Triggered, Underflow report to Port B", function(){
    let cia = new MOSCIA();
    let tick = (cycles, cnt) => {
      cnt = (cnt === true);
      for (let i=0; i < cycles; i++){
        if (cnt)
          cia.CNT = 1;
        cia.phi2 = 1;
        if (cnt)
          cia.CNT = 0;
      }
    };

    cia.RS = 0x06;
    cia.DATA = 0x08;
    cia.RS = 0x07;
    cia.DATA = 0x00;

    cia.RS = 0x0F;
    // Force latch load into Timer B,
    // enable underflow reporting on Port B bit 7,
    // set Timer B to trigger on CNT,
    // and activate Timer B
    cia.DATA = 0x33;

    cia.RS = 0x06;
    // First, test a few ticks where CNT is not high.
    expect(cia.DATA).to.be.equal(0x08); // Validate inital timer value.
    tick(1);
    tick(1);
    tick(1);
    tick(1);
    expect(cia.DATA).to.be.equal(0x08);

    // Now verify CNT triggers!
    tick(1, true);
    expect(cia.DATA).to.be.equal(0x07);
    tick(1, true);
    expect(cia.DATA).to.be.equal(0x06);
    tick(1, true);
    tick(1, true);
    tick(1, true);
    tick(1, true);
    tick(1, true);
    tick(1, true);
    expect(cia.DATA).to.be.equal(0x00);
    tick(1, true);

    // Double check that timer has reset to latch value...
    expect(cia.DATA).to.be.equal(0x08);

    // Verify Interrupt (again... but it doesn't hurt!)
    cia.RS = 0x0D;
    let IC = cia.DATA;
    expect(IC & 0x02).to.be.equal(0x02);
    expect(IC & 0x80).to.be.equal(0x80);

    // Checking Port B bit 7 which should only go high for 1 cycle!
    cia.RS = 0x01;
    expect(cia.DATA & 0x80).to.be.equal(0x80);
    tick(1, true);
    expect(cia.DATA & 0x80).to.be.equal(0x00);

    // Check Port B bit 7 toggling...
    // -------------------------------------------
    // Force latch load into Timer B,
    // enable underflow reporting on Port B bit 7,
    // setup Port B bit 7 to invert,
    // set Timer B to trigger on CNT,
    // and activate Timer B
    cia.RS = 0x0F;
    cia.DATA = 0x37;

    for (let i=0; i < 9; i++)
      tick(1, true);
    cia.RS = 0x06;
    expect(cia.DATA).to.be.equal(0x08);

    cia.RS = 0x01;
    expect(cia.DATA & 0x80).to.be.equal(0x80);
    tick(1, true);
    expect(cia.DATA & 0x80).to.be.equal(0x80);
    cia.RS = 0x06;
    expect(cia.DATA).to.be.equal(0x07);

    for (let i=0; i < 8; i++)
      tick(1, true);
    expect(cia.DATA).to.be.equal(0x08);
    cia.RS = 0x01;
    expect(cia.DATA & 0x80).to.be.equal(0x00);
  });


  it("Timer B tick on Timer A Underflow (phi2 Triggered)", function(){
    let cia = new MOSCIA();
    let tick = (cycle) => {
      for (let i=0; i < cycle; i++)
        cia.phi2 = 1;
    };

    // Setting up Timer A
    cia.RS = 0x04;
    cia.DATA = 0x02;
    cia.RS = 0x0E;
    cia.DATA = 0x11;

    // Setting up Timer B
    cia.RS = 0x06;
    cia.DATA = 0x01;
    cia.RS = 0x0F;
    cia.DATA = 0x51;

    cia.RS = 0x06;
    // Verify Timer B Low Byte Value
    expect(cia.DATA).to.be.equal(0x01);
    tick(1);
    // Timer B should not have changed.
    expect(cia.DATA).to.be.equal(0x01);
    // Timer A should have ticked down by 1.
    cia.RS = 0x04;
    expect(cia.DATA).to.be.equal(0x01);
    tick(1); tick(1);
    // Timer A should have underflowed and Timer B ticked down by 1
    expect(cia.DATA).to.be.equal(0x02);
    cia.RS = 0x06;
    expect(cia.DATA).to.be.equal(0x00);

    // Check that Timer A triggered interrupt... but Timer B did not!
    cia.RS = 0x0D;
    let IC = cia.DATA;
    expect(IC & 0x01).to.be.equal(0x01);
    expect(IC & 0x02).to.be.equal(0x00);

    tick(3); // Run through Timer A once more.
    // Check that both Timer A & B triggered interrupt!
    IC = cia.DATA;
    expect(IC & 0x01).to.be.equal(0x01);
    expect(IC & 0x02).to.be.equal(0x02);

    // Both timers should have latched back to their starting values.
    cia.RS = 0x04;
    expect(cia.DATA).to.be.equal(0x02);
    cia.RS = 0x06;
    expect(cia.DATA).to.be.equal(0x01);
  });


  it("Timer B ticks on Timer A Underflow (CNT Triggered for B only)", function(){
    let cia = new MOSCIA();
    let tick = (cycle) => {
      for (let i=0; i < cycle; i++)
        cia.phi2 = 1;
    };


    // Setup Timer A (trigger on phi2 only)
    cia.RS = 0x04;
    cia.DATA = 0x02;
    cia.RS = 0x0E;
    cia.DATA = 0x11;

    // Setup Timer B (trigger on A underflow and CNT high)
    cia.RS = 0x06;
    cia.DATA = 0x01;
    cia.RS = 0x0F;
    cia.DATA = 0x71;

    // Keep CNT low, check that underflowing A *does NOT* tick B.
    cia.CNT = 0; // <-- Just to be sure.
    tick(2);
    // Just make sure we're ticking A
    cia.RS = 0x04;
    expect(cia.DATA).to.be.equal(0x00);
    tick(1);
    expect(cia.DATA).to.be.equal(0x02);
    // Make sure B did not tick!
    cia.RS = 0x06;
    expect(cia.DATA).to.be.equal(0x01);

    // Clear interrupt flags
    cia.RS = 0x0D;
    let IC = cia.DATA;

    // NOW tick with CNT high!
    cia.CNT = 1;
    tick(3);
    // Checking A rolled over
    cia.RS = 0x04;
    expect(cia.DATA).to.be.equal(0x02);
    // Now checking B ticked...
    cia.RS = 0x06;
    expect(cia.DATA).to.be.equal(0x00);
    // Double check CNT is high
    expect(cia.CNT).to.be.equal(1);

    // One more A cycle to check B rolls over!
    tick(3);
    expect(cia.DATA).to.be.equal(0x01);
    cia.RS = 0x04;
    expect(cia.DATA).to.be.equal(0x02);
  });
});