/**
   * Switch the controller to either read-only or write-only device mode.
   *
   * <p>We assume that the only spontaneous transitions are SWITCHING_TO_READ_MODE -> READ_MODE and
   * SWITCHING_TO_WRITE_MODE -> WRITE_MODE. All other transitions only happen because we (or someone
   * else) requests them.
   */
  private synchronized void switchToMode(DeviceMode newMode) throws InterruptedException {
    // Note: remember that in general the user code may choose to spawn worker threads.
    // Thus, we may have multiple, concurrent threads simultaneously trying to switch modes.
    // We deal with that by using synchronized public methods, allowing only one client in at
    // a time; this gives us a sequential sequence of modes we need the controller to be in.
    // This method, too, is synchronized, but that's paranoia.

    // If we don't currently know his mode, we need to ask the controller where we stand
    if (null == this.controllerMode) {
      this.controllerMode = this.getMotorControllerDeviceMode();
    }

    // If the controller is being stupid in returning a non-actual mode (the mock one was)
    // then to heck with trying to keep him happy
    if (null == this.controllerMode) return;

    // We might have caught this guy mid transition. Wait until he settles down
    if (this.controllerMode == DeviceMode.SWITCHING_TO_READ_MODE
        || this.controllerMode == DeviceMode.SWITCHING_TO_WRITE_MODE) {
      for (; ; ) {
        this.controllerMode = this.getMotorControllerDeviceMode();
        //
        if (this.controllerMode == DeviceMode.SWITCHING_TO_READ_MODE
            || this.controllerMode == DeviceMode.SWITCHING_TO_WRITE_MODE) {
          SynchronousOpMode.synchronousThreadIdle();
        } else break;
      }
    }

    // If he's read-write, then that's just dandy
    if (this.controllerMode == DeviceMode.READ_WRITE) return;

    // If he's not what we want, then ask him to switch him to what we want and
    // spin until he gets there.
    if (this.controllerMode != newMode) {
      // We need to complete any existing thunks (we could be more refined, but that suffices)
      // as, to quote Johnathan Berling:
      //
      // http://ftcforum.usfirst.org/showthread.php?4352-Legacy-Motor-controller-write-to-read-mode-amount-of-time/page3
      /*
      When the loop call finishes, all commands are sent simultaneously to the device. So, it
      simultaneously gets put into read mode and told to change the channel mode. In this case
      it can't comply with the command to switch the channel mode since the port is in read mode.

          Code:
          motorLeft.setTargetPosition(firstTarget);
          motorRight.setTargetPosition(-firstTarget);

          motorLeft.setPower(1.0);
          motorRight.setPower(1.0);

          wheelController.setMotorControllerDeviceMode(DcMot orController.DeviceMode.READ_ONLY);

      In this case, all of the lines above the READ_ONLY line won't take effect until the
      device is placed back into write mode.
      */
      // What this says is that if you're switching to a new mode then there better not
      // be any existing commands still in the queue for that device. In effect, mode switches
      // should (conservatively) happen at the TOP of a loop() call so that they are compatible
      // with anything else that is issued to that controller in that call.
      //
      // We accomplish this by waiting until any incompatible operations were executed on
      // previous loop() calls. *New* incompatible operations are prevented from starting
      // by the fact that this controller object uses synchronized methods.
      int oppositeKey =
          newMode == DeviceMode.READ_ONLY
              ? this.controllerReadThunkKey
              : this.controllerWriteThunkKey;
      SynchronousOpMode.synchronousThreadWaitForLoopCycleEmptyOfActionKey(oppositeKey);

      // Tell him to switch
      this.setMotorControllerDeviceMode(newMode);

      // Wait until he gets there
      do {
        SynchronousOpMode.synchronousThreadIdle();
        this.controllerMode = this.getMotorControllerDeviceMode();
      } while (this.controllerMode != newMode);
    }
  }