import QtQuick 2.0
import QtQuick.Window 2.0
import QtQuick.Layouts 1.0
import QtQuick.Controls 1.0
import QtQuick.Dialogs 1.1
import Qt.labs.settings 1.0

Item {
	id: codeEditorView
	property string currentDocumentId: ""
	property string sourceInError
	property int openDocCount: 0
	signal documentEdit(string documentId)
	signal breakpointsChanged(string documentId)
	signal isCleanChanged(var isClean, string documentId)
	signal loadComplete

	function getDocumentText(documentId) {
		for (var i = 0; i < openDocCount; i++)	{
			if (editorListModel.get(i).documentId === documentId) {
				return editors.itemAt(i).item.getText();
			}
		}
		return "";
	}

	function isDocumentOpen(documentId) {
		for (var i = 0; i < openDocCount; i++)
			if (editorListModel.get(i).documentId === documentId &&
					editors.itemAt(i).item)
				return true;
		return false;
	}

	function openDocument(document)	{
		loadDocument(document);
		currentDocumentId = document.documentId;
	}

	function loadDocument(document) {
		for (var i = 0; i < openDocCount; i++)
			if (editorListModel.get(i).documentId === document.documentId)
				return; //already open

		if (editorListModel.count <= openDocCount)
			editorListModel.append(document);
		else
		{
			editorListModel.set(openDocCount, document);
			doLoadDocument(editors.itemAt(openDocCount).item, editorListModel.get(openDocCount), false)
			loadComplete();
		}
		openDocCount++;
	}

	function doLoadDocument(editor, document, create) {
		var data = fileIo.readFile(document.path);
		if (create)
		{
			editor.onLoadComplete.connect(function() {
				codeEditorView.loadComplete();
			});
			editor.onEditorTextChanged.connect(function() {
				documentEdit(editor.document.documentId);
				if (editor.document.isContract)
					codeModel.registerCodeChange(editor.document.documentId, editor.getText());
			});
			editor.onBreakpointsChanged.connect(function() {
				if (editor.document.isContract)
					breakpointsChanged(editor.document.documentId);
			});
			editor.onIsCleanChanged.connect(function() {
				isCleanChanged(editor.isClean, editor.document.documentId);
			});
		}
		editor.document = document;
		editor.setFontSize(editorSettings.fontSize);
		editor.sourceName = document.documentId;
		editor.setText(data, document.syntaxMode);
		editor.changeGeneration();
	}

	function getEditor(documentId) {
		for (var i = 0; i < openDocCount; i++)
		{
			if (editorListModel.get(i).documentId === documentId)
				return editors.itemAt(i).item;
		}
		return null;
	}

	function highlightExecution(documentId, location)
	{
		var editor = getEditor(documentId);
		if (editor)
		{
			if (documentId !== location.sourceName)
				findAndHightlight(location.start, location.end, location.sourceName)
			else
				editor.highlightExecution(location);
		}
	}

	// Execution is not in the current document. Try:
	// Open targeted document and hightlight (TODO) or
	// Warn user that file is not available
	function findAndHightlight(start, end, sourceName)
	{
		var editor = getEditor(currentDocumentId);
		if (editor)
			editor.showWarning(qsTr("Currently debugging in " + sourceName + ". Source not available."));
	}

	function editingContract() {
		for (var i = 0; i < openDocCount; i++)
			if (editorListModel.get(i).documentId === currentDocumentId)
				return editorListModel.get(i).isContract;
		return false;
	}

	function getBreakpoints() {
		var bpMap = {};
		for (var i = 0; i < openDocCount; i++)  {
			var documentId = editorListModel.get(i).documentId;
			var editor = editors.itemAt(i).item;
			if (editor) {
				bpMap[documentId] = editor.getBreakpoints();
			}
		}
		return bpMap;
	}

	function toggleBreakpoint() {
		var editor = getEditor(currentDocumentId);
		if (editor)
			editor.toggleBreakpoint();
	}

	function resetEditStatus(docId) {
		var editor = getEditor(docId);
		if (editor)
			editor.changeGeneration();
	}

	function goToCompilationError() {
		if (sourceInError === "")
			return;
		if (currentDocumentId !== sourceInError)
			projectModel.openDocument(sourceInError);
		for (var i = 0; i < openDocCount; i++)
		{
			var doc = editorListModel.get(i);
			if (doc.isContract && doc.documentId === sourceInError)
			{
				var editor = editors.itemAt(i).item;
				if (editor)
					editor.goToCompilationError();
				break;
			}
		}
	}

	function setFontSize(size) {
		if (size <= 10 || size >= 48)
			return;
		editorSettings.fontSize = size;
		for (var i = 0; i < editors.count; i++)
			editors.itemAt(i).item.setFontSize(size);
	}

	function displayGasEstimation(checked)
	{
		var editor = getEditor(currentDocumentId);
		if (editor)
			editor.displayGasEstimation(checked);
	}

	Component.onCompleted: projectModel.codeEditor = codeEditorView;

	Connections {
		target: codeModel
		onCompilationError: {
			sourceInError = _firstErrorLoc.source;
		}
		onCompilationComplete: {
			sourceInError = "";
			var gasCosts = codeModel.gasCostByDocumentId(currentDocumentId);
			var editor = getEditor(currentDocumentId);
			if (editor)
				editor.setGasCosts(gasCosts);
		}
	}

	Connections {
		target: projectModel
		onDocumentOpened: {
			openDocument(document);
		}

		onProjectSaving: {
			for (var i = 0; i < openDocCount; i++)
			{
				var doc = editorListModel.get(i);
				if (editors.itemAt(i))
				{
					var editor = editors.itemAt(i).item;
					if (editor)
						fileIo.writeFile(doc.path, editor.getText());
				}
			}
		}

		onProjectSaved: {
			if (projectModel.appIsClosing || projectModel.projectIsClosing)
				return;
			for (var i = 0; i < openDocCount; i++)
			{
				var doc = editorListModel.get(i);
				resetEditStatus(doc.documentId);
			}
		}

		onProjectClosed: {
			currentDocumentId = "";
			openDocCount = 0;
		}

		onDocumentSaved: {
			resetEditStatus(documentId);
		}

		onContractSaved: {
			resetEditStatus(documentId);
		}

		onDocumentSaving: {
			for (var i = 0; i < editorListModel.count; i++)
			{
				var doc = editorListModel.get(i);
				if (doc.path === document.path)
				{
					fileIo.writeFile(document.path, editors.itemAt(i).item.getText());
					break;
				}
			}
		}
	}

	CodeEditorStyle
	{
		id: style;
	}

	MessageDialog
	{
		id: messageDialog
		title: qsTr("File Changed")
		text: qsTr("This file has been changed outside of the editor. Do you want to reload it?")
		standardButtons: StandardButton.Yes | StandardButton.No
		property variant item
		property variant doc
		onYes: {
			doLoadDocument(item, doc, false);
			resetEditStatus(doc.documentId);
		}
	}

	Repeater {
		id: editors
		model: editorListModel
		onItemRemoved: {
			item.item.unloaded = true;
		}
		delegate: Loader {
			id: loader
			active: false
			asynchronous: true
			anchors.fill:  parent
			source: appService.haveWebEngine ? "WebCodeEditor.qml" : "CodeEditor.qml"
			visible: (index >= 0 && index < openDocCount && currentDocumentId === editorListModel.get(index).documentId)
			property bool changed: false
			onVisibleChanged: {
				loadIfNotLoaded()
				if (visible && item)
				{
					loader.item.setFocus();
					if (changed)
					{
						changed = false;
						messageDialog.item = loader.item;
						messageDialog.doc = editorListModel.get(index);
						messageDialog.open();
					}
					loader.item.displayGasEstimation(gasEstimationAction.checked);
				}
			}
			Component.onCompleted: {
				loadIfNotLoaded()
			}
			onLoaded: {
				doLoadDocument(loader.item, editorListModel.get(index), true)
			}

			Connections
			{
				target: projectModel
				onDocumentChanged: {
					if (!item)
						return;
					var current = editorListModel.get(index);
					if (documentId === current.documentId)
					{
						if (currentDocumentId === current.documentId)
						{
							messageDialog.item = loader.item;
							messageDialog.doc = editorListModel.get(index);
							messageDialog.open();
						}
						else
							changed = true
					}
				}

				onDocumentUpdated: {
					var document = projectModel.getDocument(documentId);
					for (var i = 0; i < editorListModel.count; i++)
						if (editorListModel.get(i).documentId === documentId)
						{
							editorListModel.set(i, document);
							break;
						}
				}

				onDocumentRemoved: {
					for (var i = 0; i < editorListModel.count; i++)
						if (editorListModel.get(i).documentId === documentId)
						{
							editorListModel.remove(i);
							openDocCount--;
							break;
						}
				}
			}

			function loadIfNotLoaded () {
				if (visible && !active) {
					active = true;
				}
			}
		}
	}
	ListModel {
		id: editorListModel
	}

	Action {
		id: increaseFontSize
		text: qsTr("Increase Font Size")
		shortcut: "Ctrl+="
		onTriggered: setFontSize(editorSettings.fontSize + 1)
	}

	Action {
		id: decreaseFontSize
		text: qsTr("Decrease Font Size")
		shortcut: "Ctrl+-"
		onTriggered: setFontSize(editorSettings.fontSize - 1)
	}

	Settings {
		id: editorSettings
		property int fontSize: 12;
	}
}