package com.kodedu.terminalfx;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.kodedu.terminalfx.annotation.WebkitCall;
import com.kodedu.terminalfx.config.TerminalConfig;
import com.kodedu.terminalfx.helper.ThreadHelper;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyIntegerProperty;
import javafx.beans.property.ReadOnlyIntegerWrapper;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.input.Clipboard;
import javafx.scene.input.ClipboardContent;
import javafx.scene.layout.Pane;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import netscape.javascript.JSObject;

import java.io.Reader;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;

public class TerminalView extends Pane {

	private final WebView webView;
	private final ReadOnlyIntegerWrapper columnsProperty;
	private final ReadOnlyIntegerWrapper rowsProperty;
	private final ObjectProperty<Reader> inputReaderProperty;
	private final ObjectProperty<Reader> errorReaderProperty;
	private TerminalConfig terminalConfig = new TerminalConfig();
	protected final CountDownLatch countDownLatch = new CountDownLatch(1);

	public TerminalView() {
		webView = new WebView();
		columnsProperty = new ReadOnlyIntegerWrapper(150);
		rowsProperty = new ReadOnlyIntegerWrapper(10);
		inputReaderProperty = new SimpleObjectProperty<>();
		errorReaderProperty = new SimpleObjectProperty<>();

		inputReaderProperty.addListener((observable, oldValue, newValue) -> {
			ThreadHelper.start(() -> {
				printReader(newValue);
			});
		});

		errorReaderProperty.addListener((observable, oldValue, newValue) -> {
			ThreadHelper.start(() -> {
				printReader(newValue);
			});
		});

		webView.getEngine().getLoadWorker().stateProperty().addListener((observable, oldValue, newValue) -> {
			getWindow().setMember("app", this);
		});
		webView.prefHeightProperty().bind(heightProperty());
		webView.prefWidthProperty().bind(widthProperty());

		webEngine().load(TerminalView.class.getResource("/hterm.html").toExternalForm());
	}

	@WebkitCall(from = "hterm")
	public String getPrefs() {
		try {
			return new ObjectMapper().writeValueAsString(getTerminalConfig());
		} catch(final Exception e) {
			throw new RuntimeException(e);
		}
	}

	public void updatePrefs(TerminalConfig terminalConfig) {
		if(getTerminalConfig().equals(terminalConfig)) {
			return;
		}

		setTerminalConfig(terminalConfig);
		final String prefs = getPrefs();

		ThreadHelper.runActionLater(() -> {
			try {
				getWindow().call("updatePrefs", prefs);
			} catch(final Exception e) {
				e.printStackTrace();
			}
		}, true);
	}

	@WebkitCall(from = "hterm")
	public void resizeTerminal(int columns, int rows) {
		columnsProperty.set(columns);
		rowsProperty.set(rows);
	}

	@WebkitCall
	public void onTerminalInit() {
		ThreadHelper.runActionLater(() -> {
			getChildren().add(webView);
		}, true);
	}

	@WebkitCall
	/**
	 * Internal use only
	 */
	public void onTerminalReady() {
		ThreadHelper.start(() -> {
			try {
				focusCursor();
				countDownLatch.countDown();
			} catch(final Exception e) {
			}
		});
	}

	private void printReader(Reader bufferedReader) {
		try {
			int nRead;
			final char[] data = new char[1 * 1024];

			while((nRead = bufferedReader.read(data, 0, data.length)) != -1) {
				final StringBuilder builder = new StringBuilder(nRead);
				builder.append(data, 0, nRead);
				print(builder.toString());
			}

		} catch(final Exception e) {
			e.printStackTrace();
		}
	}

	@WebkitCall(from = "hterm")
	public void copy(String text) {
		final Clipboard clipboard = Clipboard.getSystemClipboard();
		final ClipboardContent clipboardContent = new ClipboardContent();
		clipboardContent.putString(text);
		clipboard.setContent(clipboardContent);
	}

	public void onTerminalFxReady(Runnable onReadyAction) {
		ThreadHelper.start(() -> {
			ThreadHelper.awaitLatch(countDownLatch);

			if(Objects.nonNull(onReadyAction)) {
				ThreadHelper.start(onReadyAction);
			}
		});
	}

	protected void print(String text) {
		ThreadHelper.awaitLatch(countDownLatch);
		ThreadHelper.runActionLater(() -> {
			getTerminalIO().call("print", text);
		});

	}

	public void focusCursor() {
		ThreadHelper.runActionLater(() -> {
			webView.requestFocus();
			getTerminal().call("focus");
		}, true);
	}

	private JSObject getTerminal() {
		return (JSObject) webEngine().executeScript("t");
	}

	private JSObject getTerminalIO() {
		return (JSObject) webEngine().executeScript("t.io");
	}

	public JSObject getWindow() {
		return (JSObject) webEngine().executeScript("window");
	}

	private WebEngine webEngine() {
		return webView.getEngine();
	}

	public TerminalConfig getTerminalConfig() {
		if(Objects.isNull(terminalConfig)) {
			terminalConfig = new TerminalConfig();
		}
		return terminalConfig;
	}

	public void setTerminalConfig(TerminalConfig terminalConfig) {
		this.terminalConfig = terminalConfig;
	}

	public ReadOnlyIntegerProperty columnsProperty() {
		return columnsProperty.getReadOnlyProperty();
	}

	public int getColumns() {
		return columnsProperty.get();
	}

	public ReadOnlyIntegerProperty rowsProperty() {
		return rowsProperty.getReadOnlyProperty();
	}

	public int getRows() {
		return rowsProperty.get();
	}

	public ObjectProperty<Reader> inputReaderProperty() {
		return inputReaderProperty;
	}

	public Reader getInputReader() {
		return inputReaderProperty.get();
	}

	public void setInputReader(Reader reader) {
		inputReaderProperty.set(reader);
	}

	public ObjectProperty<Reader> errorReaderProperty() {
		return errorReaderProperty;
	}

	public Reader getErrorReader() {
		return errorReaderProperty.get();
	}

	public void setErrorReader(Reader reader) {
		errorReaderProperty.set(reader);
	}

}