/**
   * @param experimentId
   * @param sbName
   * @return
   */
  public ResultReport RetrieveResult(int experimentId, String sbName) {
    final String methodName = "RetrieveResult";
    Logfile.WriteCalled(
        logLevel,
        STR_ClassName,
        methodName,
        String.format(STRLOG_ExperimentIdSbName_arg2, experimentId, sbName));

    ResultReport resultReport;

    try {
      /*
       * Try getting the result of the completed experiment
       */
      resultReport =
          this.labManagement.getExperimentResultsDB().RetrieveResultReport(experimentId, sbName);
      if (resultReport.getStatusCode() == StatusCodes.Unknown) {
        /*
         * No results found for the experiment, check the queue to see if it ever existed
         */
        ExperimentQueueInfo experimentQueueInfo =
            this.labManagement.getExperimentQueueDB().RetrieveByExperimentId(experimentId, sbName);
        if (experimentQueueInfo != null) {
          resultReport.setStatusCode(experimentQueueInfo.getStatusCode());
        }
      }
    } catch (Exception ex) {
      Logfile.WriteError(ex.toString());
      resultReport = new ResultReport(StatusCodes.Unknown, ex.toString());
    }

    Logfile.WriteCompleted(
        logLevel,
        STR_ClassName,
        methodName,
        String.format(STRLOG_StatusCode_arg, resultReport.getStatusCode()));

    return resultReport;
  }
  @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);
  }
  /**
   * @param experimentId
   * @param sbName
   * @return
   */
  public synchronized LabExperimentStatus GetLabExperimentStatus(int experimentId, String sbName) {
    final String methodName = "GetLabExperimentStatus";
    Logfile.WriteCalled(
        Level.INFO,
        STR_ClassName,
        methodName,
        String.format(STRLOG_ExperimentIdSbName_arg2, experimentId, sbName));

    LabExperimentStatus labExperimentStatus = new LabExperimentStatus();

    try {
      /*
       * Check that parameters are valid
       */
      if (experimentId <= 0) {
        throw new RuntimeException(String.format(STRERR_InvalidExperimentId_arg, experimentId));
      }
      if (sbName == null) {
        throw new NullPointerException(STRERR_SbName);
      }

      /*
       * Get the status of the experiment from the queue
       */
      ExperimentQueueInfo experimentQueueInfo =
          this.labManagement.getExperimentQueueDB().RetrieveByExperimentId(experimentId, sbName);
      if (experimentQueueInfo == null) {
        throw new RuntimeException(
            String.format(STRERR_UnknownExperimentSbNameExperimentId_arg2, sbName, experimentId));
      }

      System.out.println(String.format("StatusCode: %s", experimentQueueInfo.getStatusCode()));

      /*
       * Experiment exists, check status
       */
      switch (experimentQueueInfo.getStatusCode()) {
        case Waiting:
          /*
           * Experiment is waiting on the queue, get the queue position and wait time
           */
          QueuedExperimentInfo queuedExperimentInfo =
              this.labManagement
                  .getExperimentQueueDB()
                  .GetQueuedExperimentInfo(experimentId, sbName);
          WaitEstimate waitEstimate = new WaitEstimate();
          waitEstimate.setEffectiveQueueLength(queuedExperimentInfo.getPosition());
          waitEstimate.setEstWait(
              queuedExperimentInfo.getWaitTime() + this.GetMinRemainingRuntime());

          System.out.println(
              String.format(
                  "WaitEstimate: QueueLength=%d  WaitTime=%f01",
                  waitEstimate.getEffectiveQueueLength(), waitEstimate.getEstWait()));

          /*
           * Set the experiment status and time it takes to run the experiment
           */
          ExperimentStatus experimentStatus =
              new ExperimentStatus(experimentQueueInfo.getStatusCode());
          experimentStatus.setEstRuntime(experimentQueueInfo.getEstimatedExecTime());
          experimentStatus.setEstRemainingRuntime(experimentQueueInfo.getEstimatedExecTime());
          experimentStatus.setWaitEstimate(waitEstimate);

          labExperimentStatus = new LabExperimentStatus(experimentStatus);

          Logfile.Write(
              logLevel,
              String.format(
                  STRLOG_ExperimentStatus_arg4,
                  waitEstimate.getEffectiveQueueLength(),
                  waitEstimate.getEstWait(),
                  experimentStatus.getEstRuntime(),
                  experimentStatus.getEstRemainingRuntime()));

          System.out.println(
              String.format(
                  STRLOG_ExperimentStatus_arg4,
                  waitEstimate.getEffectiveQueueLength(),
                  waitEstimate.getEstWait(),
                  experimentStatus.getEstRuntime(),
                  experimentStatus.getEstRemainingRuntime()));
          break;

        case Running:
          /*
           * Get the experiment status from the lab experiment engine
           */
          LabExperimentEngine labExperimentEngine =
              this.labExperimentEngines[experimentQueueInfo.getUnitId()];
          labExperimentStatus.setExperimentStatus(
              labExperimentEngine.GetExperimentStatus(experimentId, sbName));
          break;

        case Cancelled:
          /*
           * The experiment was cancelled while waiting on the queue
           */
          labExperimentStatus.setExperimentStatus(new ExperimentStatus(StatusCodes.Cancelled));
          break;

        default:
          /*
           * Experiment has completed, cancelled or failed so get the status from the experiment results
           */
          ResultReport resultReport =
              this.labManagement
                  .getExperimentResultsDB()
                  .RetrieveResultReport(experimentId, sbName);
          labExperimentStatus.setExperimentStatus(
              new ExperimentStatus(resultReport.getStatusCode()));
          break;
      }
    } catch (Exception ex) {
      Logfile.WriteError(ex.toString());
      labExperimentStatus.setExperimentStatus(new ExperimentStatus(StatusCodes.Unknown));
    }

    ExperimentStatus experimentStatus = labExperimentStatus.getExperimentStatus();

    Logfile.WriteCompleted(
        Level.INFO,
        STR_ClassName,
        methodName,
        String.format(
            STRLOG_ExperimentStatus_arg3,
            experimentStatus.getStatusCode(),
            experimentStatus.getEstRuntime(),
            experimentStatus.getEstRemainingRuntime()));

    return labExperimentStatus;
  }