<template>
	<el-dialog :title="$t('Project conflict resolver')" width="70%" :visible.sync="visible" @opened="onOpen"
	           @close="onClose"
	           :close-on-click-modal="false"
	           :append-to-body="true"
	           fullscreen
	           custom-class="flex-dialog code-conflict-resolve-editor"
	>
		<div class="wrapper">
			<el-steps :active="step" finish-status="success" align-center>
				<el-step v-for="(item, index) in steps" :title="item" :key="index"></el-step>
			</el-steps>
			<div class="container">
				<monaco-editor
					ref="diffEditor"
					class="editor"
					v-model="localData"
					language="json"
					:options="monacoEditorOptions"
					diff-editor
					:original="remoteData"
					v-show="step===0"
				/>
				<monaco-editor
					ref="editor"
					class="editor"
					v-model="localData"
					language="json"
					:options="monacoEditorOptions"
					v-show="step===1"
					@change="onChange"
				/>
			</div>
		</div>
		<div slot="footer" class="dialog-footer">
			<div>
				<el-button size="mini" @click="mergeConflicts" type="success" v-show="step===0">{{$t('Merge conflicts')}}
				</el-button>
				<el-button-group v-show="step===1">
					<el-button size="mini" @click="revealPreviousConflict(-1)" type="success">{{$t('Previous Conflict')}}
					</el-button>
					<el-button size="mini" @click="revealPreviousConflict(1)" type="success">{{$t('Next Conflict')}}</el-button>
				</el-button-group>
			</div>
			<div>
				<el-button size="mini" @click="onCancel">{{$t('Cancel')}}</el-button>
				<el-button size="mini" @click="onSave" type="primary" :disabled="step===0">{{$t('Confirm')}}</el-button>
			</div>
		</div>
	</el-dialog>
</template>

<script>
	import {mapMutations} from 'vuex';
	import MonacoEditor from "vue-monaco";
	import {formatJson, monacoEditorOptions} from "../../../utils";
	import SplitPanes from 'splitpanes'
	import i18n from "@/i18n";

	const startHeaderMarker = '<<<<<<< REMOTE';
	const splitterMarker = '=======';
	const endFooterMarker = '>>>>>>> LOCAL';
	const conflictMarkers = [startHeaderMarker, splitterMarker, endFooterMarker];
	const lensTitles = [i18n.t('Accept Remote'), i18n.t('Accept Local'), i18n.t('Accept Both')];
	const delayTrackDuration = 500;

	export default {
		name: "CodeConflictResolveDialog",
		components: {MonacoEditor, SplitPanes},
		data() {
			let monacoEditorOptionsSelf = Object.assign({
				readOnly: true,
			}, monacoEditorOptions);
			return {
				monacoEditorOptions: monacoEditorOptionsSelf,
				visible: false,
				step: 0,
				remoteData: '',
				localData: '',
				steps: this.$t('project-conflicts-resolve-steps'),
			}
		},
		mounted() {
			this.codeLensProviders = [];
			this.conflicts = [];
			this.conflictIndex = 0;
			this.decrations = [];
		},
		destroyed() {

		},
		methods: {
			show(remoteData, localData) {
				this.remoteData = '';
				this.localData = '';

				this.$nextTick(() => {
					this.remoteData = this.formatJson(remoteData || '');
					this.localData = this.formatJson(localData || '');

					this.step = 0;
					this.visible = true;
					this.monacoEditorOptions.readOnly = true;
					this.diff = null;

					this.$nextTick(() => {
						if (!this.inited) {
							this.inited = true;

							let diffEditor = this.$refs.diffEditor;
							let editor = diffEditor.getEditor();
							editor.onDidUpdateDiff(() => {
								if (!this.diff) {
									this.diff = editor.getLineChanges();
								}
							});

							let resolveEditorIns = this.$refs.editor.getEditor();
							this.mergeConflictCommand = resolveEditorIns.addCommand(0, (getService, action, conflict) => {
								this.resolveConflict(action, conflict);
							}, '');
						}
					});
				});
			},
			async onSave() {
				let resolved = true;
				for (let conflictFlag of conflictMarkers) {
					if (this.localData.includes(conflictFlag)) {
						resolved = false;
						break;
					}
				}
				if (resolved) {
					try {
						JSON.parse(this.localData);
						await this.$confirm(this.$t('The conflict has been resolved'), this.$t('Alert'), {
							confirmButtonText: this.$t('Confirm'),
							cancelButtonText: this.$t('Cancel'),
							type: 'warning'
						}).then(() => {
							this.step++;
							this.visible = false;
							this.$emit('resolved', this.localData);
						}).catch((e) => {
						});
					} catch (e) {
						this.$alert(this.$t('The format of the JSON document is wrong'), {
							type: 'warning'
						});
					}
				} else {
					this.$alert(this.$t('There are still unresolved conflicts'), {
						type: 'warning'
					});
				}
			},
			onCancel() {
				this.visible = false;
			},
			onClose() {
				this.removeAllCodeLensProviders();
				this.removeAllDecorations();
			},
			onOpen() {
			},
			formatJson(source) {
				return formatJson(source);
			},
			onChange(t) {
				if (this.timerTrack) {
					clearTimeout(this.timerTrack);
					this.timerTrack = null;
				}
				this.timerTrack = setTimeout(() => {
					this.delayTrack()
				}, delayTrackDuration);
			},
			removeAllCodeLensProviders() {
				while (this.codeLensProviders.length > 0) {
					let codeLensProvider = this.codeLensProviders.pop();
					codeLensProvider.dispose();
				}
			},
			removeAllDecorations() {
				let editor = this.$refs.editor;
				let resolveEditor = editor.getEditor();

				resolveEditor.deltaDecorations(this.decrations, []);
				this.decrations.splice(0);
			},
			delayTrack() {
				console.log('delayTrack');

				let lines = this.localData.split('\n');

				this.conflicts.splice(0);
				let currentConflict = null;
				let conflicts = this.conflicts;
				for (let i = 0, li = lines.length; i < li; i++) {
					const line = lines[i];
					if (!line) {
						continue;
					}

					if (line.startsWith(startHeaderMarker)) {
						if (currentConflict !== null) {
							currentConflict = null;
							break;
						}
						currentConflict = {remoteLine: i};
					} else if (currentConflict && !currentConflict.splitLine && line.startsWith(splitterMarker)) {
						currentConflict.splitLine = i;
					} else if (currentConflict && currentConflict.splitLine && line.startsWith(endFooterMarker)) {
						currentConflict.localLine = i;

						conflicts.push(currentConflict);

						currentConflict = null;
					}
				}

				this.removeAllCodeLensProviders();
				this.removeAllDecorations();

				for (let conflict of conflicts) {
					const {remoteLine, splitLine, localLine} = conflict;

					let line = remoteLine + 1;
					let range = {
						startLineNumber: line,
						startColumn: line,
						endLineNumber: line,
						endColumn: line
					};
					let id = line.toString();
					let lenses = [];
					for (let i = 0, li = lensTitles.length; i < li; i++) {
						const lensTitle = lensTitles[i];
						lenses.push({
							range,
							id,
							command: {
								id: this.mergeConflictCommand,
								title: lensTitle,
								arguments: [i, conflict],
							}
						})
					}

					let editor = this.$refs.editor;
					let resolveEditor = editor.getEditor();
					let codeLensProvider = editor.monaco.languages.registerCodeLensProvider('json', {
						provideCodeLenses: function (model, token) {
							return {
								lenses,
								dispose() {

								}
							};
						},
						resolveCodeLens: function (model, codeLens, token) {
							return codeLens;
						}
					});
					this.codeLensProviders.push(codeLensProvider);

					this.revealPreviousConflict();
					this.$nextTick(() => {
						let deltaDecorations = [
							{
								range: {
									startLineNumber: remoteLine + 1,
									endLineNumber: remoteLine + 1,
								},
								options: {isWholeLine: true, className: 'remote-change-marker-class'}
							},
						];
						if (splitLine - remoteLine > 1) {
							deltaDecorations.push({
								range: {
									startLineNumber: remoteLine + 2,
									endLineNumber: splitLine,
								},
								options: {isWholeLine: true, className: 'remote-change-class'}
							},)
						}
						if (localLine - splitLine > 1) {
							deltaDecorations.push({
								range: {
									startLineNumber: splitLine + 2,
									endLineNumber: localLine,
								},
								options: {isWholeLine: true, className: 'local-change-class'}
							},)
						}
						deltaDecorations.push({
							range: {
								startLineNumber: localLine + 1,
								endLineNumber: localLine + 1,
							},
							options: {isWholeLine: true, className: 'local-change-marker-class'}
						});
						let decorations = resolveEditor.deltaDecorations([], deltaDecorations);
						this.decrations.push(...decorations);
					});
				}
			},
			revealPreviousConflict(dir = 0) {
				this.conflictIndex += dir;
				if (this.conflictIndex < 0) {
					this.conflictIndex += this.conflicts.length;
				}
				this.conflictIndex = this.conflictIndex % this.conflicts.length;
				let conflict = this.conflicts[this.conflictIndex];
				let resolveEditorIns = this.$refs.editor.getEditor();
				resolveEditorIns.revealLineInCenter(conflict.remoteLine);
			},
			mergeConflicts() {
				this.monacoEditorOptions.readOnly = false;
				this.$nextTick(() => {
					this.step++;
				});

				let remoteCodeLines = this.remoteData.split('\n');
				let localCodeLines = this.localData.split('\n');
				let mergedCodeLines = localCodeLines.concat();

				//console.log(this.diff);
				let offset = 0;
				for (let {modifiedStartLineNumber, modifiedEndLineNumber, originalStartLineNumber, originalEndLineNumber} of this.diff) {
					let remotePart = [];
					for (let i = originalStartLineNumber, li = originalEndLineNumber; i <= li; i++) {
						remotePart.push(remoteCodeLines[i - 1]);
					}

					let localPart = [];
					for (let i = modifiedStartLineNumber, li = modifiedEndLineNumber; i <= li; i++) {
						localPart.push(localCodeLines[i - 1]);
					}

					let merged = [];
					merged.push(conflictMarkers[0]);
					merged.push(...remotePart);
					merged.push(conflictMarkers[1]);
					merged.push(...localPart);
					merged.push(conflictMarkers[2]);

					//console.log(merged);

					let startLine = modifiedStartLineNumber + offset + (modifiedEndLineNumber === 0 ? 1 : 0);
					mergedCodeLines.splice(startLine - 1, modifiedEndLineNumber - modifiedStartLineNumber + 1, ...merged);
					offset += 3 + remotePart.length;
				}

				this.localData = mergedCodeLines.join('\n');

				this.delayTrack();
			},
			resolveConflict(action, conflict) {
				let resolveEditorIns = this.$refs.editor.getEditor();
				let model = resolveEditorIns.getModel();

				let lines = this.localData.split('\n');
				let replacer;

				switch (action) {
					case 0:
						replacer = getTextWithRange(conflict.remoteLine, conflict.splitLine);
						break;
					case 1:
						replacer = getTextWithRange(conflict.splitLine, conflict.localLine);
						break;
					case 2:
						let remoteReplacer = getTextWithRange(conflict.remoteLine, conflict.splitLine);
						let localReplacer = getTextWithRange(conflict.splitLine, conflict.localLine);
						if (remoteReplacer === undefined) {
							if (localReplacer !== undefined) {
								replacer = localReplacer;
							}
						} else {
							if (localReplacer === undefined) {
								replacer = remoteReplacer;
							} else {
								replacer = remoteReplacer + '\n' + localReplacer;
							}
						}
						break;
				}
				/*if (replacer !== undefined) {
					replaceText(replacer);
					//this.localData = lines.join('\n');
				}*/
				replaceText(replacer);
				this.delayTrack();

				this.conflictIndex--;
				this.conflictIndex = Math.max(this.conflictIndex, 0);

				function getTextWithRange(start, end) {
					if (end - start > 1) {
						let resultLines = [];
						for (let i = start + 1; i < end; i++) {
							resultLines.push(lines[i]);
						}
						return resultLines.join('\n');
					}

					return null;
				}

				function replaceText(replacer) {
					let op = {
						range: {
							startLineNumber: conflict.remoteLine + 1,
							startColumn: 1,
							endLineNumber: conflict.localLine + 1,
							endColumn: Number.MAX_VALUE,
						},
						text: replacer,
					};
					if (replacer === null) {
						op.range.startLineNumber = conflict.remoteLine;
						op.range.startColumn = Number.MAX_VALUE;
					}
					model.pushEditOperations([], [op], () => []);
				}
			},
			...mapMutations([
				'modifyProjectDetails',
			]),
		}
	}
</script>

<style scoped>

</style>
