package eu.simstadt.regionchooser;

import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.logging.Logger;
import java.util.prefs.Preferences;
import java.util.stream.Stream;
import org.locationtech.jts.io.ParseException;
import com.ximpleware.NavException;
import com.ximpleware.XPathParseException;
import eu.simstadt.regionchooser.fast_xml_parser.ConvexHullCalculator;
import javafx.application.Platform;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Task;
import javafx.concurrent.Worker.State;
import javafx.geometry.HPos;
import javafx.geometry.VPos;
import javafx.scene.layout.Region;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.DirectoryChooser;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
import netscape.javascript.JSObject;


public class RegionChooserBrowser extends Region
{
	private static final Logger LOGGER = Logger.getLogger(RegionChooserBrowser.class.getName());
	private static final String PREF_RECENT_REPOSITORY = "RECENT_REPOSITORY";

	/**
	 * JavaFX Backend for RegionChooser. Inside simstadt_openlayers.js frontend, this class is available as `fxapp`.
	 */
	public class JavaScriptFXBridge
	{
		private Path repo;

		public JavaScriptFXBridge() {
			Preferences userPrefs = Preferences.userRoot().node("/eu/simstadt/desktop");
			String repoString = userPrefs.get(PREF_RECENT_REPOSITORY, "../TestRepository");
			repo = Paths.get(repoString);
		}

		/**
		 * Launches a background thread in which the hull gets extracted for every CityGML file. The hull gets sent back
		 * to the JS app in order to be displayed.
		 *
		 * NOTE: To be very honest, I don't really understand concurrency in JavaFX. Eric
		 *
		 */
		public void refreshHulls() {
			//NOTE: Could add progress bar?
			Task<Void> task = new Task<Void>() {
				@Override
				public Void call() throws IOException {
					ConvexHullCalculator.extractHullsForEveryCityGML(repo,
							hullKML -> Platform.runLater(() -> jsApp.call("addCityGmlHull", hullKML)));
					return null;
				}
			};

			task.setOnRunning(e -> {
				jsApp.call("display", "Importing citgyml. Please wait.");
				jsApp.call("showRepositoryName", repo.getFileName().toString());
				jsApp.call("init");
			});

			task.setOnSucceeded(e -> jsApp.call("ready"));

			new Thread(task).start();
		}

		/**
		 * This method is called from Javascript, with a prepared wktPolygon written in local coordinates.
		 */
		public int downloadRegionFromCityGMLs(String wktPolygon, String project, String csvCitygmls, String srsName)
				throws IOException, ParseException, XPathParseException, NavException {
			// It doesn't seem possible to pass arrays or list from JS to Java. So csvCitygmls contains names separated by ;
			Path[] paths = Stream.of(csvCitygmls.split(";")).map(s -> citygmlPath(project, s)).toArray(Path[]::new);

			String proposedName = csvCitygmls.replace(";", "_").replace(".gml", "") + ".gml";

			File outputFile = selectSaveFileWithDialog(project, proposedName, "part");

			if (outputFile == null) {
				return -1;
			}

			int count;
			try (BufferedWriter gmlWriter = Files.newBufferedWriter(outputFile.toPath())) {
				count = RegionExtractor.selectRegionDirectlyFromCityGML(wktPolygon, srsName, gmlWriter, paths);
			}
			return count;
		}


		public void selectRepository() {
			Preferences userPrefs = Preferences.userRoot().node("/eu/simstadt/desktop");
			String currentRepo = userPrefs.get(PREF_RECENT_REPOSITORY, "../TestRepository");

			DirectoryChooser fileChooser = new DirectoryChooser();
			Stage mainStage = (Stage) RegionChooserBrowser.this.getScene().getWindow();
			fileChooser.setTitle("Select Repository");
			fileChooser.setInitialDirectory(new File(currentRepo));
			File repoLocation = fileChooser.showDialog(mainStage);

			if (repoLocation != null) {
				repo = repoLocation.toPath();
				userPrefs.put(PREF_RECENT_REPOSITORY, repo.toAbsolutePath().toString());
				LOGGER.info("Repository was set to " + repo);
				refreshHulls();
			} else {
				LOGGER.warning("No repository chosen.");
			}
		}

		private File selectSaveFileWithDialog(String project, String citygml, String suffix) {
			Stage mainStage = (Stage) RegionChooserBrowser.this.getScene().getWindow();
			FileChooser fileChooser = new FileChooser();
			fileChooser.setTitle("Save CITYGML ids");
			if (project != null) {
				fileChooser.setInitialDirectory(repo.resolve(project + ".proj").toFile());
			} else {
				fileChooser.setInitialDirectory(repo.toFile());
			}
			if (suffix.isEmpty()) {
				fileChooser.setInitialFileName(citygml);
			} else {
				fileChooser.setInitialFileName(citygml.replace(".", "_" + suffix + "."));
			}
			FileChooser.ExtensionFilter extFilter = new FileChooser.ExtensionFilter("GML files (*.gml)", "*.gml");
			fileChooser.getExtensionFilters().add(extFilter);
			return fileChooser.showSaveDialog(mainStage);
		}

		public void log(String text) {
			LOGGER.info(text);
		}

		public void warning(String text) {
			LOGGER.warning(text);
		}

		private Path citygmlPath(String project, String citygml) {
			return repo.resolve(project + ".proj").resolve(citygml);
		}

	}

	final WebView browser = new WebView();
	final WebEngine webEngine = browser.getEngine();
	final JavaScriptFXBridge fxapp = new JavaScriptFXBridge();
	private JSObject jsApp;

	public RegionChooserBrowser() {
		//apply the styles
		getStyleClass().add("browser");
		String url = RegionChooserFX.class.getResource("website/index.html").toExternalForm();
		webEngine.load(url); // load the web page
		// process page loading
		webEngine.getLoadWorker().stateProperty().addListener(
				(ObservableValue<? extends State> ov, State oldState, State newState) -> {
					if (newState == State.SUCCEEDED) {
						jsApp = (JSObject) webEngine.executeScript("regionChooser");
						jsApp.call("setFxApp", fxapp);
						fxapp.refreshHulls();
					}
				});
		//add the web view to the scene
		getChildren().add(browser);
	}

	@Override
	protected void layoutChildren() {
		double w = getWidth();
		double h = getHeight();
		layoutInArea(browser, 0, 0, w, h, 0, HPos.CENTER, VPos.CENTER);
	}

	@Override
	protected double computePrefWidth(double height) {
		return 1024;
	}

	@Override
	protected double computePrefHeight(double width) {
		return 720;
	}
}
