package gui;

import static gui.GUIController.*;

import java.util.*;
import java.util.regex.Pattern;

import javafx.application.HostServices;
import javafx.beans.binding.StringBinding;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.image.ImageView;
import javafx.scene.layout.AnchorPane;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.controlsfx.control.CheckComboBox;

import data.*;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.layout.Pane;
import util.Tasks;

@SuppressWarnings("Duplicates")
public class StringAnalysisTabNew2 {
    public final static Logger logger = LogManager.getLogger(StringAnalysisTabNew2.class);

    @FXML
    public AnchorPane stringAnalysisTabPaneNew2;

    @FXML
    public TextArea selectedFiltersTextArea;

    @FXML
    public Label solarFilters;

    @FXML
    public Label calculateForL;

    @FXML
    public Label alsoVisualizeL;

    @FXML
    public Label displayTaxonomyL;

    @FXML
    public Label dataLimitL;

    @FXML
    public Label msdL;

    @FXML
    public Label taxonomyL;

    @FXML
    public Label minimalOccurrencesL;

    @FXML
    public Label minimalTaxonomyL;

    @FXML
    public Label minimalRelFreL;

    @FXML
    public Label taxonomySetOperationL;

    @FXML
    public Label skipValueL;

    @FXML
    public Label slowSpeedWarning1L;

    @FXML
    public Label slowSpeedWarning2L;

    @FXML
    public Label ngramValueL;

    @FXML
    public Label notePunctuationsL;

    @FXML
    public Label collocabilityL;

    @FXML
    public ImageView calculateForI;

    @FXML
    public ImageView alsoVisualizeI;

    @FXML
    public ImageView displayTaxonomyI;

    @FXML
    public ImageView msdI;

    @FXML
    public ImageView taxonomyI;

    @FXML
    public ImageView minimalOccurrencesI;

    @FXML
    public ImageView minimalTaxonomyI;

    @FXML
    public ImageView minimalRelFreI;

    @FXML
    public ImageView taxonomySetOperationI;

    @FXML
    public ImageView skipValueI;

    @FXML
    public ImageView ngramValueI;

    @FXML
    public ImageView notePunctuationsI;

    @FXML
    public ImageView collocabilityI;

    @FXML
    private TextField msdTF;
    private ArrayList<Pattern> msd;
    private ArrayList<String> msdStrings;

    @FXML
    private CheckComboBox<String> alsoVisualizeCCB;
    private ArrayList<String> alsoVisualize;

    @FXML
    private CheckComboBox<String> taxonomyCCB;
    private ArrayList<Taxonomy> taxonomy;
    @FXML
    private ComboBox<String> calculateForCB;
    private CalculateFor calculateFor;

    @FXML
    private CheckComboBox<String> collocabilityCCB;
    private ArrayList<Collocability> collocability;

    @FXML
    private ComboBox<String> ngramValueCB;
    private Integer ngramValue;

    @FXML
    private ComboBox<String> skipValueCB;
    private Integer skipValue;

    @FXML
    private CheckBox notePunctuationsChB;
    private boolean notePunctuations;

    @FXML
    private CheckBox displayTaxonomyChB;
    private boolean displayTaxonomy;

    @FXML
    private TextField minimalOccurrencesTF;
    private Integer minimalOccurrences;

    @FXML
    private TextField minimalTaxonomyTF;
    private Integer minimalTaxonomy;

    @FXML
    private TextField minimalRelFreTF;
    private Integer minimalRelFre;

    @FXML
    private ComboBox<String> taxonomySetOperationCB;
    private String taxonomySetOperation;

    @FXML
    private Pane paneWords;

    @FXML
    private Button computeNgramsB;

    @FXML
    private Button changeLanguageB;

    @FXML
    private Button cancel;

    @FXML
    public ProgressBar ngramProgressBar;
    @FXML
    public Label progressLabel;
//
//    @FXML
//    private Hyperlink helpH;

    private enum MODE {
        LETTER,
        WORD
    }

    private MODE currentMode;

    private Corpus corpus;
    private HashMap<String, HashSet<String>> solarFiltersMap;
    private HostServices hostService;
    private ListChangeListener<String> taxonomyListener;
    private ListChangeListener<String> alsoVisualizeListener;
    private ListChangeListener<String> collocabilityListener;
    private ChangeListener<String> calculateForListener;
    private ChangeListener<Boolean> msdListener;
    private ChangeListener<Boolean> minimalOccurrencesListener;
    private ChangeListener<Boolean> minimalTaxonomyListener;
    private ChangeListener<Boolean> minimalRelFreListener;
    private boolean useDb;

    private static final ObservableList<String> COLLOCABILITY_ITEMS = FXCollections.observableArrayList("Dice", "t-score", "MI", "MI3", "logDice", "simple LL");
    private static final String [] N_GRAM_COMPUTE_FOR_WORDS_ARRAY = {"calculateFor.WORD", "calculateFor.LOWERCASE_WORD", "calculateFor.LEMMA", "calculateFor.MORPHOSYNTACTIC_SPECS"};
    private static final ArrayList<String> N_GRAM_COMPUTE_FOR_WORDS = new ArrayList<>(Arrays.asList(N_GRAM_COMPUTE_FOR_WORDS_ARRAY));
    private static final String [] N_GRAM_COMPUTE_FOR_WORDS_ORTH_ARRAY = {"calculateFor.WORD", "calculateFor.LOWERCASE_WORD"};
    private static final ArrayList<String> N_GRAM_COMPUTE_FOR_WORDS_ORTH = new ArrayList<>(Arrays.asList(N_GRAM_COMPUTE_FOR_WORDS_ORTH_ARRAY));
    private static final String [] N_GRAM_COMPUTE_FOR_WORDS_GOS_ARRAY = {"calculateFor.WORD", "calculateFor.LOWERCASE_WORD", "calculateFor.LEMMA", "calculateFor.MORPHOSYNTACTIC_SPECS", "calculateFor.NORMALIZED_WORD"};
    private static final ArrayList<String> N_GRAM_COMPUTE_FOR_WORDS_GOS = new ArrayList<>(Arrays.asList(N_GRAM_COMPUTE_FOR_WORDS_GOS_ARRAY));
    private static final String [] ALSO_VISUALIZE_ITEMS_LEMMA_ARRAY = {"calculateFor.WORD_TYPE", "calculateFor.MORPHOSYNTACTIC_SPECS"};
    private static final ArrayList<String> ALSO_VISUALIZE_ITEMS_LEMMA = new ArrayList<>(Arrays.asList(ALSO_VISUALIZE_ITEMS_LEMMA_ARRAY));
    private static final String [] ALSO_VISUALIZE_ITEMS_WORDS_ARRAY = {"calculateFor.LEMMA", "calculateFor.WORD_TYPE", "calculateFor.MORPHOSYNTACTIC_SPECS"};
    private static final ArrayList<String> ALSO_VISUALIZE_ITEMS_WORDS = new ArrayList<>(Arrays.asList(ALSO_VISUALIZE_ITEMS_WORDS_ARRAY));
    private static final String [] ALSO_VISUALIZE_ITEMS_WORDS_GOS_ARRAY = {"calculateFor.LEMMA", "calculateFor.WORD_TYPE", "calculateFor.MORPHOSYNTACTIC_SPECS", "calculateFor.NORMALIZED_WORD"};
    private static final ArrayList<String> ALSO_VISUALIZE_ITEMS_WORDS_GOS = new ArrayList<>(Arrays.asList(ALSO_VISUALIZE_ITEMS_WORDS_GOS_ARRAY));
    private static final String [] ALSO_VISUALIZE_ITEMS_NORMALIZED_WORDS_ARRAY = {"calculateFor.LEMMA", "calculateFor.WORD_TYPE", "calculateFor.MORPHOSYNTACTIC_SPECS"};
    private static final ArrayList<String> ALSO_VISUALIZE_ITEMS_NORMALIZED_WORDS = new ArrayList<>(Arrays.asList(ALSO_VISUALIZE_ITEMS_NORMALIZED_WORDS_ARRAY));
    private static final String [] ALSO_VISUALIZE_ITEMS_MSD_ARRAY = {"calculateFor.WORD_TYPE"};
    private static final ArrayList<String> ALSO_VISUALIZE_ITEMS_MSD = new ArrayList<>(Arrays.asList(ALSO_VISUALIZE_ITEMS_MSD_ARRAY));
    private static final String [] ALSO_VISUALIZE_ITEMS_EMPTY_ARRAY = {};
    private static final ArrayList<String> ALSO_VISUALIZE_ITEMS_EMPTY = new ArrayList<>(Arrays.asList(ALSO_VISUALIZE_ITEMS_EMPTY_ARRAY));

    private static final String [] TAXONOMY_SET_OPERATION_ARRAY = {"taxonomySetOperation.UNION", "taxonomySetOperation.INTERSECTION"};
    private static final ArrayList<String> TAXONOMY_SET_OPERATION = new ArrayList<>(Arrays.asList(TAXONOMY_SET_OPERATION_ARRAY));

    // TODO: pass observables for taxonomy based on header scan
    // after header scan
    public void init() {
        // add CSS style
        stringAnalysisTabPaneNew2.getStylesheets().add("style.css");
        stringAnalysisTabPaneNew2.getStyleClass().add("root");

        manageTranslations();

        currentMode = MODE.WORD;
        toggleMode(currentMode);

        // ngram value CB
        ngramValueCB.valueProperty().addListener((observable, oldValue, newValue) -> {
            if (newValue.equals("nivo črk")) {
                ngramValue = 0;
                toggleMode(MODE.LETTER);
            } else {
                ngramValue = Integer.valueOf(newValue);
                toggleMode(MODE.WORD);
            }

            // skip only on ngrams of more than one word
            if (ngramValue > 1) {
                skipValueCB.setDisable(false);
            } else {
                skipValueCB.getSelectionModel().select(0);
                skipValue = 0;
                skipValueCB.setDisable(true);
            }

            logger.info("ngramValueCB:", ngramValue);
        });

        // set first n-gram value to 2 at index 0
        ngramValueCB.getSelectionModel().select(0); // selected index
        ngramValue = 2; // actual value at that index

        // set default values
        minimalTaxonomyTF.setText("1");
        minimalTaxonomy = 1;

        minimalTaxonomyTF.setDisable(true);

        notePunctuations = false;
        // set
        notePunctuationsChB.selectedProperty().addListener((observable, oldValue, newValue) -> {
            notePunctuations = newValue;
            if(skipValue > 0 || notePunctuations){
                collocabilityCCB.setDisable(true);
            } else {
                collocabilityCCB.setDisable(false);
            }
            logger.info("note punctuations: ", notePunctuations);
        });
        notePunctuationsChB.setSelected(false);
        notePunctuationsChB.setTooltip(new Tooltip(I18N.get("message.TOOLTIP_readNotePunctuationsChB")));
        notePunctuationsChB.setDisable(false);

        displayTaxonomy = false;
        displayTaxonomyChB.setSelected(false);
        // set

        if (Tax.getCorpusTypesWithTaxonomy().contains(corpus.getCorpusType()) && corpus.getObservableListTaxonomy().size() > 0) {
            displayTaxonomyChB.setDisable(false);
            displayTaxonomyChB.selectedProperty().addListener((observable, oldValue, newValue) -> {
                displayTaxonomy = newValue;
                if (displayTaxonomy) {
                    minimalTaxonomyTF.setDisable(false);
                } else {
                    minimalTaxonomyTF.setDisable(true);

                    minimalTaxonomyTF.setText("1");
                    minimalTaxonomy = 1;
                }
                logger.info("display taxonomy: ", displayTaxonomy);
            });
            displayTaxonomyChB.setTooltip(new Tooltip(I18N.get("message.TOOLTIP_readDisplayTaxonomyChB")));
        } else {
            displayTaxonomyChB.setDisable(true);
        }

        if (calculateForListener != null){
            calculateForCB.valueProperty().removeListener(calculateForListener);
        }

        // calculateForCB
        calculateForListener = new ChangeListener<String>() {
            boolean ignoreCode = false;
            @Override
            public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
                if (ignoreCode) {
                    return;
                }
                boolean languageChanged = newValue == null;
                ArrayList<Integer> checkedIndices = new ArrayList<>();
                checkedIndices.addAll(alsoVisualizeCCB.getCheckModel().getCheckedIndices());
                if (languageChanged) {
                    ignoreCode = true;
                    if (corpus.getCorpusType() == CorpusType.GOS) {
                        newValue = I18N.getTranslatedValue(oldValue, N_GRAM_COMPUTE_FOR_WORDS_GOS);
                        calculateForCB.getSelectionModel().select(newValue);
                    } else {
                        newValue = I18N.getTranslatedValue(oldValue, N_GRAM_COMPUTE_FOR_WORDS);
                        calculateForCB.getSelectionModel().select(newValue);
                    }

                    // override if orth mode, allow only word
                    if (corpus.isGosOrthMode()) {
                        newValue = I18N.getTranslatedValue(oldValue, N_GRAM_COMPUTE_FOR_WORDS_ORTH);
                        calculateForCB.getSelectionModel().select(newValue);
                    }
                    ignoreCode = false;
                }

                calculateFor = CalculateFor.factory(newValue);

                if (alsoVisualizeListener != null) {
                    alsoVisualizeCCB.getCheckModel().getCheckedItems().removeListener(alsoVisualizeListener);
                }

                alsoVisualizeCCB.getCheckModel().clearChecks();
                alsoVisualizeCCB.getItems().removeAll();
                if (newValue.equals(CalculateFor.LEMMA.toString())) {
                    alsoVisualizeCCB.getItems().setAll(I18N.translatedObservableList(ALSO_VISUALIZE_ITEMS_LEMMA));
                } else if (newValue.equals(CalculateFor.WORD.toString()) || newValue.equals(CalculateFor.LOWERCASE_WORD.toString())) {
                    if (corpus.getCorpusType() == CorpusType.GOS)
                        alsoVisualizeCCB.getItems().setAll(I18N.translatedObservableList(ALSO_VISUALIZE_ITEMS_WORDS_GOS));
                    else
                        alsoVisualizeCCB.getItems().setAll(I18N.translatedObservableList(ALSO_VISUALIZE_ITEMS_WORDS));
                } else if (newValue.equals(CalculateFor.NORMALIZED_WORD.toString())) {
                    alsoVisualizeCCB.getItems().setAll(I18N.translatedObservableList(ALSO_VISUALIZE_ITEMS_NORMALIZED_WORDS));
                } else if (newValue.equals(CalculateFor.MORPHOSYNTACTIC_SPECS.toString())) {
                    alsoVisualizeCCB.getItems().setAll(I18N.translatedObservableList(ALSO_VISUALIZE_ITEMS_MSD));
                } else {
                    alsoVisualizeCCB.getItems().setAll(I18N.translatedObservableList(ALSO_VISUALIZE_ITEMS_EMPTY));
                }

                alsoVisualizeListener = new ListChangeListener<String>() {
                    @Override
                    public void onChanged(Change<? extends String> c) {
                        alsoVisualize = new ArrayList<>();
                        ObservableList<String> checkedItems = alsoVisualizeCCB.getCheckModel().getCheckedItems();
                        alsoVisualize.addAll(checkedItems);
                        logger.info(String.format("Selected also visualize items: %s", StringUtils.join(checkedItems, ",")));
                    }
                };

                alsoVisualizeCCB.getCheckModel().getCheckedItems().addListener(alsoVisualizeListener);

                if (languageChanged) {
                    for (int i : checkedIndices) {
                        alsoVisualizeCCB.getCheckModel().check(i);
                    }
                }

                logger.info("calculateForCB:", calculateFor.toString());
            }
        };

        calculateForCB.valueProperty().addListener(calculateForListener);

        calculateForCB.getSelectionModel().select(0);

        taxonomySetOperationCB.setDisable(true);
        taxonomySetOperationCB.valueProperty().addListener((observable, oldValue, newValue) -> {
            if(newValue == null){
                newValue = I18N.getTranslatedValue(oldValue, TAXONOMY_SET_OPERATION);
                taxonomySetOperationCB.getSelectionModel().select(newValue);
            }
            taxonomySetOperation = newValue;
            logger.info("Taxonomy set operation:", taxonomySetOperation);
        });

        taxonomySetOperationCB.getSelectionModel().select(0);

        // collocabilityCCB
        if (collocabilityListener != null){
            collocabilityCCB.getCheckModel().getCheckedItems().removeListener(collocabilityListener);
        }

        collocabilityListener = new ListChangeListener<String>() {
            @Override
            public void onChanged(Change<? extends String> c) {
                collocability = new ArrayList<>();
                ObservableList<Collocability> checkedItems = FXCollections.observableArrayList();
                for (String el : collocabilityCCB.getCheckModel().getCheckedItems()) {
                    checkedItems.add(Collocability.factory(el));
                }
                collocability.addAll(checkedItems);
                if(collocability.size() > 0) {
                    notePunctuationsChB.setDisable(true);
                    skipValueCB.setDisable(true);
                } else {
                    notePunctuationsChB.setDisable(false);
                    skipValueCB.setDisable(false);
                }
                logger.info(String.format("Selected collocabilities: %s", StringUtils.join(collocabilityCCB.getCheckModel().getCheckedItems(), ",")));
            }
        };

        collocabilityCCB.getCheckModel().clearChecks();
        collocabilityCCB.getItems().removeAll();
        collocabilityCCB.getItems().setAll(FXCollections.observableArrayList(COLLOCABILITY_ITEMS));
        collocabilityCCB.setDisable(false);

        collocabilityCCB.getCheckModel().getCheckedItems().addListener(collocabilityListener);

        // msd
        if (msdListener != null){
            msdTF.focusedProperty().removeListener(msdListener);
        }

        // msd
        msdListener = new ChangeListener<Boolean>() {
            @Override
            public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
                if (!newValue) {
                    // focus lost
                    String value = msdTF.getText();
                    logger.info("msdTf: ", value);

                    if (!ValidationUtil.isEmpty(value)) {
                        ArrayList<String> msdTmp = new ArrayList<>(Arrays.asList(value.split(" ")));

                        int nOfRequiredMsdTokens = ngramValue == 0 ? 1 : ngramValue;
                        if (msdTmp.size() != nOfRequiredMsdTokens) {
                            String msg = String.format(I18N.get("message.WARNING_MISMATCHED_NGRAM_AND_TOKENS_VALUES"), nOfRequiredMsdTokens, msdTmp.size());
                            logAlert(msg);
                            showAlert(Alert.AlertType.ERROR, msg);
                        }
                        msd = new ArrayList<>();
                        msdStrings = new ArrayList<>();
                        for (String msdToken : msdTmp) {
                            msd.add(Pattern.compile(msdToken));
                            msdStrings.add(msdToken);
                        }
                        logger.info(String.format("msd accepted (%d)", msd.size()));

                    } else if (!ValidationUtil.isEmpty(newValue)) {
                        msd = new ArrayList<>();
                        msdStrings = new ArrayList<>();
                    }
                }
            }
        };
        msdTF.focusedProperty().addListener(msdListener);

        msdTF.setText("");
        msd = new ArrayList<>();

        if (alsoVisualizeListener != null){
            alsoVisualizeCCB.getCheckModel().getCheckedItems().removeListener(alsoVisualizeListener);
        }

        alsoVisualizeListener = new ListChangeListener<String>() {
            @Override
            public void onChanged(Change<? extends String> c) {
                alsoVisualize = new ArrayList<>();
                ObservableList<String> checkedItems = alsoVisualizeCCB.getCheckModel().getCheckedItems();
                alsoVisualize.addAll(checkedItems);
                logger.info(String.format("Selected also visualize items: %s", StringUtils.join(checkedItems, ",")));
            }
        };
        alsoVisualizeCCB.getCheckModel().clearChecks();
        alsoVisualizeCCB.getItems().removeAll();
        if (corpus.getCorpusType() != CorpusType.GOS){
            alsoVisualizeCCB.getItems().setAll(I18N.translatedObservableList(ALSO_VISUALIZE_ITEMS_WORDS));
        } else {
            alsoVisualizeCCB.getItems().setAll(I18N.translatedObservableList(ALSO_VISUALIZE_ITEMS_WORDS_GOS));
        }

        alsoVisualizeCCB.getCheckModel().getCheckedItems().addListener(alsoVisualizeListener);

        // taxonomy
        if (Tax.getCorpusTypesWithTaxonomy().contains(corpus.getCorpusType()) && corpus.getObservableListTaxonomy().size() > 0) {
            taxonomyCCB.setDisable(false);
        } else {
            taxonomyCCB.setDisable(true);
        }

        if (taxonomyListener != null){
            taxonomyCCB.getCheckModel().getCheckedItems().removeListener(taxonomyListener);
        }

        taxonomyListener = new ListChangeListener<String>() {
            boolean changing = true;

            @Override
            public void onChanged(ListChangeListener.Change<? extends String> c){
                if(changing) {
                    ObservableList<String> checkedItems = taxonomyCCB.getCheckModel().getCheckedItems();
                    ArrayList<Taxonomy> checkedItemsTaxonomy = Taxonomy.modifyingTaxonomy(taxonomy, checkedItems, corpus);

                    taxonomy = new ArrayList<>();
                    taxonomy.addAll(checkedItemsTaxonomy);

                    taxonomyCCB.getItems().removeAll();
                    taxonomyCCB.getItems().setAll(corpus.getObservableListTaxonomy());

                    changing = false;
                    taxonomyCCB.getCheckModel().clearChecks();
                    for (Taxonomy t : checkedItemsTaxonomy) {
                        taxonomyCCB.getCheckModel().check(t.toLongNameString());
                    }
                    if (taxonomyCCB.getCheckModel().getCheckedItems().size() > 0) {
                        taxonomySetOperationCB.setDisable(false);
                    } else {
                        taxonomySetOperationCB.getSelectionModel().select(0);
                        taxonomySetOperationCB.setDisable(true);
                    }
                    changing = true;
                    logger.info(String.format("Selected taxonomy: %s", StringUtils.join(checkedItems, ",")));
                }
            }
        };
        taxonomyCCB.getCheckModel().clearChecks();

        taxonomyCCB.getItems().removeAll();
        taxonomyCCB.getItems().setAll(corpus.getObservableListTaxonomy());

        taxonomyCCB.getCheckModel().getCheckedItems().addListener(taxonomyListener);

        // skip
        skipValueCB.valueProperty().addListener((observable, oldValue, newValue) -> {
            skipValue = Integer.valueOf(newValue);
            if(skipValue > 0 || notePunctuations) {
                collocabilityCCB.setDisable(true);
            } else {
                collocabilityCCB.setDisable(false);
            }
            logger.info("Skip " + skipValue);
        });

        skipValueCB.setDisable(false);
        skipValueCB.getSelectionModel().select(0);
        skipValue = 0;

        // cvv
        minimalOccurrencesTF.setText("1");
        minimalOccurrences = 1;

        if (minimalOccurrencesListener != null){
            minimalOccurrencesTF.focusedProperty().removeListener(minimalOccurrencesListener);
        }

        // msd
        minimalOccurrencesListener = new ChangeListener<Boolean>() {
            @Override
            public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
                if (!newValue) {
                    // focus lost
                    String value = minimalOccurrencesTF.getText();
                    if (!ValidationUtil.isEmpty(value)) {
                        if (!ValidationUtil.isNumber(value)) {
                            logAlert("minimalOccurrencesTF: " + I18N.get("message.WARNING_ONLY_NUMBERS_ALLOWED"));
                            GUIController.showAlert(Alert.AlertType.ERROR, I18N.get("message.WARNING_ONLY_NUMBERS_ALLOWED"));
                        } else {
                            minimalOccurrences = Integer.parseInt(value);
                        }
                    } else {
                        minimalOccurrencesTF.setText("1");
                        minimalOccurrences = 1;
                    }
                }
            }
        };
        minimalOccurrencesTF.focusedProperty().addListener(minimalOccurrencesListener);


        if (minimalTaxonomyListener != null){
            minimalTaxonomyTF.focusedProperty().removeListener(minimalTaxonomyListener);
        }

        minimalTaxonomyListener = new ChangeListener<Boolean>() {
            @Override
            public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
                if (!newValue) {
                    // focus lost
                    String value = minimalTaxonomyTF.getText();
                    if (!ValidationUtil.isEmpty(value)) {
                        if (!ValidationUtil.isNumber(value)) {
                            logAlert("minimalTaxonomyTF: " + I18N.get("message.WARNING_ONLY_NUMBERS_ALLOWED"));
                            GUIController.showAlert(Alert.AlertType.ERROR, I18N.get("message.WARNING_ONLY_NUMBERS_ALLOWED"));
                        } else {
                            minimalTaxonomy = Integer.parseInt(value);
                        }
                    } else {
                        minimalTaxonomyTF.setText("1");
                        minimalTaxonomy = 1;
                    }
                }
            }
        };
        minimalTaxonomyTF.focusedProperty().addListener(minimalTaxonomyListener);

        // set default values
        minimalRelFreTF.setText("1");
        minimalRelFre = 1;

        if (minimalRelFreListener != null){
            minimalRelFreTF.focusedProperty().removeListener(minimalRelFreListener);
        }

        minimalRelFreListener = new ChangeListener<Boolean>() {
            @Override
            public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
                if (!newValue) {
                    // focus lost
                    String value = minimalRelFreTF.getText();
                    if (!ValidationUtil.isEmpty(value)) {
                        if (!ValidationUtil.isNumber(value)) {
                            logAlert("minimalRelFreTF: " + I18N.get("message.WARNING_ONLY_NUMBERS_ALLOWED"));
                            GUIController.showAlert(Alert.AlertType.ERROR, I18N.get("message.WARNING_ONLY_NUMBERS_ALLOWED"));
                        } else {
                            minimalRelFre = Integer.parseInt(value);
                        }
                    } else {
                        minimalRelFreTF.setText("1");
                        minimalRelFre = 1;
                    }
                }
            }
        };
        minimalRelFreTF.focusedProperty().addListener(minimalRelFreListener);


        changeLanguageB.setOnAction(e -> {
            if (I18N.getLocale() == new Locale.Builder().setLanguage("sl").setRegion("SI").build()){
                I18N.setLocale(Locale.ENGLISH);
            } else {
                I18N.setLocale(new Locale.Builder().setLanguage("sl").setRegion("SI").build());
            }
            Messages.reload();
            Messages.updateChooseCorpusL();
            logger.info("change language");
        });

        computeNgramsB.setOnAction(e -> {
            compute();
            logger.info("compute button");
        });

//        helpH.setOnAction(e -> openHelpWebsite());

        cancel.setVisible(false);
    }

    private void addTooltipToImage(ImageView image, StringBinding stringBinding){
        Tooltip tooltip = new Tooltip();
        tooltip.textProperty().bind(stringBinding);
        Tooltip.install(image, tooltip);
    }

    private void manageTranslations(){
//        helpH.textProperty().bind(I18N.createStringBinding("hyperlink.help"));
        changeLanguageB.textProperty().bind(I18N.createStringBinding("button.language"));
        computeNgramsB.textProperty().bind(I18N.createStringBinding("button.computeNgrams"));
        cancel.textProperty().bind(I18N.createStringBinding("button.cancel"));

        calculateForL.textProperty().bind(I18N.createStringBinding("label.calculateFor"));
        alsoVisualizeL.textProperty().bind(I18N.createStringBinding("label.alsoVisualize"));
        displayTaxonomyL.textProperty().bind(I18N.createStringBinding("label.displayTaxonomy"));
        skipValueL.textProperty().bind(I18N.createStringBinding("label.skipValue"));
        slowSpeedWarning1L.textProperty().bind(I18N.createStringBinding("label.slowSpeedWarning"));
        slowSpeedWarning2L.textProperty().bind(I18N.createStringBinding("label.slowSpeedWarning"));
        ngramValueL.textProperty().bind(I18N.createStringBinding("label.ngramValue"));
        notePunctuationsL.textProperty().bind(I18N.createStringBinding("label.notePunctuations"));
        collocabilityL.textProperty().bind(I18N.createStringBinding("label.collocability"));

        dataLimitL.textProperty().bind(I18N.createStringBinding("label.dataLimit"));
        msdL.textProperty().bind(I18N.createStringBinding("label.msd"));
        taxonomyL.textProperty().bind(I18N.createStringBinding("label.taxonomy"));
        minimalOccurrencesL.textProperty().bind(I18N.createStringBinding("label.minimalOccurrences"));
        minimalTaxonomyL.textProperty().bind(I18N.createStringBinding("label.minimalTaxonomy"));
        minimalRelFreL.textProperty().bind(I18N.createStringBinding("label.minimalRelFre"));
        taxonomySetOperationL.textProperty().bind(I18N.createStringBinding("label.taxonomySetOperation"));
        solarFilters.textProperty().bind(I18N.createStringBinding("label.solarFilters"));

        addTooltipToImage(calculateForI, I18N.createStringBinding("label.wordSet.calculateForH"));
        addTooltipToImage(alsoVisualizeI, I18N.createStringBinding("label.wordSet.alsoVisualizeH"));
        addTooltipToImage(displayTaxonomyI, I18N.createStringBinding("label.wordSet.displayTaxonomyH"));
        addTooltipToImage(skipValueI, I18N.createStringBinding("label.wordSet.skipValueH"));
        addTooltipToImage(ngramValueI, I18N.createStringBinding("label.wordSet.ngramValueH"));
        addTooltipToImage(notePunctuationsI, I18N.createStringBinding("label.wordSet.notePunctuationsH"));
        addTooltipToImage(collocabilityI, I18N.createStringBinding("label.wordSet.collocabilityH"));

        addTooltipToImage(msdI, I18N.createStringBinding("label.wordSet.msdH"));
        addTooltipToImage(taxonomyI, I18N.createStringBinding("label.wordSet.taxonomyH"));
        addTooltipToImage(minimalOccurrencesI, I18N.createStringBinding("label.wordSet.minimalOccurrencesH"));
        addTooltipToImage(minimalTaxonomyI, I18N.createStringBinding("label.wordSet.minimalTaxonomyH"));
        addTooltipToImage(minimalRelFreI, I18N.createStringBinding("label.wordPart.minimalRelFreH"));
        addTooltipToImage(taxonomySetOperationI, I18N.createStringBinding("label.letter.taxonomySetOperationH"));

        taxonomySetOperationCB.itemsProperty().bind(I18N.createObjectBinding(TAXONOMY_SET_OPERATION));
    }

    /**
     * Toggles visibility for panes which hold fields for skipgram value (not applicable when calculating for letters) etc.,
     * sets combobox values to what is applicable ...
     *
     * @param mode
     */
    public void toggleMode(MODE mode) {
        if (mode == null) {
            mode = currentMode;
        }

        logger.info("mode: ", mode.toString());

        if (mode == MODE.WORD) {
            paneWords.setVisible(true);
            if (corpus.getCorpusType() == CorpusType.GOS) {
                calculateForCB.itemsProperty().unbind();
                calculateForCB.itemsProperty().bind(I18N.createObjectBinding(N_GRAM_COMPUTE_FOR_WORDS_GOS));
            } else {
                calculateForCB.itemsProperty().unbind();
                calculateForCB.itemsProperty().bind(I18N.createObjectBinding(N_GRAM_COMPUTE_FOR_WORDS));
            }

        }

        // override if orth mode, allow only word
        if (corpus.isGosOrthMode()) {
            calculateForCB.itemsProperty().unbind();
            calculateForCB.itemsProperty().bind(I18N.createObjectBinding(N_GRAM_COMPUTE_FOR_WORDS_ORTH));
            msdTF.setDisable(true);
        } else {
            msdTF.setDisable(false);
        }
    }

    private void compute() {
        Filter filter = new Filter();
        filter.setNgramValue(ngramValue);
        filter.setCalculateFor(calculateFor);
        filter.setTaxonomy(taxonomy);
        filter.setDisplayTaxonomy(displayTaxonomy);
        filter.setAl(AnalysisLevel.STRING_LEVEL);
        filter.setSkipValue(skipValue);
        filter.setSolarFilters(solarFiltersMap);
        filter.setNotePunctuations(notePunctuations);
        filter.setMultipleKeys(alsoVisualize);

        // setMsd must be behind alsoVisualize
        filter.setMsd(msd);
        filter.setMinimalOccurrences(minimalOccurrences);
        filter.setMinimalTaxonomy(minimalTaxonomy);
        filter.setMinimalRelFre(minimalRelFre);
        filter.setCollocability(collocability);
        filter.setTaxonomySetOperation(taxonomySetOperation);

        String message = Validation.validateForStringLevel(filter);
        if (message == null) {
            // no errors
            logger.info("Executing: ", filter.toString());
            StatisticsNew statistic = new StatisticsNew(corpus, filter, useDb);
            execute(statistic);
        } else {
            logAlert(message);
            showAlert(Alert.AlertType.ERROR, "Prosim izpolnite polja:", message);
        }
    }

    private void logAlert(String alert) {
        logger.info("alert: " + alert);
    }

    private void openHelpWebsite(){
        hostService.showDocument(Messages.HELP_URL);
    }

    public Corpus getCorpus() {
        return corpus;
    }

    public void setCorpus(Corpus corpus) {
        this.corpus = corpus;

        if (corpus.getCorpusType() != CorpusType.SOLAR) {
            setSelectedFiltersLabel(null);
        } else {
            setSelectedFiltersLabel("/");
        }
    }

    public void setSelectedFiltersLabel(String content) {
        if (content != null) {
            solarFilters.setVisible(true);
            selectedFiltersTextArea.setVisible(true);
            selectedFiltersTextArea.setText(content);
        } else {
            solarFilters.setVisible(false);
            selectedFiltersTextArea.setVisible(false);
        }
    }

    private void execute(StatisticsNew statistic) {
        Filter f = statistic.getFilter();
        logger.info("Started execution: ", f);

        Tasks t = new Tasks(corpus, useDb, cancel, ngramProgressBar, progressLabel);
        if (f.getMinimalRelFre() > 1){
            final Task<Void> mainTask = t.prepareTaskForMinRelFre(statistic);
            final Thread thread = new Thread(mainTask, "task");
            thread.setDaemon(true);
            thread.start();
        } else {
            final Task<Void> mainTask = t.prepareMainTask(statistic);
            final Thread thread = new Thread(mainTask, "task");
            thread.setDaemon(true);
            thread.start();
        }
    }

    public void setSolarFiltersMap(HashMap<String, HashSet<String>> solarFiltersMap) {
        this.solarFiltersMap = solarFiltersMap;
    }
    public void setHostServices(HostServices hostServices){
        this.hostService = hostServices;
    }
}
