/**
   * Runs a relay defined by a chain of steps.
   *
   * @param <RES> return type of the entire relay
   * @param firstStep a first step that defines the entire relay
   * @param callback
   * @param relaySyncCallback a {@link SyncCallback} wrapped as {@link RelaySyncCallback}
   */
  public static <RES> RelayOk run(
      final WipCommandProcessor commandProcessor,
      Step<RES> firstStep,
      final GenericCallback<RES> callback,
      final RelaySyncCallback relaySyncCallback) {

    return firstStep.accept(
        new Step.Visitor<RelayOk, RES>() {
          @Override
          public RelayOk visitFinal(RES finalResult) {
            if (callback != null) {
              callback.success(finalResult);
            }
            return relaySyncCallback.finish();
          }

          @Override
          public RelayOk visitSend(final SendStepSimple<RES> sendStep) {
            final RelaySyncCallback.Guard guard = relaySyncCallback.newGuard();

            WipCommandCallback sendCallback =
                new WipCommandCallback() {
                  @Override
                  public void messageReceived(WipCommandResponse response) {
                    Step<RES> processResult;
                    try {
                      processResult = sendStep.processResponse();
                    } catch (ProcessException e) {
                      if (callback != null) {
                        callback.failure(e);
                      }
                      // Todo: consider throwing e.
                      return;
                    }
                    RelayOk relayOk =
                        run(commandProcessor, processResult, callback, guard.getRelay());
                    guard.discharge(relayOk);
                  }

                  @Override
                  public void failure(String message) {
                    if (callback != null) {
                      callback.failure(sendStep.processFailure(new Exception(message)));
                    }
                  }
                };

            return commandProcessor.send(
                sendStep.getParams(), sendCallback, guard.asSyncCallback());
          }

          @Override
          public <RESPONSE> RelayOk visitSend(final SendStepWithResponse<RESPONSE, RES> sendStep) {
            final RelaySyncCallback.Guard guard = relaySyncCallback.newGuard();

            GenericCallback<RESPONSE> sendCallback =
                new GenericCallback<RESPONSE>() {
                  @Override
                  public void success(RESPONSE response) {
                    Step<RES> processResult;
                    try {
                      processResult = sendStep.processResponse(response);
                    } catch (ProcessException e) {
                      if (callback != null) {
                        callback.failure(e);
                      }
                      // Todo: consider throwing e.
                      return;
                    }
                    RelayOk relayOk =
                        run(commandProcessor, processResult, callback, guard.getRelay());
                    guard.discharge(relayOk);
                  }

                  @Override
                  public void failure(Exception exception) {
                    if (callback != null) {
                      callback.failure(sendStep.processFailure(exception));
                    }
                  }
                };

            return commandProcessor.send(
                sendStep.getParams(), sendCallback, guard.asSyncCallback());
          }
        });
  }