////////////////////////////////////////////////////////////////////////////////
// CPU6510C64.cpp -- this file is part of the Emulator Developers Kit
// available at http://ourworld.compuserve.com/homepages/pc64/develop.htm

RegisterPersistentClass(CPU6510C64);


////////////////////////////////////////////////////////////////////////////////
// initialisation

void CPU6510C64::DoInit() {

  // initialize base class
  CPU65xx::DoInit();

  // initialize components
  P.Init("P", this);
  DrawLowBit3.Init("DrawLowBit3", this);
  DrawLowBit3.SetOnFire((pfn)OnDrawLowBit3);
  DrawLowBit6.Init("DrawLowBit6", this);
  DrawLowBit6.SetOnFire((pfn)OnDrawLowBit6);
  DrawLowBit7.Init("DrawLowBit7", this);
  DrawLowBit7.SetOnFire((pfn)OnDrawLowBit7);

  // on RESET reset also the port
  Reset.SetOnLow((pfn)OnResetLow);

  // don't reset the port in the constructor where it is not initialized
  UpdatePort(0, 0);
}


////////////////////////////////////////////////////////////////////////////////
// reset also the port

void CPU6510C64::OnResetLow() {

  // reset parent class
  CPU65xx::OnResetLow();

  // reset the port
  UpdatePort(0, 0);
}


////////////////////////////////////////////////////////////////////////////////
// The CPU port of the C64 behaves strange in input mode:
//
// 00010111 (17) pull-up resistors, drawn high
// 00100000 (20) pull-down transistor, drawn low
// 11000000 (C0) keeps last high for 65ms, then drawn low
// 00001000 (08) no datasette: keeps last high for 1s, then drawn low
//               datasette connected: high and low randomly (mains?)
//               
// In reality, times depend on both CPU temperature and how long the output was
// high (measure bit 3 with CPUPORT3.ASM on the C64). This isn't emulated.

void CPU6510C64::UpdatePort(byte bNewDDR, byte bNewLatch) {

  // set new port outputs and pull-up/pull-down inputs
  // inputs 3, 6 and 7 are low
  P.SetOutput((byte)((bNewLatch & bNewDDR) | ((bNewLatch | ~bNewDDR) & 0x17)));

  // start timers for bits which were high outputs and are now inputs
  byte bStart = (byte)(bDDR & bLatch & ~bNewDDR);

  // stop timers for bits which are now outputs
  byte bStop = (byte)(bHighInputs & bNewDDR);

  // start or stop the timers
  if ((bStart & 0x08) != 0) {
    bHighInputs |= 0x08;
    DrawLowBit3.StartCounter(985248);
  } else if ((bStop & 0x08) != 0) {
    bHighInputs &= ~0x08;
    DrawLowBit3.StopCounter();
  }
  if ((bStart & 0x40) != 0) {
    bHighInputs |= 0x40;
    DrawLowBit6.StartCounter(65 * 985248 / 1000);
  } else if ((bStop & 0x40) != 0) {
    bHighInputs &= ~0x40;
    DrawLowBit6.StopCounter();
  }
  if ((bStart & 0x80) != 0) {
    bHighInputs |= 0x80;
    DrawLowBit7.StartCounter(65 * 985248 / 1000);
  } else if ((bStop & 0x80) != 0) {
    bHighInputs &= ~0x80;
    DrawLowBit7.StopCounter();
  }

  // save new state
  bDDR = bNewDDR;
  bLatch = bNewLatch;
}

void CPU6510C64::OnDrawLowBit3() {
  bHighInputs &= ~0x08;
}

void CPU6510C64::OnDrawLowBit6() {
  bHighInputs &= ~0x40;
}

void CPU6510C64::OnDrawLowBit7() {
  bHighInputs &= ~0x80;
}


////////////////////////////////////////////////////////////////////////////////
// port access functions

byte CPU6510C64::ReadDDR() {
  return bDDR;
}

void CPU6510C64::WriteDDR(byte bValue) {
  UpdatePort(bValue, bLatch);
}

byte CPU6510C64::ReadPR() {
  assert(bHighInputs == (bHighInputs & 0xC8));
  return (byte)(P.GetInput() | bHighInputs);
}

void CPU6510C64::WritePR(byte bValue) {
  UpdatePort(bDDR, bValue);
}
