@Override
  public void run() {
    final String methodName = "run";
    Logfile.WriteCalled(logLevel, STR_ClassName, methodName);

    /*
     * Initialise state machine
     */
    States lastState = States.Done;
    States thisState = States.Init;
    int nextUnit = this.labManagement.getFarmSize() - 1;
    this.running = true;

    /*
     * Allow other threads to check the state of this thread
     */
    Delay.MilliSeconds(500);

    /*
     * State machine loop
     */
    try {
      boolean success;

      while (thisState != States.Done) {
        /*
         * Display message on each state change
         */
        if (thisState != lastState) {
          String logMessage =
              String.format(STRLOG_StateChange_arg2, lastState.toString(), thisState.toString());
          if (debugTrace == true) {
            System.out.println(logMessage);
          }
          //                    Logfile.Write(logLevel, logMessage);

          lastState = thisState;
        }

        switch (thisState) {
          case Init:
            /*
             * Update lab status
             */
            this.online = true;
            this.labStatusMessage = StatusCodes.Ready.toString();

            /*
             * Revert any 'Running' experiments back to 'Waiting' so that they can be run again
             */
            ArrayList<ExperimentQueueInfo> experimentQueueInfoList =
                this.labManagement.getExperimentQueueDB().RetrieveByStatusCode(StatusCodes.Running);
            if (experimentQueueInfoList != null) {
              for (ExperimentQueueInfo experimentQueueInfo : experimentQueueInfoList) {
                success =
                    this.labManagement
                        .getExperimentQueueDB()
                        .UpdateStatus(experimentQueueInfo.getId(), StatusCodes.Waiting);

                /*
                 * Delete the database statistics entry for this experiment
                 */
                ExperimentStatisticsInfo experimentStatisticInfo =
                    this.labManagement
                        .getExperimentStatisticsDB()
                        .RetrieveByExperimentId(
                            experimentQueueInfo.getExperimentId(), experimentQueueInfo.getSbName());
                if (experimentStatisticInfo != null) {
                  this.labManagement
                      .getExperimentStatisticsDB()
                      .Delete(experimentStatisticInfo.getId());
                }

                /*
                 * Delete the database results entry for this experiment
                 */
                ExperimentResultInfo experimentResultInfo =
                    this.labManagement
                        .getExperimentResultsDB()
                        .RetrieveByExperimentId(
                            experimentQueueInfo.getExperimentId(), experimentQueueInfo.getSbName());
                if (experimentResultInfo != null) {
                  this.labManagement.getExperimentResultsDB().Delete(experimentResultInfo.getId());
                }

                String logMessage =
                    String.format(
                        STRLOG_RevertToWaiting_arg,
                        experimentQueueInfo.getExperimentId(),
                        experimentQueueInfo.getSbName(),
                        success);
                Logfile.Write(logMessage);
              }
            }

            /*
             * Check if any experiments have not notified their ServiceBroker
             */
            thisState = States.CheckNotified;
            break;

          case Idle:
            /*
             * Wait for an experiment to be submitted or timeout after a certain time. In either case, check
             * the experiment queue. Maybe an experiment submission signal got missed and it didn't get seen
             * here. It has happened before.
             */
            if (this.labManagement.getSignalSubmitted().Wait(INT_DelayCheckQueueSeconds * 1000)
                == true) {
              /*
               * Check if shutting down
               */
              if (this.stopRunning == true) {
                thisState = States.StopRunning;
                break;
              }

              /*
               * An experiment has been submitted, go check the queue
               */
              this.labManagement.getSignalSubmitted().Reset();
              thisState = States.CheckQueue;
              break;
            }

            /*
             * Timed out, go check some other things
             */
            thisState = States.Maintenance;
            break;

          case CheckQueue:
            /*
             * Check the queue to see if there are any experiments waiting
             */
            if (this.labManagement.getExperimentQueueDB().GetCountWaiting() > 0) {
              thisState = States.StartEngine;
              break;
            }

            thisState = States.Idle;
            break;

          case StartEngine:
            /*
             * Find the available experiment engine to run a waiting experiment
             */
            boolean foundAvailable = false;
            for (int i = 0; i < this.labManagement.getFarmSize(); i++) {
              /*
               * Determine the next experiment engine for running the experiment
               */
              if (++nextUnit == this.labManagement.getFarmSize()) {
                nextUnit = 0;
              }
              if (debugTrace == true) {
                System.out.println(String.format("[nextUnit: %s]", nextUnit));
              }

              /*
               * Get the experiment engine and check to see if it is online
               */
              LabExperimentEngine labExperimentEngine = this.labExperimentEngines[nextUnit];
              LabStatus labStatus = labExperimentEngine.GetLabStatus();
              if (labStatus.isOnline() == false) {
                /*
                 * This one is not online
                 */
                if (debugTrace == true) {
                  System.out.println(String.format("[nextUnit: %s is Offline]", nextUnit));
                }
                continue;
              }

              /*
               * Determine if this experiment engine is currently running
               */
              if (labExperimentEngine.isRunning() == false) {
                /*
                 * Not running, try starting it
                 */
                if (labExperimentEngine.Start() == true) {
                  /*
                   * The available engine has been started, wait a bit before checking the queue
                   */
                  foundAvailable = true;
                  Delay.MilliSeconds(1000);
                  break;
                }
              }
            }

            /*
             * Check if there are any engines available
             */
            if (foundAvailable == false) {
              /*
               * No engines available to start running
               */
              thisState = States.Idle;
              break;
            }

            thisState = States.CheckNotified;
            break;

          case CheckNotified:
            /*
             * Check if any experiments have not notified their ServiceBroker
             */
            success = true;
            ArrayList<ExperimentResultInfo> allNotNotified =
                this.labManagement.getExperimentResultsDB().RetrieveAllNotNotified();
            if (allNotNotified != null) {
              Iterator iterator = allNotNotified.iterator();
              while (iterator.hasNext() && success == true) {
                ExperimentResultInfo experimentResultInfo = (ExperimentResultInfo) iterator.next();
                success =
                    this.labExperimentEngines[0].NotifyServiceBroker(
                        experimentResultInfo.getExperimentId(), experimentResultInfo.getSbName());
              }
            }

            thisState = States.CheckQueue;
            break;

          case Maintenance:
            thisState = States.Idle;
            break;

          case StopRunning:
            thisState = States.Done;
            break;
        }
      }
    } catch (Exception ex) {
      Logfile.WriteError(ex.toString());
    }

    /*
     * Thread is no longer running
     */
    this.running = false;

    Logfile.WriteCompleted(logLevel, STR_ClassName, methodName);
  }