@Override
  protected boolean performLeavingAction(WizardStepDirection direction) {
    if (direction == WizardStepDirection.FINISH) {
      try {
        if (state.getTranslator() != null) {
          state.getTranslator().close();
        }
      } catch (OperatorException e) {
        ImportWizardUtils.showErrorMessage(
            state.getDataResultSetFactory().getResourceName(), e.toString(), e);
      }

      // use settings edited by user even if he never pressed Enter or otherwise confirmed his
      // changes
      for (int i = 0; i < previewTable.getColumnCount(); i++) {
        EditableTableHeaderColumn col =
            (EditableTableHeaderColumn) previewTable.getColumnModel().getColumn(i);
        if (col.getHeaderEditor() instanceof MetaDataTableHeaderCellEditor) {
          MetaDataTableHeaderCellEditor editor =
              (MetaDataTableHeaderCellEditor) col.getHeaderEditor();
          editor.updateColumnMetaData();
        }
      }
    }
    return true;
  }
/**
 * This Wizard Step might be used to defined the meta data of each attribute.
 *
 * @author Sebastian Land, Simon Fischer
 */
public class MetaDataDeclarationWizardStep extends WizardStep {

  /** Publicly exposes the method {@link #configurePropertiesFromAction(Action)} public. */
  private static class ReconfigurableButton extends JButton {

    private static final long serialVersionUID = 1L;

    private ReconfigurableButton(Action action) {
      super(action);
    }

    @Override
    protected void configurePropertiesFromAction(Action a) {
      super.configurePropertiesFromAction(a);
    }
  }

  private Action reloadAction =
      new ResourceAction("wizard.validate_value_types") {

        private static final long serialVersionUID = 1L;

        @Override
        public void actionPerformed(ActionEvent e) {
          toggleReload();
        }
      };
  private Action cancelReloadAction =
      new ResourceAction("wizard.abort_validate_value_types") {

        private static final long serialVersionUID = 1L;

        @Override
        public void actionPerformed(ActionEvent e) {
          toggleReload();
        }
      };
  private ReconfigurableButton reloadButton = new ReconfigurableButton(reloadAction);

  private Action guessValueTypes =
      new ResourceAction("wizard.guess_value_types") {

        private static final long serialVersionUID = 1L;

        @Override
        public void actionPerformed(ActionEvent e) {
          toggleGuessValueTypes();
        }
      };
  private Action cancelGuessValueTypes =
      new ResourceAction("wizard.abort_guess_value_types") {

        private static final long serialVersionUID = 1L;

        @Override
        public void actionPerformed(ActionEvent e) {
          toggleGuessValueTypes();
        }
      };
  private ReconfigurableButton guessButton = new ReconfigurableButton(guessValueTypes);

  private JCheckBox errorsAsMissingBox =
      new JCheckBox(
          new ResourceAction("wizard.error_tolerant") {

            private static final long serialVersionUID = 1L;

            @Override
            public void actionPerformed(ActionEvent e) {
              state.getTranslationConfiguration().setFaultTolerant(errorsAsMissingBox.isSelected());
            }
          });
  private JCheckBox filterErrorsBox =
      new JCheckBox(
          new ResourceAction("wizard.show_error_rows") {

            private static final long serialVersionUID = 1L;

            @Override
            public void actionPerformed(ActionEvent e) {
              if (filteredModel != null) {
                filteredModel.setFilterEnabled(filterErrorsBox.isSelected());
              }
            }
          });

  private JComboBox dateFormatField =
      new JComboBox(ParameterTypeDateFormat.PREDEFINED_DATE_FORMATS);

  private JCheckBox limitedPreviewBox =
      new JCheckBox(
          I18N.getMessage(
              I18N.getGUIBundle(),
              "gui.action.importwizard.limited_preview.label",
              ImportWizardUtils.getPreviewLength()));

  private WizardState state;

  private JPanel panel = new JPanel(new BorderLayout());
  private JScrollPane tableScrollPane;

  private ErrorTableModel errorTableModel = new ErrorTableModel();
  private RowFilteringTableModel filteredModel;
  private JLabel errorLabel = new JLabel();

  private boolean canProceed = true;
  private MetaDataValidator headerValidator;

  public MetaDataDeclarationWizardStep(WizardState state) {
    super("importwizard.metadata");
    limitedPreviewBox.setSelected(true);

    this.state = state;
    this.headerValidator = new MetaDataValidator(this, state.getTranslationConfiguration());
    dateFormatField.setEditable(true);
    dateFormatField.addActionListener(
        new ActionListener() {

          @Override
          public void actionPerformed(ActionEvent e) {
            MetaDataDeclarationWizardStep.this
                .state
                .getTranslationConfiguration()
                .setDatePattern((String) dateFormatField.getSelectedItem());
          }
        });

    JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
    buttonPanel.add(reloadButton);
    buttonPanel.add(guessButton);
    buttonPanel.add(limitedPreviewBox);

    JLabel label = new ResourceLabel("date_format");
    label.setLabelFor(dateFormatField);
    buttonPanel.add(label);
    buttonPanel.add(dateFormatField);
    panel.add(buttonPanel, BorderLayout.NORTH);

    JPanel errorPanel = new JPanel(new GridBagLayout());
    GridBagConstraints c = new GridBagConstraints();
    c.fill = GridBagConstraints.BOTH;
    c.anchor = GridBagConstraints.FIRST_LINE_START;
    c.ipadx = c.ipady = 4;
    c.weighty = 0;
    c.weightx = 1;

    c.gridwidth = 1;
    c.weightx = 1;
    errorPanel.add(errorLabel, c);

    c.weightx = 0;
    c.gridwidth = GridBagConstraints.RELATIVE;
    errorPanel.add(errorsAsMissingBox, c);
    c.weightx = 0;
    c.gridwidth = GridBagConstraints.REMAINDER;
    errorPanel.add(filterErrorsBox, c);

    final JTable errorTable = new JTable(errorTableModel);
    errorTable
        .getSelectionModel()
        .addListSelectionListener(
            new ListSelectionListener() {

              @Override
              public void valueChanged(ListSelectionEvent e) {
                if (!e.getValueIsAdjusting()) {
                  final int selected = errorTable.getSelectedRow();
                  if (selected >= 0) {
                    ParsingError error = errorTableModel.getErrorInRow(selected);
                    int row = error.getExampleIndex();
                    row = filteredModel.inverseTranslateRow(row);
                    if (row == -1) {
                      return;
                    }
                    int col = error.getColumn();
                    previewTable.setRowSelectionInterval(row, row);
                    previewTable.setColumnSelectionInterval(col, col);
                  }
                }
              }
            });
    final JScrollPane errorScrollPane = new JScrollPane(errorTable);
    errorScrollPane.setPreferredSize(new Dimension(500, 80));
    c.weighty = 1;
    c.gridwidth = GridBagConstraints.REMAINDER;
    errorPanel.add(errorScrollPane, c);

    panel.add(errorPanel, BorderLayout.SOUTH);

    final JLabel dummy = new JLabel("-");
    dummy.setPreferredSize(new Dimension(500, 500));
    dummy.setMinimumSize(new Dimension(500, 500));
    tableScrollPane =
        new JScrollPane(
            dummy, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
    panel.add(tableScrollPane, BorderLayout.CENTER);
  }

  @Override
  protected boolean performEnteringAction(WizardStepDirection direction) {
    dateFormatField.setSelectedItem(state.getTranslationConfiguration().getDatePattern());
    errorsAsMissingBox.setSelected(state.getTranslationConfiguration().isFaultTolerant());

    new ProgressThread("loading_data") {

      @Override
      public void run() {
        DataResultSet previewResultSet = null;
        try {
          previewResultSet = state.getDataResultSetFactory().makeDataResultSet(state.getOperator());
          state.getTranslationConfiguration().reconfigure(previewResultSet);
        } catch (OperatorException e1) {
          ImportWizardUtils.showErrorMessage(
              state.getDataResultSetFactory().getResourceName(), e1.toString(), e1);
          return;
        } finally {
          if (previewResultSet != null) {
            try {
              previewResultSet.close();
            } catch (OperatorException e) {
              LogService.getRoot()
                  .log(
                      Level.WARNING,
                      "Could not close preview result for "
                          + state.getDataResultSetFactory().getResourceName()
                          + "!");
            }
          }
        }

        try {
          TableModel dataPreview =
              state.getDataResultSetFactory().makePreviewTableModel(getProgressListener());
          // Copy name annotations to name
          int nameIndex = state.getTranslationConfiguration().getNameRow();
          if (nameIndex != -1 && dataPreview != null) {
            for (int i = 0; i < dataPreview.getColumnCount(); i++) {
              ColumnMetaData columnMetaData =
                  state.getTranslationConfiguration().getColumnMetaData(i);
              final String foundName = (String) dataPreview.getValueAt(nameIndex, i);
              if ((foundName != null) && !foundName.isEmpty()) {
                columnMetaData.setUserDefinedAttributeName(foundName);
              }
            }
          }
        } catch (Exception e) {
          ImportWizardUtils.showErrorMessage(
              state.getDataResultSetFactory().getResourceName(), e.toString(), e);
          return;
        }
        guessValueTypes();
      }
    }.start();
    return true;
  }

  public void updateErrors() {
    final List<ParsingError> errorList = new ArrayList<ParsingError>();

    canProceed = true;
    if (headerValidator.getErrors().size() > 0) {
      List<ParsingError> headerErrors = headerValidator.getErrors();
      errorList.addAll(headerErrors);
      canProceed = false;
    }
    errorList.addAll(state.getTranslator().getErrors());
    SwingUtilities.invokeLater(
        new Runnable() {

          @Override
          public void run() {

            final int size = errorList.size();
            errorLabel.setText(size + " errors.");
            if (size == 0) {
              errorLabel.setIcon(SwingTools.createIcon("16/ok.png"));
            } else {
              errorLabel.setIcon(SwingTools.createIcon("16/error.png"));
            }
          }
        });
    errorTableModel.setErrors(errorList);
    fireStateChanged();
  }

  private void updateTableModel(ExampleSet exampleSet) {
    if (previewTable == null) {
      previewTable = new ExtendedJTable(false, false, false);
    }

    // data model
    DataTableViewerTableModel model =
        new DataTableViewerTableModel(new DataTableExampleSetAdapter(exampleSet, null));
    List<Integer> rowsList = new LinkedList<Integer>();
    int lastHit = -1;
    for (ParsingError error : state.getTranslator().getErrors()) {
      if (error.getExampleIndex() != lastHit) {
        rowsList.add(error.getExampleIndex());
        lastHit = error.getExampleIndex();
      }
    }
    int[] rowMap = new int[rowsList.size()];
    int j = 0;
    for (Integer row : rowsList) {
      rowMap[j++] = row;
    }
    filteredModel = new RowFilteringTableModel(model, rowMap, filterErrorsBox.isSelected());
    previewTable.setModel(filteredModel);

    // Header model

    TableColumnModel columnModel = previewTable.getColumnModel();
    previewTable.setTableHeader(new EditableTableHeader(columnModel));
    // header editors and renderers and values
    MetaDataTableHeaderCellEditor headerRenderer = new MetaDataTableHeaderCellEditor();
    MetaDataTableHeaderCellEditor headerEditor = new MetaDataTableHeaderCellEditor(headerValidator);
    headerValidator.addHeaderRenderer(headerRenderer);
    for (int i = 0; i < previewTable.getColumnCount(); i++) {
      EditableTableHeaderColumn col =
          (EditableTableHeaderColumn) previewTable.getColumnModel().getColumn(i);
      col.setHeaderValue(state.getTranslationConfiguration().getColumnMetaData()[i]);
      col.setHeaderRenderer(headerRenderer);
      col.setHeaderEditor(headerEditor);
    }
    previewTable.getTableHeader().setReorderingAllowed(false);

    previewTable.setCellColorProvider(
        new CellColorProviderAlternating() {

          @Override
          public Color getCellColor(int row, int column) {
            row = filteredModel.translateRow(row);
            ParsingError error = state.getTranslator().getErrorByExampleIndexAndColumn(row, column);
            if (error != null) {
              return SwingTools.DARK_YELLOW;
            } else {
              return super.getCellColor(row, column);
            }
          }
        });
    tableScrollPane.setViewportView(previewTable);
  }

  @Override
  protected boolean performLeavingAction(WizardStepDirection direction) {
    if (direction == WizardStepDirection.FINISH) {
      try {
        if (state.getTranslator() != null) {
          state.getTranslator().close();
        }
      } catch (OperatorException e) {
        ImportWizardUtils.showErrorMessage(
            state.getDataResultSetFactory().getResourceName(), e.toString(), e);
      }

      // use settings edited by user even if he never pressed Enter or otherwise confirmed his
      // changes
      for (int i = 0; i < previewTable.getColumnCount(); i++) {
        EditableTableHeaderColumn col =
            (EditableTableHeaderColumn) previewTable.getColumnModel().getColumn(i);
        if (col.getHeaderEditor() instanceof MetaDataTableHeaderCellEditor) {
          MetaDataTableHeaderCellEditor editor =
              (MetaDataTableHeaderCellEditor) col.getHeaderEditor();
          editor.updateColumnMetaData();
        }
      }
    }
    return true;
  }

  @Override
  protected boolean canGoBack() {
    return true;
  }

  @Override
  protected boolean canProceed() {
    return canProceed;
  }

  @Override
  protected JComponent getComponent() {
    return panel;
  }

  private void reload() {
    reloadButton.configurePropertiesFromAction(cancelReloadAction);
    new ProgressThread("loading_data") {

      @Override
      public void run() {
        DataResultSet resultSet = null;
        try {
          if (state.getTranslator() != null) {
            state.getTranslator().close();
          }
          resultSet = state.getDataResultSetFactory().makeDataResultSet(null);
          state.getTranslator().clearErrors();
          final ExampleSet exampleSet =
              state.readNow(resultSet, limitedPreviewBox.isSelected(), getProgressListener());

          SwingUtilities.invokeLater(
              new Runnable() {

                @Override
                public void run() {
                  updateTableModel(exampleSet);
                  updateErrors();
                }
              });
        } catch (OperatorException e) {
          ImportWizardUtils.showErrorMessage(
              state.getDataResultSetFactory().getResourceName(), e.toString(), e);
        } finally {
          if (resultSet != null) {
            try {
              resultSet.close();
            } catch (OperatorException e) {
              // LogService.getRoot().log(Level.WARNING, "Failed to close result set: " + e, e);
              LogService.getRoot()
                  .log(
                      Level.WARNING,
                      I18N.getMessage(
                          LogService.getRoot().getResourceBundle(),
                          "com.rapidminer.operator.nio.MetaDataDeclarationWizardStep.closing_result_set_error",
                          e),
                      e);
            }
          }
          getProgressListener().complete();
          SwingUtilities.invokeLater(
              new Runnable() {

                @Override
                public void run() {
                  reloadButton.configurePropertiesFromAction(reloadAction);
                  reloadButton.setEnabled(true);
                  isReloading = false;
                }
              });
        }
      }
    }.start();
  }

  private void cancelReload() {
    state.getTranslator().cancelLoading();
    reloadButton.setEnabled(false);
  }

  private void guessValueTypes() {
    guessButton.configurePropertiesFromAction(cancelGuessValueTypes);
    isGuessing = true;
    new ProgressThread("guessing_value_types") {

      @Override
      public void run() {
        Thread.yield();
        DataResultSet resultSet = null;
        try {
          if (state.getTranslator() != null) {
            state.getTranslator().close();
          }
          resultSet = state.getDataResultSetFactory().makeDataResultSet(null);
          state.getTranslator().clearErrors();
          state.getTranslationConfiguration().resetValueTypes();
          state
              .getTranslator()
              .guessValueTypes(
                  state.getTranslationConfiguration(),
                  resultSet,
                  state.getNumberOfPreviewRows(),
                  getProgressListener());
          if (!state.getTranslator().isGuessingCancelled()) {
            final ExampleSet exampleSet =
                state.readNow(resultSet, limitedPreviewBox.isSelected(), getProgressListener());
            SwingUtilities.invokeLater(
                new Runnable() {

                  @Override
                  public void run() {
                    updateTableModel(exampleSet);
                    updateErrors();
                  }
                });
          }
        } catch (OperatorException e) {
          ImportWizardUtils.showErrorMessage(
              state.getDataResultSetFactory().getResourceName(), e.toString(), e);
        } finally {
          if (resultSet != null) {
            try {
              resultSet.close();
            } catch (OperatorException e) {
              // LogService.getRoot().log(Level.WARNING, "Failed to close result set: " + e, e);
              LogService.getRoot()
                  .log(
                      Level.WARNING,
                      I18N.getMessage(
                          LogService.getRoot().getResourceBundle(),
                          "com.rapidminer.operator.nio.MetaDataDeclarationWizardStep.closing_result_set_error",
                          e),
                      e);
            }
          }
          getProgressListener().complete();
          SwingUtilities.invokeLater(
              new Runnable() {

                @Override
                public void run() {
                  guessButton.configurePropertiesFromAction(guessValueTypes);
                  guessButton.setEnabled(true);
                  isGuessing = false;
                }
              });
        }
      }
    }.start();
  }

  private boolean isGuessing = false;
  private boolean isReloading = false;
  private ExtendedJTable previewTable;

  private void cancelGuessing() {
    state.getTranslator().cancelGuessing();
    state.getTranslator().cancelLoading();
    guessButton.setEnabled(false);
  }

  private void toggleGuessValueTypes() {
    isGuessing = !isGuessing;
    if (isGuessing) {
      guessValueTypes();
    } else {
      cancelGuessing();
    }
  }

  private void toggleReload() {
    isReloading = !isReloading;
    if (isReloading) {
      reload();
    } else {
      cancelReload();
    }
  }
}