package gui;

import static data.CorpusType.*;
import static gui.GUIController.*;
import static util.Util.*;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.*;

import javafx.beans.binding.StringBinding;
import javafx.collections.FXCollections;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.image.ImageView;
import javafx.scene.layout.AnchorPane;
import javafx.util.Duration;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOCase;
import org.apache.commons.io.LineIterator;
import org.apache.commons.io.filefilter.FileFilterUtils;
import org.apache.commons.io.filefilter.TrueFileFilter;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import alg.XML_processing;
import data.Corpus;
import data.CorpusType;
import data.Enums.solar.SolarFilters;
import data.Tax;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.layout.Pane;
import javafx.stage.DirectoryChooser;
import javafx.stage.Stage;
import javafx.application.HostServices;

public class CorpusTab {
	public final static Logger logger = LogManager.getLogger(CorpusTab.class);
	public Pane setCorpusWrapperP;

	private Stage stage;

	@FXML
	private AnchorPane corpusTabPane;

	@FXML
	private Button chooseCorpusLocationB;

	@FXML
	private CheckBox readHeaderInfoChB;
	private boolean readHeaderInfo;

	@FXML
	private Button chooseResultsLocationB;

	@FXML
	private Button changeLanguageB;

	@FXML
	private Label chooseCorpusL;
	private String chooseCorpusLabelContent;

	@FXML
	private Label chooseResultsL;
	private String chooseResultsLabelContent;

	@FXML
	public Label chooseCorpusLocationL;

	@FXML
	public Label readHeaderInfoL;

	@FXML
	public Label chooseResultsLocationL;

	@FXML
	public Label selectReaderL;

	@FXML
	public Label outputNameL;

	@FXML
	public Label punctuationL;

	@FXML
	public ImageView chooseCorpusLocationI;

	@FXML
	public ImageView readHeaderInfoI;

	@FXML
	public ImageView chooseResultsLocationI;

	@FXML
	public ImageView selectReaderI;

	@FXML
	public ImageView outputNameI;

	@FXML
	public ImageView punctuationI;

	@FXML
	public TextField outputNameTF;
	public String outputName = "";

	@FXML
	public ComboBox<String> selectReaderCB;
	public String selectReader;

	@FXML
	public ComboBox<String> punctuationCB;
	public String punctuation;

	@FXML
	private ProgressIndicator locationScanPI;

	@FXML
//	private Hyperlink helpH;

	// *** shared ***
	private Corpus corpus;
	private CorpusType corpusType;

	// tabs - used to enable/disable
	private Tab aboutTab;
	private Tab stringLevelTabNew2;
	private Tab oneWordAnalysisTab;
	private Tab characterLevelTab;
	private Tab wordFormationTab;
	private Tab wordLevelTab;
	private Tab filterTab;
	private TabPane tabPane;
	private StringAnalysisTabNew2 satNew2Controller;
	private OneWordAnalysisTab oneWordTabController;
	private AboutTab aboutTabController;
	private CharacterAnalysisTab catController;
	private FiltersForSolar ffsController;
//	private WordFormationTab wfController;
	private WordLevelTab wlController;
	private HostServices hostService;

	private String corpusLocation;
	private String corpusFilesSize;

	private static final String [] SELECT_READER_ARRAY = {"VERT + REGI", "XML (Šolar 1.0)", "XML (GOS 1.0)", "XML (KOST 2.0)", "XML (ssj500k 2.1)", "XML (Gigafida 2.0)", "XML (Gigafida 1.0, Kres 1.0)"};
	private static final ArrayList<String> SELECT_READER = new ArrayList<>(Arrays.asList(SELECT_READER_ARRAY));

	private static final String [] PUNCTUATION_ARRAY = {"punctuation.COMMA", "punctuation.POINT"};
	private static final ArrayList<String> PUNCTUATION = new ArrayList<>(Arrays.asList(PUNCTUATION_ARRAY));

	private Collection<File> corpusFiles;
	private File selectedDirectory;

	/**
	 * Hack allowing to modify the default behavior of the tooltips.
	 * @param openDelay The open delay, knowing that by default it is set to 1000.
	 * @param visibleDuration The visible duration, knowing that by default it is set to 5000.
	 * @param closeDelay The close delay, knowing that by default it is set to 200.
	 * @param hideOnExit Indicates whether the tooltip should be hide on exit,
	 * knowing that by default it is set to false.
	 */
	private static void updateTooltipBehavior(double openDelay, double visibleDuration,
											  double closeDelay, boolean hideOnExit) {
		try {
			// Get the non public field "BEHAVIOR"
			Field fieldBehavior = Tooltip.class.getDeclaredField("BEHAVIOR");
			// Make the field accessible to be able to get and set its value
			fieldBehavior.setAccessible(true);
			// Get the value of the static field
			Object objBehavior = fieldBehavior.get(null);
			// Get the constructor of the private static inner class TooltipBehavior
			Constructor<?> constructor = objBehavior.getClass().getDeclaredConstructor(
					Duration.class, Duration.class, Duration.class, boolean.class
			);
			// Make the constructor accessible to be able to invoke it
			constructor.setAccessible(true);
			// Create a new instance of the private static inner class TooltipBehavior
			Object tooltipBehavior = constructor.newInstance(
					new Duration(openDelay), new Duration(visibleDuration),
					new Duration(closeDelay), hideOnExit
			);
			// Set the new instance of TooltipBehavior
			fieldBehavior.set(null, tooltipBehavior);
		} catch (Exception e) {
			throw new IllegalStateException(e);
		}
	}

	public void initialize() {
		// add CSS style
		corpusTabPane.getStylesheets().add("style.css");
		corpusTabPane.getStyleClass().add("root");

		stage = new Stage();

		manageTranslations();

		selectReaderCB.setItems(FXCollections.observableArrayList(SELECT_READER));

		selectReaderCB.valueProperty().addListener((observable, oldValue, newValue) -> {
			if(newValue == null){
				selectReaderCB.getSelectionModel().select(newValue);
			}
			selectReader = newValue;
			selectReader();
			if(corpus != null && corpus.getCorpusType() != null) {
				changeCorpus(false);
			}
			logger.info("calculateForCB:", newValue);
		});

		outputNameTF.focusedProperty().addListener((observable, oldValue, newValue) -> {
			corpus.setCorpusName(outputNameTF.getText());
			outputName = outputNameTF.getText();
		});

		selectReaderCB.getSelectionModel().select(0);

		// comma / point choice
		punctuationCB.valueProperty().addListener((observable, oldValue, newValue) -> {
			if(newValue == null){
				newValue = I18N.getTranslatedValue(oldValue, PUNCTUATION);
				punctuationCB.getSelectionModel().select(newValue);
			}
			punctuation = newValue;
			if(corpus != null) {
				corpus.setPunctuation(I18N.getRootValue(punctuation, PUNCTUATION));
			}
		});

		punctuationCB.getSelectionModel().select(0);

		// add listeners
		chooseCorpusLocationB.setOnAction(e -> chooseCorpusLocation());
//		helpH.setOnAction(e -> openHelpWebsite());

		readHeaderInfoChB.selectedProperty().addListener((observable, oldValue, newValue) -> {
			readHeaderInfo = newValue;
			if(corpusFiles != null) {
				changeCorpus(false);
			}
			logger.info("read headers: ", readHeaderInfo);
		});

		chooseResultsLocationB.setOnAction(e -> chooseResultsLocation(null));

        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");
        });

		// set labels and toggle visibility
		togglePiAndSetCorpusWrapper(false);
	}

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

	private void manageTranslations(){
		chooseCorpusLocationB.textProperty().bind(I18N.createStringBinding("button.setCorpusLocation"));
		chooseCorpusLocationL.textProperty().bind(I18N.createStringBinding("label.setCorpusLocation"));
		readHeaderInfoChB.textProperty().bind(I18N.createStringBinding("checkBox.readHeaderInfo"));
		chooseResultsLocationB.textProperty().bind(I18N.createStringBinding("button.chooseResultsLocation"));
		chooseResultsLocationL.textProperty().bind(I18N.createStringBinding("label.chooseResultsLocation"));
//		helpH.textProperty().bind(I18N.createStringBinding("hyperlink.help"));
		changeLanguageB.textProperty().bind(I18N.createStringBinding("button.language"));
		chooseCorpusL.textProperty().bind(I18N.createStringBinding("message.LABEL_CORPUS_LOCATION_NOT_SET"));
		chooseResultsL.textProperty().bind(I18N.createStringBinding("message.LABEL_RESULTS_LOCATION_NOT_SET"));

		readHeaderInfoL.textProperty().bind(I18N.createStringBinding("label.readHeaderInfo"));
		selectReaderL.textProperty().bind(I18N.createStringBinding("label.selectReader"));
		outputNameL.textProperty().bind(I18N.createStringBinding("label.outputName"));
		punctuationL.textProperty().bind(I18N.createStringBinding("label.punctuation"));

		addTooltipToImage(chooseCorpusLocationI, I18N.createStringBinding("label.corpusTab.chooseCorpusLocationH"));
		addTooltipToImage(readHeaderInfoI, I18N.createStringBinding("label.corpusTab.readHeaderInfoH"));
		addTooltipToImage(chooseResultsLocationI, I18N.createStringBinding("label.corpusTab.chooseResultsLocationH"));
		addTooltipToImage(selectReaderI, I18N.createStringBinding("label.corpusTab.selectReaderH"));
		addTooltipToImage(outputNameI, I18N.createStringBinding("label.corpusTab.outputNameH"));
		addTooltipToImage(punctuationI, I18N.createStringBinding("label.corpusTab.punctuationH"));

		punctuationCB.itemsProperty().bind(I18N.createObjectBinding(PUNCTUATION));
	}

	private void togglePiAndSetCorpusWrapper(boolean piIsActive) {
		locationScanPI.setVisible(piIsActive);
		setCorpusWrapperP.setLayoutX(piIsActive ? 100.0 : 10.0);
	}

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

	/**
	 * In order for a directory to pass as a valid corpus location, following criteria has to be met:
	 * <ul>
	 * <li>it can't be null</li>
	 * <li>it has to be readable</li>
	 * <li>it has to contain xml files</li>
	 * <li>xml files have to contain valid headers from which we can infer the corpus type</li>
	 * <li>corpus type must be one of the expected corpus types - as noted in the @see data.CorpusType.class	</li>
	 * </ul>
	 * <p>
	 * Additionally, if the user checks to read taxonomy/filters from the corpus files, that read
	 * has to produce a non-empty list results list
	 */
	private void chooseCorpusLocation() {
		File oldSelectedDirectory = selectedDirectory != null ? new File(selectedDirectory.getAbsolutePath()) : null;
		selectedDirectory = directoryChooser();
		if(selectedDirectory == null) {
			selectedDirectory = oldSelectedDirectory;
		}

		if (selectedDirectory != null && ValidationUtil.isReadableDirectory(selectedDirectory)) {
			logger.info("selected corpus dir: ", selectedDirectory.getAbsolutePath());

			// scan for xml files
			corpusFiles = FileUtils.listFiles(selectedDirectory, FileFilterUtils.suffixFileFilter("xml", IOCase.INSENSITIVE), TrueFileFilter.INSTANCE);

			changeCorpus(true);

		}
	}

	private void changeCorpus(boolean resetResultsLocation){
		// make sure there are corpus files in selected directory or notify the user about it
		if (corpusFiles.size() == 0) {
			// try .vert
			corpusFiles = FileUtils.listFiles(selectedDirectory, FileFilterUtils.suffixFileFilter("vert", IOCase.INSENSITIVE), TrueFileFilter.INSTANCE);
			Collection<File> corpusFilesRegi = FileUtils.listFiles(selectedDirectory, FileFilterUtils.suffixFileFilter("regi", IOCase.INSENSITIVE), TrueFileFilter.INSTANCE);

			if (corpusFiles.size() == 0){
				logger.info("alert: ", I18N.get("message.WARNING_CORPUS_NOT_FOUND"));
				showAlert(Alert.AlertType.ERROR, I18N.get("message.WARNING_CORPUS_NOT_FOUND"), null);
			} else if (corpusFilesRegi.size() == 0){
				GUIController.showAlert(Alert.AlertType.ERROR, String.format(I18N.get("message.ERROR_NO_REGI_FILE_FOUND"), selectedDirectory.getAbsolutePath()));
			} else {
				corpusLocation = selectedDirectory.getAbsolutePath();
				corpusFilesSize = String.valueOf(corpusFiles.size());
				Messages.setChooseCorpusProperties(corpusLocation, corpusFilesSize, corpusType != null ? corpusType.toString() : null);

				corpus.setCorpusType(corpusType);

				Messages.setChooseCorpusProperties(corpusLocation, corpusFilesSize, corpusType.toString());

				StringBuilder sb = new StringBuilder();
				sb.append(corpusLocation)
						.append("\n")
						.append(String.format(I18N.get("message.NOTIFICATION_FOUND_X_FILES"), corpusFiles.size()))
						.append("\n")
						.append(String.format(I18N.get("message.NOTIFICATION_CORPUS"), corpusType.toString()));

				String result = sb.toString();

				logger.debug(result);

				initNewCorpus(selectedDirectory, corpusFiles);
				if(resetResultsLocation){
					chooseResultsLocation(selectedDirectory);
				} else {
					chooseResultsLocation(new File(chooseResultsLabelContent));
				}
				Messages.setChooseCorpusProperties(corpusLocation, corpusFilesSize, corpusType.toString());

				corpus.setChosenCorpusLocation(selectedDirectory);
				corpus.setDetectedCorpusFiles(corpusFiles);

				chooseCorpusLabelContent = result;
				if (readHeaderInfo) {
					logger.info("reading header info...");
					readHeaderInfo();
				} else {
					setResults();

					setCorpusForAnalysis();
				}

			}

		} else {
			corpusLocation = selectedDirectory.getAbsolutePath();
			corpusFilesSize = String.valueOf(corpusFiles.size());
			Messages.setChooseCorpusProperties(corpusLocation, corpusFilesSize, corpusType != null ? corpusType.toString() : null);

			selectReader();
			StringBuilder sb = new StringBuilder();
			sb.append(corpusLocation)
					.append("\n")
					.append(String.format(I18N.get("message.NOTIFICATION_FOUND_X_FILES"), corpusFiles.size()))
					.append("\n")
                    .append("\n");

			String chooseCorpusLabelContentTmp = sb.toString();
			logger.debug(chooseCorpusLabelContentTmp);

			if (chooseCorpusLabelContentTmp == null) {
				logger.info("alert: ", I18N.get("message.WARNING_CORPUS_NOT_FOUND"));
				showAlert(Alert.AlertType.ERROR, I18N.get("message.WARNING_CORPUS_NOT_FOUND"), null);
			} else {

				initNewCorpus(selectedDirectory, corpusFiles);
				if(resetResultsLocation){
					chooseResultsLocation(selectedDirectory);
				} else {
					chooseResultsLocation(new File(chooseResultsLabelContent));
				}
				Messages.setChooseCorpusProperties(corpusLocation, corpusFilesSize, corpusType.toString());

				corpus.setChosenCorpusLocation(selectedDirectory);
				corpus.setDetectedCorpusFiles(corpusFiles);
				chooseCorpusLabelContent = chooseCorpusLabelContentTmp;
				logger.info("corpus dir: ", corpus.getChosenCorpusLocation().getAbsolutePath());

				if (readHeaderInfo) {
					logger.info("reading header info...");
					readHeaderInfo();
				} else {
					setResults();

					setCorpusForAnalysis();
				}
			}
		}
		corpus.setCorpusName(outputName);
		corpus.setPunctuation(I18N.getRootValue(punctuation, PUNCTUATION));
	}

	/**
	 * If a user selects a valid corpus location, we define a new corpus (so none of the old data gets carried over)
	 *
	 * @param selectedDirectory
	 * @param corpusFiles
	 */
	private void initNewCorpus(File selectedDirectory, Collection<File> corpusFiles) {
		corpus = new Corpus();
		corpus.setCorpusType(corpusType);
		corpus.setDetectedCorpusFiles(corpusFiles);
		corpus.setChosenCorpusLocation(selectedDirectory);
	}

	private void chooseResultsLocation(File dir) {
		// results location can be set either to default value (after selecting valid corpus location) - dir attribute
		// or to a dir picked via directoryChooser (when dir == null
		File selectedDirectory = dir == null ? directoryChooser() : dir;

		if (selectedDirectory != null) {
			String resultsLocationPath = selectedDirectory.getAbsolutePath().concat(File.separator);
			File chosenResultsLocationTmp = new File(resultsLocationPath);

			if (!ValidationUtil.isValidDirectory(chosenResultsLocationTmp)) {
				showAlert(Alert.AlertType.ERROR, I18N.get("message.WARNING_RESULTS_DIR_NOT_VALID"));
				logger.info("alert: ", I18N.get("message.WARNING_RESULTS_DIR_NOT_VALID"));
			} else {
				corpus.setChosenResultsLocation(chosenResultsLocationTmp);
				chooseResultsLabelContent = corpus.getChosenResultsLocation().getAbsolutePath();
				chooseResultsL.textProperty().unbind();
				chooseResultsL.setText(chooseResultsLabelContent);
				logger.info("results dir: " + chooseResultsLabelContent);
			}
		}
	}

	private void setResults() {
		// if everything is ok
		// check and enable checkbox if GOS

		// set default results location
		String defaultResultsLocationPath = corpus.getChosenCorpusLocation().getAbsolutePath();
		logger.info("setting default results location to: ", defaultResultsLocationPath);

		Messages.setChooseCorpusL(chooseCorpusL, chooseCorpusLabelContent);
	}

	private void readHeaderInfo() {
		CorpusType corpusType = corpus.getCorpusType();
		Collection<File> corpusFiles = corpus.getDetectedCorpusFiles();
		togglePiAndSetCorpusWrapper(true);
		chooseCorpusL.textProperty().unbind();
		chooseCorpusL.setText(I18N.get("message.LABEL_SCANNING_CORPUS"));

		logger.info("reading header data for ", corpusType.toString());

		if (corpusType == CorpusType.GIGAFIDA || corpusType == CorpusType.GOS || corpusType == CorpusType.KOST || corpusType == CorpusType.SSJ500K || corpusType == CorpusType.GIGAFIDA2) {
			boolean corpusIsSplit = corpusFiles.size() > 1;

			final Task<HashSet<String>> task = new Task<HashSet<String>>() {
				@Override
				protected HashSet<String> call() throws Exception {
					HashSet<String> values = new HashSet<>();
					long i = 0;

					if (!corpusIsSplit) {
						updateProgress(-1.0f, -1.0f);
					}

					for (File file : corpusFiles) {
						values.addAll((Collection<? extends String>) XML_processing.readXmlHeaderTaxonomyAndFilters(file.getAbsolutePath(), corpusIsSplit, corpusType));
						i++;

						if (corpusIsSplit) {
							updateProgress(i, corpusFiles.size());
						}
					}

					updateProgress(1.0f, 1.0f);
					return values;
				}
			};

			locationScanPI.progressProperty().bind(task.progressProperty());

			task.setOnSucceeded(e -> {
				ObservableList<String> readTaxonomy = Tax.getTaxonomyForComboBox(corpusType, task.getValue());

				if (ValidationUtil.isEmpty(readTaxonomy)) {
					// if no taxonomy found alert the user and keep other tabs disabled
//					aboutTab.setDisable(true);
					stringLevelTabNew2.setDisable(true);
					oneWordAnalysisTab.setDisable(true);
					characterLevelTab.setDisable(true);
					wordLevelTab.setDisable(true);
					filterTab.setDisable(true);
					Messages.updateChooseCorpusL();

					logger.info("No taxonomy found in headers.");
					GUIController.showAlert(Alert.AlertType.ERROR, I18N.get("message.WARNING_NO_TAXONOMY_FOUND"));
				} else {
					// set taxonomy, update label
					corpus.setTaxonomy(readTaxonomy);
					corpus.setHeaderRead(true);
					Messages.setChooseCorpusL(chooseCorpusL, chooseCorpusLabelContent);
					setResults();
					setCorpusForAnalysis();
				}

				togglePiAndSetCorpusWrapper(false);

			});

			task.setOnCancelled(e -> togglePiAndSetCorpusWrapper(false));
			task.setOnFailed(e -> togglePiAndSetCorpusWrapper(false));

			final Thread thread = new Thread(task, "task");
			thread.setDaemon(true);
			thread.start();
		} else if (corpusType == CorpusType.SOLAR) {
			// many many fields
			boolean corpusIsSplit = corpusFiles.size() > 1;

			final Task<HashMap<String, HashSet<String>>> task = new Task<HashMap<String, HashSet<String>>>() {
				@Override
				protected HashMap<String, HashSet<String>> call() throws Exception {
					HashMap<String, HashSet<String>> values = new HashMap<>();
					long i = 0;

					if (!corpusIsSplit) {
						updateProgress(-1.0f, -1.0f);
					}

					for (File file : corpusFiles) {
						HashMap<String, HashSet<String>> tmpvalues = (HashMap<String, HashSet<String>>) XML_processing.readXmlHeaderTaxonomyAndFilters(file.getAbsolutePath(), corpusIsSplit, corpusType);

						// update final results
						for (Map.Entry<String, HashSet<String>> entry : tmpvalues.entrySet()) {
							if (values.containsKey(entry.getKey())) {
								values.get(entry.getKey()).addAll(entry.getValue());
							} else {
								values.put(entry.getKey(), entry.getValue());
							}
						}

						i++;

						if (corpusIsSplit) {
							updateProgress(i, corpusFiles.size());
						}
					}

					updateProgress(1.0f, 1.0f);
					return values;
				}
			};

			locationScanPI.progressProperty().bind(task.progressProperty());

			task.setOnSucceeded(e -> {
				HashMap<String, HashSet<String>> values = task.getValue();

				if (ValidationUtil.isEmpty(values)) {
					// if no taxonomy found alert the user and keep other tabs disabled
					logger.info("No solar filters found in headers.");
					GUIController.showAlert(Alert.AlertType.ERROR, I18N.get("message.WARNING_NO_SOLAR_FILTERS_FOUND"));
				} else {
					HashMap<String, ObservableList<String>> filtersForComboBoxes = SolarFilters.getFiltersForComboBoxes(values);
					// set taxonomy, update label
					corpus.setSolarFiltersForXML(values);
					corpus.setSolarFilters(filtersForComboBoxes);
					corpus.setHeaderRead(true);
					Messages.setChooseCorpusL(chooseCorpusL, chooseCorpusLabelContent);
					setResults();
					setCorpusForAnalysis();
				}

				togglePiAndSetCorpusWrapper(false);

			});

			task.setOnCancelled(e -> togglePiAndSetCorpusWrapper(false));
			task.setOnFailed(e -> togglePiAndSetCorpusWrapper(false));

			final Thread thread = new Thread(task, "task");
			thread.setDaemon(true);
			thread.start();
		} else if (corpusType == CorpusType.VERT) {
			// many many fields
			boolean corpusIsSplit = corpusFiles.size() > 1;

			final Task<HashSet<String>> task = new Task<HashSet<String>>() {
				@Override
				protected HashSet<String> call() throws Exception {
					HashSet<String> values = new HashSet<>();
					long i = 0;

					if (!corpusIsSplit) {
						updateProgress(-1.0f, -1.0f);
					}

					for (File file : corpusFiles) {
						HashSet<String> tmpvalues = XML_processing.readVertHeaderTaxonomyAndFilters(file.getAbsolutePath(), corpusIsSplit, corpusType);

						// update final results
						for (String entry : tmpvalues) {
							if (!entry.equals("-")) {
								values.add(entry);
							}
						}

						i++;

						if (corpusIsSplit) {
							updateProgress(i, corpusFiles.size());
						}
					}

					updateProgress(1.0f, 1.0f);
					return values;
				}
			};

			locationScanPI.progressProperty().bind(task.progressProperty());

			task.setOnSucceeded(e -> {
				ObservableList<String> readTaxonomy = Tax.getTaxonomyForComboBox(corpusType, task.getValue());
					// set taxonomy, update label
					corpus.setTaxonomy(readTaxonomy);
					corpus.setHeaderRead(true);
					Messages.setChooseCorpusL(chooseCorpusL, chooseCorpusLabelContent);
					setResults();
					setCorpusForAnalysis();
	//				}

			togglePiAndSetCorpusWrapper(false);

			});

			task.setOnCancelled(e -> togglePiAndSetCorpusWrapper(false));
			task.setOnFailed(e -> togglePiAndSetCorpusWrapper(false));


			final Thread thread = new Thread(task, "task");
			thread.setDaemon(true);
			thread.start();
		}

	}

	private void setCorpusForAnalysis() {
		if (corpus.validate()) {
			// new statistic, enable tabs...
//			aboutTab.setDisable(false);
//			aboutTabController.init();
			stringLevelTabNew2.setDisable(false);
			satNew2Controller.setCorpus(corpus);
			satNew2Controller.init();
			oneWordAnalysisTab.setDisable(false);
			oneWordTabController.setCorpus(corpus);
			oneWordTabController.init();
			characterLevelTab.setDisable(false);
			catController.setCorpus(corpus);
			catController.init();
			wordLevelTab.setDisable(false);
			wlController.setCorpus(corpus);
			wlController.init();

			if (corpus.getCorpusType() == CorpusType.SOLAR) {
				filterTab.setDisable(false);
				tabPane.getTabs().add(1, filterTab);
				ffsController.setCorpus(corpus);
				ffsController.initFilters();
			} else {
				filterTab.setDisable(true);
				tabPane.getTabs().removeAll(filterTab);
			}
		} else {
			GUIController.showAlert(Alert.AlertType.ERROR, corpus.getValidationErrorsToString());
		}
	}

	private File directoryChooser() {
		DirectoryChooser directoryChooser = new DirectoryChooser();

		// open in the folder where the jar is located if possible
		File workingDir = getWorkingDirectory();

		if (workingDir != null) {
			directoryChooser.setInitialDirectory(workingDir);
		}

		return directoryChooser.showDialog(stage);
	}

	private void selectReader() {
		switch (selectReader) {
			case "VERT + REGI":
				corpusType = VERT;
				break;
			case "XML (Šolar 1.0)":
				corpusType = SOLAR;
				break;
			case "XML (GOS 1.0)":
				corpusType = GOS;
				break;
			case "XML (KOST 2.0)":
				corpusType = KOST;
				break;
			case "XML (ssj500k 2.1)":
				corpusType = SSJ500K;
				break;
			case "XML (Gigafida 2.0)":
				corpusType = GIGAFIDA2;
				break;
			case "XML (Gigafida 1.0, Kres 1.0)":
				corpusType = GIGAFIDA;
				break;
			default:
				logger.debug("No output detected!");
				break;
		}
	}

	private String detectCorpusType(Collection<File> corpusFiles) {
		// check that we recognize this corpus
		// read first file only, maybe later do all, if toll on resources is acceptable
		File f = corpusFiles.iterator().next();
		String title = XML_processing.readXMLHeaderTag(f.getAbsolutePath(), "title").toLowerCase();
		String attrib = XML_processing.readXMLHeaderAttribute(f.getAbsolutePath(), "body", "base").toLowerCase();
		String debug = "";

		// check if XML file's title contains any of recognized corpus titles
		corpusType = null;
		if (title.contains(SOLAR.getNameLowerCase())) {
			corpusType = SOLAR;
		} else if (title.contains(GIGAFIDA.getNameLowerCase())) {
			String edition = XML_processing.readXMLHeaderTag(f.getAbsolutePath(), "edition").toLowerCase();
			if (Double.valueOf(edition) < 2.0) {
				corpusType = GIGAFIDA;
			} else {
				corpusType = GIGAFIDA2;
			}
		} else if (title.contains(GOS.getNameLowerCase())) {
			corpusType = GOS;
		} else if (attrib.contains(SSJ500K.getNameLowerCase())) {
			corpusType = SSJ500K;
		} else if (attrib.contains(KOST.getNameLowerCase())) {
			corpusType = KOST;
		}

		if (corpusType == null) {
			return null;
		} else {
			corpus.setCorpusType(corpusType);

			Messages.setChooseCorpusProperties(corpusLocation, corpusFilesSize, corpusType.toString());

			StringBuilder sb = new StringBuilder();
			sb.append(corpusLocation)
					.append("\n")
					.append(String.format(I18N.get("message.NOTIFICATION_FOUND_X_FILES"), corpusFiles.size()))
					.append("\n")
					.append(String.format(I18N.get("message.NOTIFICATION_CORPUS"), corpusType.toString()));

			String result = sb.toString();

			logger.debug(result);
			return result;
		}
	}

	public Corpus getCorpus() {
		return corpus;
	}

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

	public void setAboutTab(Tab aboutTab) { this.aboutTab = aboutTab; }

	public void setStringLevelTabNew2(Tab stringLevelTabNew2) { this.stringLevelTabNew2 = stringLevelTabNew2; }

	public void setOneWordAnalysisTab(Tab oneWordAnalysisTab) { this.oneWordAnalysisTab = oneWordAnalysisTab; }

	public void setCharacterLevelTab(Tab characterLevelTab) { this.characterLevelTab = characterLevelTab; }

	public void setWordLevelTab(Tab wordLevelTab) {
		this.wordLevelTab = wordLevelTab;
	}

	public void setFilterTab(Tab filterTab) {
		this.filterTab = filterTab;
	}

	public void setFfsController(FiltersForSolar ffsController) {
		this.ffsController = ffsController;
	}

	public void setTabPane(TabPane tabPane) {
		this.tabPane = tabPane;
	}

	public void setSatNew2Controller(StringAnalysisTabNew2 satNew2Controller) { this.satNew2Controller = satNew2Controller; }

	public void setOneWordTabController(OneWordAnalysisTab oneWordTabController) { this.oneWordTabController = oneWordTabController; }

	public void setCatController(CharacterAnalysisTab catController) { this.catController = catController; }

	public void setWlController(WordLevelTab wlController) {
		this.wlController = wlController;
	}

	public void setHostServices(HostServices hostServices){
		this.hostService = hostServices;
	}

    public void setAboutTabController(AboutTab aboutTabController) {
        this.aboutTabController = aboutTabController;
    }
}
