@Inject
  public StartStopScanningAction(
      ScannerDispatcherThreadFactory scannerThreadFactory,
      StateMachine stateMachine,
      ResultTable resultTable,
      StatusBar statusBar,
      FeederGUIRegistry feederRegistry,
      PingerRegistry pingerRegistry,
      @Named("startStopButton") Button startStopButton,
      GUIConfig guiConfig) {
    this(startStopButton.getDisplay());

    this.scannerThreadFactory = scannerThreadFactory;
    this.resultTable = resultTable;
    this.statusBar = statusBar;
    this.feederRegistry = feederRegistry;
    this.pingerRegistry = pingerRegistry;
    this.button = startStopButton;
    this.stateMachine = stateMachine;
    this.guiConfig = guiConfig;

    // add listeners to all state changes
    stateMachine.addTransitionListener(this);

    // set the default image
    ScanningState state = stateMachine.getState();
    button.setImage(buttonImages[state.ordinal()]);
    button.setText(buttonTexts[state.ordinal()]);
  }
  public void transitionTo(final ScanningState state, final Transition transition) {
    if (statusBar.isDisposed() || transition == Transition.INIT) return;

    // TODO: separate GUI and non-GUI stuff
    switch (state) {
      case IDLE:
        // reset state text
        button.setEnabled(true);
        updateProgress(null, 0, 0);
        statusBar.setStatusText(null);
        break;
      case STARTING:
        // start the scan from scratch!
        resultTable.removeAll();
        try {
          scannerThread =
              scannerThreadFactory.createScannerThread(
                  feederRegistry.createFeeder(),
                  StartStopScanningAction.this,
                  createResultsCallback(state));
          stateMachine.startScanning();
          mainWindowTitle = statusBar.getShell().getText();
        } catch (RuntimeException e) {
          stateMachine.reset();
          throw e;
        }
        break;
      case RESTARTING:
        // restart the scanning - rescan
        resultTable.resetSelection();
        try {
          scannerThread =
              scannerThreadFactory.createScannerThread(
                  feederRegistry.createRescanFeeder(resultTable.getSelection()),
                  StartStopScanningAction.this,
                  createResultsCallback(state));
          stateMachine.startScanning();
          mainWindowTitle = statusBar.getShell().getText();
        } catch (RuntimeException e) {
          stateMachine.reset();
          throw e;
        }
        break;
      case SCANNING:
        scannerThread.start();
        break;
      case STOPPING:
        statusBar.setStatusText(Labels.getLabel("state.waitForThreads"));
        break;
      case KILLING:
        button.setEnabled(false);
        statusBar.setStatusText(Labels.getLabel("state.killingThreads"));
        break;
    }
    // change button image
    button.setImage(buttonImages[state.ordinal()]);
    button.setText(buttonTexts[state.ordinal()]);
  }
/**
 * Start/Stop button action class. It listens to presses on the buttons as well as updates gui
 * statuses
 *
 * @author Anton Keks
 */
@Singleton
public class StartStopScanningAction
    implements SelectionListener, ScanningProgressCallback, StateTransitionListener {

  private ScannerDispatcherThreadFactory scannerThreadFactory;
  private ScannerDispatcherThread scannerThread;
  private GUIConfig guiConfig;
  private PingerRegistry pingerRegistry;

  private String mainWindowTitle;
  private StatusBar statusBar;
  private ResultTable resultTable;
  private FeederGUIRegistry feederRegistry;
  private Button button;

  Image[] buttonImages = new Image[ScanningState.values().length];
  String[] buttonTexts = new String[ScanningState.values().length];

  private Display display;
  private StateMachine stateMachine;

  /** Creates internal stuff independent from all other external dependencies */
  StartStopScanningAction(Display display) {
    this.display = display;

    // preload button images
    buttonImages[ScanningState.IDLE.ordinal()] =
        new Image(display, Labels.getInstance().getImageAsStream("button.start.img"));
    buttonImages[ScanningState.SCANNING.ordinal()] =
        new Image(display, Labels.getInstance().getImageAsStream("button.stop.img"));
    buttonImages[ScanningState.STARTING.ordinal()] = buttonImages[ScanningState.SCANNING.ordinal()];
    buttonImages[ScanningState.RESTARTING.ordinal()] =
        buttonImages[ScanningState.SCANNING.ordinal()];
    buttonImages[ScanningState.STOPPING.ordinal()] =
        new Image(display, Labels.getInstance().getImageAsStream("button.kill.img"));
    buttonImages[ScanningState.KILLING.ordinal()] = buttonImages[ScanningState.STOPPING.ordinal()];

    // preload button texts
    buttonTexts[ScanningState.IDLE.ordinal()] = Labels.getLabel("button.start");
    buttonTexts[ScanningState.SCANNING.ordinal()] = Labels.getLabel("button.stop");
    buttonTexts[ScanningState.STARTING.ordinal()] = buttonTexts[ScanningState.SCANNING.ordinal()];
    buttonTexts[ScanningState.RESTARTING.ordinal()] = buttonTexts[ScanningState.SCANNING.ordinal()];
    buttonTexts[ScanningState.STOPPING.ordinal()] = Labels.getLabel("button.kill");
    buttonTexts[ScanningState.KILLING.ordinal()] = Labels.getLabel("button.kill");
  }

  @Inject
  public StartStopScanningAction(
      ScannerDispatcherThreadFactory scannerThreadFactory,
      StateMachine stateMachine,
      ResultTable resultTable,
      StatusBar statusBar,
      FeederGUIRegistry feederRegistry,
      PingerRegistry pingerRegistry,
      @Named("startStopButton") Button startStopButton,
      GUIConfig guiConfig) {
    this(startStopButton.getDisplay());

    this.scannerThreadFactory = scannerThreadFactory;
    this.resultTable = resultTable;
    this.statusBar = statusBar;
    this.feederRegistry = feederRegistry;
    this.pingerRegistry = pingerRegistry;
    this.button = startStopButton;
    this.stateMachine = stateMachine;
    this.guiConfig = guiConfig;

    // add listeners to all state changes
    stateMachine.addTransitionListener(this);

    // set the default image
    ScanningState state = stateMachine.getState();
    button.setImage(buttonImages[state.ordinal()]);
    button.setText(buttonTexts[state.ordinal()]);
  }

  /** Called when scanning button is clicked */
  public void widgetDefaultSelected(SelectionEvent e) {
    widgetSelected(e);
  }

  /** Called when scanning button is clicked */
  public void widgetSelected(SelectionEvent event) {
    // ask for confirmation before erasing scanning results
    if (stateMachine.inState(ScanningState.IDLE)) {
      if (!preScanChecks()) return;
    }
    ScanMenuActions.isLoadedFromFile = false;
    stateMachine.transitionToNext();
  }

  private boolean preScanChecks() {
    // autodetect usable pingers and silently ignore any changes -
    // user should see any errors only if they have explicitly selected a pinger
    pingerRegistry.checkSelectedPinger();

    // ask user for confirmation if needed
    if (guiConfig.askScanConfirmation && resultTable.getItemCount() > 0) {
      MessageBox box =
          new MessageBox(resultTable.getShell(), SWT.ICON_QUESTION | SWT.YES | SWT.NO | SWT.SHEET);
      box.setText(Labels.getLabel("text.scan.new"));
      box.setMessage(Labels.getLabel("text.scan.confirmation"));
      if (box.open() != SWT.YES) {
        return false;
      }
    }
    return true;
  }

  public void transitionTo(final ScanningState state, final Transition transition) {
    if (statusBar.isDisposed() || transition == Transition.INIT) return;

    // TODO: separate GUI and non-GUI stuff
    switch (state) {
      case IDLE:
        // reset state text
        button.setEnabled(true);
        updateProgress(null, 0, 0);
        statusBar.setStatusText(null);
        break;
      case STARTING:
        // start the scan from scratch!
        resultTable.removeAll();
        try {
          scannerThread =
              scannerThreadFactory.createScannerThread(
                  feederRegistry.createFeeder(),
                  StartStopScanningAction.this,
                  createResultsCallback(state));
          stateMachine.startScanning();
          mainWindowTitle = statusBar.getShell().getText();
        } catch (RuntimeException e) {
          stateMachine.reset();
          throw e;
        }
        break;
      case RESTARTING:
        // restart the scanning - rescan
        resultTable.resetSelection();
        try {
          scannerThread =
              scannerThreadFactory.createScannerThread(
                  feederRegistry.createRescanFeeder(resultTable.getSelection()),
                  StartStopScanningAction.this,
                  createResultsCallback(state));
          stateMachine.startScanning();
          mainWindowTitle = statusBar.getShell().getText();
        } catch (RuntimeException e) {
          stateMachine.reset();
          throw e;
        }
        break;
      case SCANNING:
        scannerThread.start();
        break;
      case STOPPING:
        statusBar.setStatusText(Labels.getLabel("state.waitForThreads"));
        break;
      case KILLING:
        button.setEnabled(false);
        statusBar.setStatusText(Labels.getLabel("state.killingThreads"));
        break;
    }
    // change button image
    button.setImage(buttonImages[state.ordinal()]);
    button.setText(buttonTexts[state.ordinal()]);
  }

  /**
   * @return the appropriate ResultsCallback instance, depending on the configured display method.
   */
  private ScanningResultCallback createResultsCallback(ScanningState state) {
    // rescanning must follow the same strategy of displaying all hosts (even the dead ones),
    // because the results are already in the list
    if (guiConfig.displayMethod == DisplayMethod.ALL || state == ScanningState.RESTARTING) {
      return new ScanningResultCallback() {
        public void prepareForResults(ScanningResult result) {
          resultTable.addOrUpdateResultRow(result);
        }

        public void consumeResults(ScanningResult result) {
          resultTable.addOrUpdateResultRow(result);
        }
      };
    }
    if (guiConfig.displayMethod == DisplayMethod.ALIVE) {
      return new ScanningResultCallback() {
        public void prepareForResults(ScanningResult result) {}

        public void consumeResults(ScanningResult result) {
          if (result.getType().ordinal() >= ResultType.ALIVE.ordinal())
            resultTable.addOrUpdateResultRow(result);
        }
      };
    }
    if (guiConfig.displayMethod == DisplayMethod.PORTS) {
      return new ScanningResultCallback() {
        public void prepareForResults(ScanningResult result) {}

        public void consumeResults(ScanningResult result) {
          if (result.getType() == ResultType.WITH_PORTS) resultTable.addOrUpdateResultRow(result);
        }
      };
    }
    throw new UnsupportedOperationException(guiConfig.displayMethod.toString());
  }

  public void updateProgress(
      final InetAddress currentAddress, final int runningThreads, final int percentageComplete) {
    if (display.isDisposed()) return;
    display.asyncExec(
        new Runnable() {
          public void run() {
            if (statusBar.isDisposed()) return;

            // update status bar
            if (currentAddress != null) {
              statusBar.setStatusText(
                  Labels.getLabel("state.scanning") + currentAddress.getHostAddress());
            }
            statusBar.setRunningThreads(runningThreads);
            statusBar.setProgress(percentageComplete);

            // show percentage in main window title
            if (!stateMachine.inState(ScanningState.IDLE))
              statusBar.getShell().setText(percentageComplete + "% - " + mainWindowTitle);
            else statusBar.getShell().setText(mainWindowTitle);

            // change button image according to the current state
            button.setImage(buttonImages[stateMachine.getState().ordinal()]);
          }
        });
  }
}