/**
   * Dump Java Class Histogram to CSV.
   *
   * @param isSelected If this value is true, this method dumps data which is selected in class
   *     filter, otherwise this method dumps all snapshot data.
   */
  public void dumpClassHistogramToCSV(boolean isSelected) {
    FileChooser dialog = new FileChooser();
    ResourceBundle resource =
        ResourceBundle.getBundle("snapshotResources", new Locale(HeapStatsUtils.getLanguage()));

    dialog.setTitle(resource.getString("dialog.csvchooser.title"));
    dialog.setInitialDirectory(new File(HeapStatsUtils.getDefaultDirectory()));
    dialog
        .getExtensionFilters()
        .addAll(
            new ExtensionFilter("CSV file (*.csv)", "*.csv"),
            new ExtensionFilter("All files", "*.*"));
    File csvFile = dialog.showSaveDialog(WindowController.getInstance().getOwner());

    if (csvFile != null) {
      Predicate<? super ObjectData> filter = histogramController.getFilter();
      TaskAdapter<CSVDumpHeap> task =
          new TaskAdapter<>(
              new CSVDumpHeap(
                  csvFile,
                  isSelected ? currentTarget.get() : startCombo.getItems(),
                  isSelected ? filter : null,
                  HeapStatsUtils.getReplaceClassName()));
      super.bindTask(task);

      Thread parseThread = new Thread(task);
      parseThread.start();
    }
  }
  /**
   * Event handler of OK button.
   *
   * @param event ActionEvent of this event.
   */
  @FXML
  private void onOkClick(ActionEvent event) {
    int startIdx = startCombo.getSelectionModel().getSelectedIndex();
    int endIdx = endCombo.getSelectionModel().getSelectedIndex();
    currentTarget.set(
        FXCollections.observableArrayList(startCombo.getItems().subList(startIdx, endIdx + 1)));
    currentClassNameSet.set(FXCollections.observableSet());
    summaryData.set(new SummaryData(currentTarget.get()));

    Task<Void> topNTask = histogramController.getDrawTopNDataTask(currentTarget.get(), true, null);
    super.bindTask(topNTask);
    Thread topNThread = new Thread(topNTask);
    topNThread.start();

    Task<Void> summarizeTask =
        summaryController.getCalculateGCSummaryTask(this::drawRebootSuspectLine);
    super.bindTask(summarizeTask);
    Thread summarizeThread = new Thread(summarizeTask);
    summarizeThread.start();
  }
  @Override
  public void setData(Object data, boolean select) {
    super.setData(data, select);
    snapshotList.setText((String) data);

    TaskAdapter<ParseHeader> task =
        new TaskAdapter<>(
            new ParseHeader(
                Arrays.asList((String) data), HeapStatsUtils.getReplaceClassName(), true));
    task.setOnSucceeded(
        evt -> {
          startCombo.setItems(FXCollections.observableArrayList(task.getTask().getSnapShotList()));
          endCombo.setItems(FXCollections.observableArrayList(task.getTask().getSnapShotList()));
          startCombo.getSelectionModel().selectFirst();
          endCombo.getSelectionModel().selectLast();
        });
    super.bindTask(task);

    Thread parseThread = new Thread(task);
    parseThread.start();
  }
  /**
   * Event handler of SnapShot file button.
   *
   * @param event ActionEvent of this event.
   */
  @FXML
  public void onSnapshotFileClick(ActionEvent event) {
    FileChooser dialog = new FileChooser();
    ResourceBundle resource =
        ResourceBundle.getBundle("snapshotResources", new Locale(HeapStatsUtils.getLanguage()));

    dialog.setTitle(resource.getString("dialog.filechooser.title"));
    dialog.setInitialDirectory(new File(HeapStatsUtils.getDefaultDirectory()));
    dialog
        .getExtensionFilters()
        .addAll(
            new ExtensionFilter("SnapShot file (*.dat)", "*.dat"),
            new ExtensionFilter("All files", "*.*"));

    List<File> snapshotFileList =
        dialog.showOpenMultipleDialog(WindowController.getInstance().getOwner());

    if (snapshotFileList != null) {
      clearAllItems();

      HeapStatsUtils.setDefaultDirectory(snapshotFileList.get(0).getParent());
      List<String> files =
          snapshotFileList.stream().map(File::getAbsolutePath).collect(Collectors.toList());
      snapshotList.setText(files.stream().collect(Collectors.joining("; ")));

      TaskAdapter<ParseHeader> task =
          new TaskAdapter<>(new ParseHeader(files, HeapStatsUtils.getReplaceClassName(), true));
      task.setOnSucceeded(
          evt -> {
            ObservableList<SnapShotHeader> list =
                FXCollections.observableArrayList(task.getTask().getSnapShotList());
            startCombo.setItems(list);
            endCombo.setItems(list);
            startCombo.getSelectionModel().selectFirst();
            endCombo.getSelectionModel().selectLast();
          });
      super.bindTask(task);

      Thread parseThread = new Thread(task);
      parseThread.start();
    }
  }
  /**
   * Dump GC Statistics to CSV.
   *
   * @param isSelected If this value is true, this method dumps data which is selected time range,
   *     otherwise this method dumps all snapshot data.
   */
  public void dumpGCStatisticsToCSV(boolean isSelected) {
    FileChooser dialog = new FileChooser();
    dialog.setTitle("Select CSV files");
    dialog.setInitialDirectory(new File(HeapStatsUtils.getDefaultDirectory()));
    dialog
        .getExtensionFilters()
        .addAll(
            new ExtensionFilter("CSV file (*.csv)", "*.csv"),
            new ExtensionFilter("All files", "*.*"));
    File csvFile = dialog.showSaveDialog(WindowController.getInstance().getOwner());

    if (csvFile != null) {
      TaskAdapter<CSVDumpGC> task =
          new TaskAdapter<>(
              new CSVDumpGC(csvFile, isSelected ? currentTarget.get() : startCombo.getItems()));
      super.bindTask(task);

      Thread parseThread = new Thread(task);
      parseThread.start();
    }
  }
  /** Initializes the controller class. */
  @Override
  public void initialize(URL url, ResourceBundle rb) {
    super.initialize(url, rb);

    summaryData = new SimpleObjectProperty<>();
    summaryController.summaryDataProperty().bind(summaryData);
    currentTarget = new SimpleObjectProperty<>(FXCollections.emptyObservableList());
    summaryController.currentTargetProperty().bind(currentTarget);
    histogramController.currentTargetProperty().bind(currentTarget);
    snapshotController.currentTargetProperty().bind(currentTarget);
    currentClassNameSet = new SimpleObjectProperty<>();
    summaryController.currentClassNameSetProperty().bind(currentClassNameSet);
    histogramController.currentClassNameSetProperty().bind(currentClassNameSet);
    histogramController.instanceGraphProperty().bind(radioInstance.selectedProperty());
    snapshotController.instanceGraphProperty().bind(radioInstance.selectedProperty());
    snapshotSelectionModel = new SimpleObjectProperty<>();
    snapshotSelectionModel.bind(snapshotController.snapshotSelectionModelProperty());
    histogramController.snapshotSelectionModelProperty().bind(snapshotSelectionModel);
    histogramController.setDrawRebootSuspectLine(this::drawRebootSuspectLine);
    topNList = new SimpleObjectProperty<>();
    topNList.bind(histogramController.topNListProperty());
    snapshotController.topNListProperty().bind(topNList);
    currentSnapShotHeader = new SimpleObjectProperty<>();
    currentSnapShotHeader.bind(
        snapshotController.snapshotSelectionModelProperty().get().selectedItemProperty());
    reftreeController.currentSnapShotHeaderProperty().bind(currentSnapShotHeader);

    currentObjectTag = new SimpleLongProperty();
    // TODO:
    //   Why can I NOT use binding?
    //   First binding is enabled. But second binding is disabled.
    //   So I use ChangeListener to avoid this issue.
    // currentObjectTag.bind(histogramController.currentObjectTagProperty());
    // currentObjectTag.bind(snapshotController.currentObjectTagProperty());
    histogramController
        .currentObjectTagProperty()
        .addListener(
            (v, o, n) -> Optional.ofNullable(n).ifPresent(m -> currentObjectTag.set((Long) m)));
    snapshotController
        .currentObjectTagProperty()
        .addListener(
            (v, o, n) -> Optional.ofNullable(n).ifPresent(m -> currentObjectTag.set((Long) m)));
    reftreeController.currentObjectTagProperty().bind(currentObjectTag);

    snapshotMain.getSelectionModel().selectedItemProperty().addListener(this::onTabChanged);

    startCombo.setConverter(new SnapShotHeaderConverter());
    endCombo.setConverter(new SnapShotHeaderConverter());

    okBtn
        .disableProperty()
        .bind(
            startCombo
                .getSelectionModel()
                .selectedIndexProperty()
                .greaterThanOrEqualTo(endCombo.getSelectionModel().selectedIndexProperty()));

    setOnWindowResize(
        (v, o, n) ->
            Platform.runLater(
                () ->
                    Stream.of(
                            summaryController.getHeapChart(),
                            summaryController.getInstanceChart(),
                            summaryController.getGcTimeChart(),
                            summaryController.getMetaspaceChart(),
                            histogramController.getTopNChart())
                        .forEach(c -> Platform.runLater(() -> drawRebootSuspectLine(c)))));

    histogramController.setTaskExecutor(
        t -> {
          bindTask(t);
          (new Thread(t)).start();
        });
  }