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, null);
			repo = repoString == null? null : 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.
		 */
		public void refreshHulls() {
			if (repo == null || !Files.exists(repo)) {
				selectRepository();
			}
			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.");
				if (repo != null) {
					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. Executes it in
		 * the background to avoid freezing the GUI
		 */
		public void downloadRegionFromCityGMLs(String wktPolygon, String project, String csvCitygmls, String srsName) {
			// 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;
			}

			Task<Integer> downloadTask = new Task<Integer>() {
				@Override
				public Integer call() throws IOException, XPathParseException, NavException, ParseException {
					int count = -1;
					try (BufferedWriter gmlWriter = Files.newBufferedWriter(outputFile.toPath())) {
						count = RegionExtractor.selectRegionDirectlyFromCityGML(wktPolygon, srsName, gmlWriter, paths);
					}
					LOGGER.info(outputFile + " has been written");
					return count;
				}
			};

			downloadTask.setOnRunning(e -> jsApp.call("downloadStart"));

			downloadTask.setOnSucceeded(e -> jsApp.call("downloadFinished", e.getSource().getValue()));

			new Thread(downloadTask).start();
		}


		public void selectRepository() {
			Preferences userPrefs = Preferences.userRoot().node("/eu/simstadt/desktop");

			DirectoryChooser fileChooser = new DirectoryChooser();
			Stage mainStage = (Stage) RegionChooserBrowser.this.getScene().getWindow();
			fileChooser.setTitle("Select Repository");
			if (repo != null && Files.exists(repo)) {
				fileChooser.setInitialDirectory(repo.toFile());
			}
			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 && !isRepoAProject()) {
				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);
		}

		/*
		 * NOTE: Users sometime select some_region.proj/ as a repository.
		 * SimStadt won't show any project in this "repository", but RegionChooser shouldn't complain and
		 * still be able to extract regions.
		 */
		private boolean isRepoAProject(){
			return repo.toString().endsWith(".proj");
		}

		private Path citygmlPath(String project, String citygml) {
			if (isRepoAProject()){
				return repo.resolve(citygml);
			} else {
				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;
	}
}
