#include "diskii.h"

#ifdef TEENSYDUINO
#include <Arduino.h>
#include "teensy-println.h"
#else
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#endif

#include "applemmu.h" // for FLOATING

#include "globals.h"
#include "appleui.h"

#include "diskii-rom.h"

#define DISKIIMAGIC 0xAA

// how many CPU cycles do we wait to spin down the disk drive? 1023000 == 1 second
#define SPINDOWNDELAY (1023000)

// 10 second delay before flushing
#define FLUSHDELAY (1023000 * 10)

#define SPINFOREVER -2
#define NOTSPINNING -1

DiskII::DiskII(AppleMMU *mmu)
{
  this->mmu = mmu;

  curPhase[0] = curPhase[1] = 0;
  curHalfTrack[0] = curHalfTrack[1] = 0;
  curWozTrack[0] = curWozTrack[1] = 0xFF;

  writeMode = false;
  writeProt = false; // FIXME: expose an interface to this
  readWriteLatch = 0x00;
  sequencer = 0;
  dataRegister = 0;
  driveSpinupCycles[0] = driveSpinupCycles[1] = 0; // CPU cycle number when the disk drive spins up
  deliveredDiskBits[0] = deliveredDiskBits[1] = 0;

  disk[0] = disk[1] = NULL;
  diskIsSpinningUntil[0] = diskIsSpinningUntil[1] = -1;
  flushAt[0] = flushAt[1] = 0;
  selectedDisk = 0;
}

DiskII::~DiskII()
{
}

bool DiskII::Serialize(int8_t fd)
{
  uint8_t buf[27] = { DISKIIMAGIC,
		      readWriteLatch,
		      sequencer,
		      dataRegister,
		      writeMode,
		      writeProt,
		      selectedDisk };
  
  if (g_filemanager->write(fd, buf, 7) != 7) {
    return false;
  }

  for (int i=0; i<2; i++) {
    uint8_t ptr = 0;
    buf[ptr++] = curHalfTrack[i];
    buf[ptr++] = curWozTrack[i];
    buf[ptr++] = curPhase[i];
    buf[ptr++] = ((driveSpinupCycles[i] >> 56) & 0xFF);
    buf[ptr++] = ((driveSpinupCycles[i] >> 48) & 0xFF);
    buf[ptr++] = ((driveSpinupCycles[i] >> 40) & 0xFF);
    buf[ptr++] = ((driveSpinupCycles[i] >> 32) & 0xFF);
    buf[ptr++] = ((driveSpinupCycles[i] >> 24) & 0xFF);
    buf[ptr++] = ((driveSpinupCycles[i] >> 16) & 0xFF);
    buf[ptr++] = ((driveSpinupCycles[i] >>  8) & 0xFF);
    buf[ptr++] = ((driveSpinupCycles[i]      ) & 0xFF);
    buf[ptr++] = ((deliveredDiskBits[i] >> 56) & 0xFF);
    buf[ptr++] = ((deliveredDiskBits[i] >> 48) & 0xFF);
    buf[ptr++] = ((deliveredDiskBits[i] >> 40) & 0xFF);
    buf[ptr++] = ((deliveredDiskBits[i] >> 32) & 0xFF);
    buf[ptr++] = ((deliveredDiskBits[i] >> 24) & 0xFF);
    buf[ptr++] = ((deliveredDiskBits[i] >> 16) & 0xFF);
    buf[ptr++] = ((deliveredDiskBits[i] >>  8) & 0xFF);
    buf[ptr++] = ((deliveredDiskBits[i]      ) & 0xFF);
    buf[ptr++] = (diskIsSpinningUntil[i] >> 56) & 0xFF;
    buf[ptr++] = (diskIsSpinningUntil[i] >> 48) & 0xFF;
    buf[ptr++] = (diskIsSpinningUntil[i] >> 40) & 0xFF;
    buf[ptr++] = (diskIsSpinningUntil[i] >> 32) & 0xFF;
    buf[ptr++] = (diskIsSpinningUntil[i] >> 24) & 0xFF;
    buf[ptr++] = (diskIsSpinningUntil[i] >> 16) & 0xFF;
    buf[ptr++] = (diskIsSpinningUntil[i] >>  8) & 0xFF;
    buf[ptr++] = (diskIsSpinningUntil[i]      ) & 0xFF;
    // Safety check: keeping the hard-coded 27 and comparing against ptr.
    // If we change the 27, also need to change the size of buf[] above
    if (g_filemanager->write(fd, buf, 27) != ptr) {
      return false;
    }
    
    if (disk[i]) {
      // Make sure we have flushed the disk images
      disk[i]->flush();
      flushAt[i] = 0; // and there's no need to re-flush them now

      buf[0] = 1;
      if (g_filemanager->write(fd, buf, 1) != 1)
	return false;

      // FIXME: this ONLY works for builds using the filemanager to read
      // the disk image, so it's broken until we port Woz to do that!
      const char *fn = disk[i]->diskName();
      if (g_filemanager->write(fd, fn, strlen(fn)+1) != strlen(fn)+1)  // include null terminator
	return false;
      if (!disk[i]->Serialize(fd))
	return false;
    } else {
      buf[0] = 0;
      if (g_filemanager->write(fd, buf, 0) != 1)
	return false;
    }
  }

  buf[0] = DISKIIMAGIC;
  if (g_filemanager->write(fd, buf, 1) != 1)
    return false;

  return true;
}

bool DiskII::Deserialize(int8_t fd)
{
  uint8_t buf[MAXPATH];
  if (g_filemanager->read(fd, buf, 7) != 7)
    return false;
  if (buf[0] != DISKIIMAGIC)
    return false;

  readWriteLatch = buf[1];
  sequencer = buf[2];
  dataRegister = buf[3];
  writeMode = buf[4];
  writeProt = buf[5];
  selectedDisk = buf[6];

  for (int i=0; i<2; i++) {
    uint8_t ptr = 0;
    if (g_filemanager->read(fd, buf, 27) != 27)
      return false;

    curHalfTrack[i] = buf[ptr++];
    curWozTrack[i] = buf[ptr++];
    curPhase[i] = buf[ptr++];

    driveSpinupCycles[i] = buf[ptr++];
    driveSpinupCycles[i] <<= 8; driveSpinupCycles[i] |= buf[ptr++];
    driveSpinupCycles[i] <<= 8; driveSpinupCycles[i] |= buf[ptr++];
    driveSpinupCycles[i] <<= 8; driveSpinupCycles[i] |= buf[ptr++];
    driveSpinupCycles[i] <<= 8; driveSpinupCycles[i] |= buf[ptr++];
    driveSpinupCycles[i] <<= 8; driveSpinupCycles[i] |= buf[ptr++];
    driveSpinupCycles[i] <<= 8; driveSpinupCycles[i] |= buf[ptr++];
    driveSpinupCycles[i] <<= 8; driveSpinupCycles[i] |= buf[ptr++];

    deliveredDiskBits[i] = buf[ptr++];
    deliveredDiskBits[i] <<= 8; deliveredDiskBits[i] |= buf[ptr++];
    deliveredDiskBits[i] <<= 8; deliveredDiskBits[i] |= buf[ptr++];
    deliveredDiskBits[i] <<= 8; deliveredDiskBits[i] |= buf[ptr++];
    deliveredDiskBits[i] <<= 8; deliveredDiskBits[i] |= buf[ptr++];
    deliveredDiskBits[i] <<= 8; deliveredDiskBits[i] |= buf[ptr++];
    deliveredDiskBits[i] <<= 8; deliveredDiskBits[i] |= buf[ptr++];
    deliveredDiskBits[i] <<= 8; deliveredDiskBits[i] |= buf[ptr++];

    diskIsSpinningUntil[i] = buf[ptr++];
    diskIsSpinningUntil[i] <<= 8; diskIsSpinningUntil[i] |= buf[ptr++];
    diskIsSpinningUntil[i] <<= 8; diskIsSpinningUntil[i] |= buf[ptr++];
    diskIsSpinningUntil[i] <<= 8; diskIsSpinningUntil[i] |= buf[ptr++];
    diskIsSpinningUntil[i] <<= 8; diskIsSpinningUntil[i] |= buf[ptr++];
    diskIsSpinningUntil[i] <<= 8; diskIsSpinningUntil[i] |= buf[ptr++];
    diskIsSpinningUntil[i] <<= 8; diskIsSpinningUntil[i] |= buf[ptr++];
    diskIsSpinningUntil[i] <<= 8; diskIsSpinningUntil[i] |= buf[ptr++];
    
    if (disk[i])
      delete disk[i];
    if (g_filemanager->read(fd, buf, 1) != 1)
      return false;
    if (buf[0]) {
      disk[i] = new WozSerializer();

      ptr = 0;
      // FIXME: MAXPATH check!
      while (1) {
	if (g_filemanager->read(fd, &buf[ptr++], 1) != 1)
	  return false;
	if (buf[ptr-1] == 0)
	  break;
      }
      if (buf[0]) {
	// Important we don't read all the tracks, so we can also flush
	// writes back to the fd...
	disk[i]->readFile((char *)buf, false, T_AUTO); // FIXME error checking    
      } else {
	// ERROR: there's a disk but we don't have the path to its image?
	return false;
      }
      
      if (!disk[i]->Deserialize(fd))
	return false;
    } else {
      disk[i] = NULL;
    }
  }

  if (g_filemanager->read(fd, buf, 1) != 1)
    return false;
  if (buf[0] != DISKIIMAGIC)
    return false;

  return true;
}

void DiskII::Reset()
{
  curPhase[0] = curPhase[1] = 0;
  curHalfTrack[0] = curHalfTrack[1] = 0;

  writeMode = false;
  writeProt = false; // FIXME: expose an interface to this
  readWriteLatch = 0x00;

  ejectDisk(0);
  ejectDisk(1);
}

void DiskII::driveOff()
{
  if (diskIsSpinningUntil[selectedDisk] == SPINFOREVER) {
    diskIsSpinningUntil[selectedDisk] = g_cpu->cycles + SPINDOWNDELAY; // 1 second lag
    // The drive-is-on-indicator is turned off later, when the disk
    // actually spins down.
  }
  
  if (disk[selectedDisk]) {
    flushAt[selectedDisk] = g_cpu->cycles + FLUSHDELAY;
    if (flushAt[selectedDisk] == 0)
      flushAt[selectedDisk] = 1; // fudge magic number; 0 is "don't flush"
  }
}

void DiskII::driveOn()
{
  if (diskIsSpinningUntil[selectedDisk] != SPINFOREVER) {
    // If the drive isn't already spinning, then start keeping track of how
    // many bits we've delivered (so we can honor the disk bit-delivery time
    // that might be in the Woz disk image).
    driveSpinupCycles[selectedDisk] = g_cpu->cycles;
    deliveredDiskBits[selectedDisk] = 0;
    diskIsSpinningUntil[selectedDisk] = SPINFOREVER;
  }
  // FIXME: does the sequencer get reset? Maybe if it's the selected disk? Or no?
  // sequencer = 0;
  g_ui->drawOnOffUIElement(UIeDisk1_activity + selectedDisk, true);
}

uint8_t DiskII::readSwitches(uint8_t s)
{
  switch (s) {
  case 0x00: // change stepper motor phase
    break;
  case 0x01:
    setPhase(0);
    break;
  case 0x02:
    break;
  case 0x03:
    setPhase(1);
    break;
  case 0x04:
    break;
  case 0x05:
    setPhase(2);
    break;
  case 0x06: // 3 off
    break;
  case 0x07: // 3 on
    setPhase(3);
    break;

  case 0x08: // drive off
    driveOff();
    break;
  case 0x09: // drive on
    driveOn();
    break;

  case 0x0A: // select drive 1
    select(0);
    break;
  case 0x0B: // select drive 2
    select(1);
    break;

  case 0x0C: // shift one read or write byte
    readWriteLatch = readOrWriteByte();
    if (readWriteLatch & 0x80) {
      if (!(sequencer & 0x80)) {
	//	printf("SEQ RESET EARLY [1]\n");
      }
      sequencer = 0;
    }
    break;

  case 0x0D: // load data register (latch)
    // This is complex and incomplete. cf. Logic State Sequencer, 
    // UTA2E, p. 9-14
    if (!writeMode) {
      if (isWriteProtected())
	readWriteLatch |= 0x80;
      else
	readWriteLatch &= 0x7F;
    }
    if (!(sequencer & 0x80)) {
      //      printf("SEQ RESET EARLY [2]\n");
    }
    sequencer = 0;
    break;

  case 0x0E: // set read mode
    setWriteMode(false);
    break;
  case 0x0F: // set write mode
    setWriteMode(true);
    break;
  }

  // Any even address read returns the readWriteLatch (UTA2E Table 9.1,
  // p. 9-12, note 2)
  return (s & 1) ? FLOATING : readWriteLatch;
}

void DiskII::writeSwitches(uint8_t s, uint8_t v)
{
  switch (s) {
  case 0x00: // change stepper motor phase
    break;
  case 0x01:
    setPhase(0);
    break;
  case 0x02:
    break;
  case 0x03:
    setPhase(1);
    break;
  case 0x04:
    break;
  case 0x05:
    setPhase(2);
    break;
  case 0x06: // 3 off
    break;
  case 0x07: // 3 on
    setPhase(3);
    break;

  case 0x08: // drive off
    driveOff();
    break;
  case 0x09: // drive on
    driveOn();
    break;

  case 0x0A: // select drive 1
    select(0);
    break;
  case 0x0B: // select drive 2
    select(1);
    break;

  case 0x0C: // shift one read or write byte
    if (readOrWriteByte() & 0x80) {
      if (!(sequencer & 0x80)) {
	//	printf("SEQ RESET EARLY [3]\n");
      }
      sequencer = 0;
    }
    break;

  case 0x0D: // drive write
    break;

  case 0x0E: // set read mode
    setWriteMode(false);
    break;

  case 0x0F: // set write mode
    setWriteMode(true);
    break;
  }

  // All writes update the latch
  if (writeMode) {
    readWriteLatch = v;
  }
}

/* The Disk ][ has a stepper motor that moves the head across the tracks.
 * Switches 0-7 turn off and on the four different magnet phases; pulsing 
 * from (e.g.) phase 0 to phase 1 makes the motor move up a track, and 
 * (e.g.) phase 1 to phase 0 makes the motor move down a track.
 *
 * Except that's not quite true: the stepper actually moves the head a 
 * _half_ track.
 *
 * This is a very simplified version of the stepper motor code. In theory, 
 * we should keep track of all 4 phase magnets; and then only move up or down
 * a half track when two adjacent motors are on (not three adjacent motors;
 * and not two opposite motors). But that physical characteristic isn't 
 * important for most diskettes, and our image formats aren't likely to 
 * be able to provide appropriate half-track data to the programs that played 
 * tricks with these half-tracks (for copy protection or whatever).
 * 
 * This setPhase is only called when turning *on* a phase. It's assumed that 
 * something is turning *off* the phases correctly; and that the combination 
 * of the previous phase that was on and the current phase that's being turned
 * on are reliable enough to determine direction.
 *
 * The _phase_delta array is four sets of offsets - one for each
 * current phase, detailing what the step will be given the next
 * phase.  This kind of emulates the messiness of going from phase 0
 * to 2 -- it's going to move forward two half-steps -- but then doing
 * the same thing again is just going to move you back two half-steps...
 *
 */

void DiskII::setPhase(uint8_t phase)
{
  const int8_t _phase_delta[16] = {  0,  1,  2, -1, // prev phase 0 -> 0/1/2/3
				    -1,  0,  1,  2, // prev phase 1 -> 0/1/2/3
				    -2, -1,  0,  1, // prev phase 2 -> 0/1/2/3
				     1, -2, -1,  0  // prev phase 3 -> 0/1/2/3
  };

  int8_t prevPhase = curPhase[selectedDisk];
  int8_t prevHalfTrack = curHalfTrack[selectedDisk];


  curHalfTrack[selectedDisk] += _phase_delta[(prevPhase * 4) + phase];
  curPhase[selectedDisk] = phase;

  // Cap at 35 tracks (a normal disk size). Some drives let you go farther, 
  // and we could support that by increasing this limit - but the images 
  // would be different too, so there would be more work to abstract out...
  if (curHalfTrack[selectedDisk] > 35 * 2 - 1) {
    curHalfTrack[selectedDisk] = 35 * 2 - 1;
  }

  // Don't go past the innermost track, of course.
  if (curHalfTrack[selectedDisk] < 0) {
    curHalfTrack[selectedDisk] = 0;
    // recalibrate! This is where the fun noise goes DaDaDaDaDaDaDaDaDa
  }

  if (curHalfTrack[selectedDisk] != prevHalfTrack) {
    if (disk[selectedDisk]) {
      curWozTrack[selectedDisk] = disk[selectedDisk]->dataTrackNumberForQuarterTrack(curHalfTrack[selectedDisk]*2);
    } else {
      curWozTrack[selectedDisk] = 0;
    }
  }
}

bool DiskII::isWriteProtected()
{
  return (writeProt ? 0xFF : 0x00);
}

int64_t DiskII::calcExpectedBits()
{
  // If the disk isn't spinning, then it can't be expected to deliver data
  if (diskIsSpinningUntil[selectedDisk]==NOTSPINNING)
    return 0;

  int64_t cyclesPassed = g_cpu->cycles - driveSpinupCycles[selectedDisk];
  // This constant defines how fast the disk drive "spins".
  // 4.0 is good for DOS 3.3 writes, and reads as 205ms in
  //   Copy 2+'s drive speed verifier.
  // 3.99: 204.5ms
  // 3.90: 199.9ms
  // 3.91: 200.5ms
  // 3.51: 176ms, and is too fast for DOS to write properly.
  //
  // As-is, this won't read NIB files for some reason I haven't
  // fully understood; but if you slow the disk down to /5.0,
  // then they load?
  int64_t expectedDiskBits = (float)cyclesPassed / 3.90;

  return expectedDiskBits - deliveredDiskBits[selectedDisk];
}

void DiskII::setWriteMode(bool enable)
{
  if (enable) {
    // At this point we need to update the track pointer so we know
    // where we're going to start writing bits.

    int64_t db = calcExpectedBits();
    if (db > 0) {
      // make sure the disk is at the right point for our program counter's time
      // before we start writing data.
      deliveredDiskBits[selectedDisk] += db;
      while (db) {
	sequencer <<= 1;
	sequencer |= disk[selectedDisk]->nextDiskBit(curWozTrack[selectedDisk]);
	db--;
      }
    }
  }
  writeMode = enable;
}

static uint8_t _lc(char c)
{
  if (c >= 'A' && c <= 'Z') {
    c = c - 'A' + 'a';
  }
  return c;
}

static bool _endsWithI(const char *s1, const char *s2)
{
  if (strlen(s2) > strlen(s1)) {
    return false;
  }
  
  const char *p = &s1[strlen(s1)-1];
  int16_t l = strlen(s2)-1;
  while (l >= 0) {
    if (_lc(*p--) != _lc(s2[l]))
      return false;
    l--;
  }
  return true;
}

void DiskII::insertDisk(int8_t driveNum, const char *filename, bool drawIt)
{
  ejectDisk(driveNum);

  disk[driveNum] = new WozSerializer();
  // intentionally 'false' (see above call to readFile)
  if (!disk[driveNum]->readFile(filename, false, T_AUTO)) {
    delete disk[driveNum];
    disk[driveNum] = NULL;
    return;
  }

  curWozTrack[driveNum] = disk[driveNum]->dataTrackNumberForQuarterTrack(curHalfTrack[driveNum]*2);

  if (drawIt)
    g_ui->drawOnOffUIElement(UIeDisk1_state + driveNum, false);
}

void DiskII::ejectDisk(int8_t driveNum)
{
  if (disk[driveNum]) {
    disk[driveNum]->flush();
    flushAt[driveNum] = 0;
    delete disk[driveNum];
    disk[driveNum] = NULL;
    g_ui->drawOnOffUIElement(UIeDisk1_state + driveNum, true);
  }
}

void DiskII::select(int8_t which)
{
  if (which != 0 && which != 1)
    return;

  if (which != selectedDisk) {
    if (diskIsSpinningUntil[selectedDisk] == SPINFOREVER) {
      // FIXME: I'm not sure what the right behavior is here (read
      // UTA2E and see if the state diagrams show the right
      // behavior). This spins it down immediately based on something
      // I read about the duoDisk not having both motors on
      // simultaneously.
      diskIsSpinningUntil[selectedDisk] = NOTSPINNING;
      // FIXME: consume any disk bits that need to be consumed, and
      // spin it down
      g_ui->drawOnOffUIElement(UIeDisk1_activity + selectedDisk, false);

      // Spin up the other one though
      diskIsSpinningUntil[which] = SPINFOREVER;
      g_ui->drawOnOffUIElement(UIeDisk1_activity + which, true);
    }
    
    // Queue flushing the cache of the disk that's no longer selected
    if (disk[selectedDisk]) {
      flushAt[selectedDisk] = g_cpu->cycles + FLUSHDELAY;
      if (flushAt[selectedDisk] == 0)
	flushAt[selectedDisk] = 1; // fudge magic number; 0 is "don't flush"
    }
    
    // set the selected disk drive
    selectedDisk = which;

    // FIXME: does this reset the sequencer?
    sequencer = 0;
  }

  // Update the current woz track for the given disk drive
  if (disk[selectedDisk]) {
    curWozTrack[selectedDisk] =
      disk[selectedDisk]->dataTrackNumberForQuarterTrack(curHalfTrack[selectedDisk]*2);
  }
}

uint8_t DiskII::readOrWriteByte()
{
  if (!disk[selectedDisk]) {
    return 0xFF;
  }

  int32_t bitsToDeliver;
  
  if (diskIsSpinningUntil[selectedDisk] != SPINFOREVER &&
      diskIsSpinningUntil[selectedDisk] < g_cpu->cycles) {
    // Uum, disk isn't spinning?
    goto done;
  }

  bitsToDeliver = calcExpectedBits();
  
  if (writeMode && !writeProt) {
    // It's a write request.

    // Write requests from DOS 3.3 start with 40 self-sync bytes
    // (cf. Beneath Apple DOS, p.3-8 and 3-9). These 0XFF bytes are
    // written in a 40-cycle loop, where a bit is written every 4
    // cycles; it intentionally lets 2 0-bits slip in there to
    // provide the self-sync pattern.
    //
    // So the timing here is important. Figure out how many bits
    // should have been laid down to the track, and those are 0s.

    int64_t expectedBits = calcExpectedBits();
    while (expectedBits > 0) {
      disk[selectedDisk]->writeNextWozBit(curWozTrack[selectedDisk], 0);
      expectedBits--;
      deliveredDiskBits[selectedDisk]++;
    }

    disk[selectedDisk]->writeNextWozByte(curWozTrack[selectedDisk], readWriteLatch);
    deliveredDiskBits[selectedDisk] += 8;
    goto done;
  }

  if (bitsToDeliver > 0) {
    // We're expected to deliver some bits to the Disk II sequencer.
    // Instead of piecemeal delivering a small number of bits (which we
    // could do, but it's kinda busywork) - instead, we'll do one of two
    // possible things.
    //
    // The first: if we're expecting a small number of bits to be delivered,
    // then we'll grab the next byte from the nibble stream and return it.
    // This itself has three possible cases -
    //   (a) we should be delivering less than a full byte, but we're
    //       actually going to deliver a full byte. bitsToDeliver will
    //       become negative, because we're delivering these too early.
    //       The next call will probably see that it has nothing to deliver
    //       and, as long as the disk image we're using doesn't have a
    //       really fine tolerance on the delivery rate of the bits,
    //       it will all come out in the wash.
    //   (b) we should be delivering exactly a byte, and we're doing the
    //       absolute right thing.
    //   (c) we are more than 1 byte, but less than 2 bytes, behind. If
    //       this is the case, we're probably making up for a timing
    //       problem in this code - where the bits would now have been
    //       lost. By returning the first byte that we found, we're hoping
    //       that the next call will be closer to on time, and we will
    //       eventually catch back up to the stream. Hopefully this makes
    //       the stream a little more resilient - and the error isn't
    //       so far off that the reader notices something is weird on the
    //       timing. (Standard RWTS doesn't, but some copy protection
    //       might.)
    if (bitsToDeliver < 16) {
      while (bitsToDeliver > -16 && ((sequencer & 0x80) == 0)) {
	sequencer <<= 1;
	sequencer |= disk[selectedDisk]->nextDiskBit(curWozTrack[selectedDisk]);
	bitsToDeliver--;
	deliveredDiskBits[selectedDisk]++;
      }
      goto done;
    }

    // If we reach here, we're throwing away a bunch of missed data.
    // This might be normal (where the machine wasn't listening for the data),
    // or it might be exceptional (something wrong with the tuning of data
    // delivery, based on the magic constant in expectedDiskBits above)...
    deliveredDiskBits[selectedDisk] += bitsToDeliver;
    while (bitsToDeliver) {
      sequencer <<= 1;
      sequencer |= disk[selectedDisk]->nextDiskBit(curWozTrack[selectedDisk]);
      bitsToDeliver--;
    }
  }

    
 done:
  return sequencer;
}

const char *DiskII::DiskName(int8_t num)
{
  if (disk[num]) {
    return disk[num]->diskName();
  }

  // Nothing inserted in that drive
  return "";
}

void DiskII::loadROM(uint8_t *toWhere)
{
#ifdef TEENSYDUINO
  println("loading DiskII rom");
  for (uint16_t i=0; i<=0xFF; i++) {
    toWhere[i] = pgm_read_byte(&romData[i]);
  }
#else
  printf("loading DiskII rom\n");
  memcpy(toWhere, romData, 256);
#endif
}

void DiskII::maintenance(int64_t cycle)
{
  // Handle spin-down for the drive. Drives stay on for a second after
  // the stop was noticed.
  for (int i=0; i<2; i++) {
    if (diskIsSpinningUntil[i] != SPINFOREVER && 
	g_cpu->cycles > diskIsSpinningUntil[i]) {
      // Stop the given disk drive spinning
      diskIsSpinningUntil[i] = NOTSPINNING;
      // FIXME: consume any disk bits that need to be consumed, and spin it down
      g_ui->drawOnOffUIElement(UIeDisk1_activity + i, false);
    }

    if (flushAt[i] &&
	g_cpu->cycles > flushAt[i]) {
      if (disk[i]) {
	disk[i]->flush();
      }
      flushAt[i] = 0;
    }

  }
}

uint8_t DiskII::selectedDrive()
{
  return selectedDisk;
}

uint8_t DiskII::headPosition(uint8_t drive)
{
  return curHalfTrack[drive];
}


