<template>
	<div class="board" @dragover="onDragOver" @drop="onDrop" @paste="onPaste" v-resize="onResize">
		<div style="height: 1px;"></div>
		<!--<el-scrollbar class="full-size">-->
		<svg ref="board" class="svg-board full-size" version="1.1" xmlns="http://www.w3.org/2000/svg"
		     xmlns:xlink="http://www.w3.org/1999/xlink" @mousedown="onBoardMouseDown">
			<g ref="borderWrapper" :style="boardStyle">
				<g id="layer" stroke-width="2" fill="none" fill-rule="evenodd">
					<link-line v-for="(line, key, index) in lines" :data="line" :key="index" @dblclick="onDeleteLine"></link-line>
					<path v-show="lineDrawing.visible" class="line hover" :d="lineDrawing.path"></path>
				</g>
				<g id="nodes">
					<process-node v-for="(process, key, index) of subProcessMap" :ref="'pn_' + key" :process="process"
					              :key="index"
					              @click="selectProcessNode(process, key)"
					              @hover-pin="onPinHover"
					              @leave-pin="onPinLeave"
					              @down-pin="onPinDown"
					              @delete="onProcessNodeDelete"
					              @copy="onProcessNodeCopy"
					              @change-position="onProcessNodeMove"
					              @edit-meta="onEditMeta"
					              @dblclick="editSubProcess(process)"
					              @meta-modified="onProcessMetaModified"
					              @transform="onClickTransform"
					/>
				</g>
			</g>
		</svg>
		<!--</el-scrollbar>-->
		<tool-tip ref="toolTip"/>
	</div>
</template>

<script>
	//:style="boardStyle"
	import {mapState, mapMutations, mapGetters, mapActions} from 'vuex'
	import ProcessNode from "./Board/ProcessNode";
	import Process from "./Board/Process";
	import LinkLine from "./Board/LinkLine";
	import ToolTip from "./Board/ToolTip";
	import {DOCK_POINT_OFFSET} from "../../../config";
	import events from "../../../global-events";
	import generateUUID from "uuid/v4";
	import copy from 'copy-to-clipboard'
	import {clonePureObj} from "../../../utils";

	const customs = ['custom', 'divider'];

	export default {
		name: "Board",
		components: {ToolTip, LinkLine, ProcessNode,},
		props: [],
		data() {
			return {
				process: null,
				subProcessMap: null,
				selectedProcessNode: null,
				lines: {},
				lineDrawing: {
					visible: false,
					path: '',
					process: null,
				},
				boardOffset: {
					x: 0,
					y: 0,
				}
			}
		},
		mounted() {

		},
		computed: {
			...mapState({
				drawState: state => state.behavior.drawState,
				editable: state => state.behavior.editable,
				scale: state => state.behavior.drawState.boardScale,
			}),
			boardStyle() {
				return {
					transform: `scale(${this.scale}) translate(${this.boardOffset.x}px, ${this.boardOffset.y}px)`
				}
			},
		},
		methods: {
			...mapActions([
				'addCustomProcessMeta',
				'addProcessFromPrefab',
			]),
			...mapMutations([
				'deleteProcessMeta',
				'setScale',
				'makeBehaviorDirty',
			]),
			async edit(process, resolveProcess, lastProcess) {
				//this.lastProcess = this.process;

				this.boardOffset.x = 0;
				this.boardOffset.y = 0;
				this.setScale(0);
				this.selectedProcessNode = null;
				this.process = process;
				this.resolveProcess = resolveProcess;

				if (!this.process.meta.subEntry) {
					const subProcessData = await this.addSubProcessData('entry', {x: 10, y: 10,});
					this.$set(this.process.meta, 'subEntry', subProcessData.uuid);
				}

				this.updateSubProcess();
				this.measure();

				this.$nextTick(function(){
					this.selectProcessNode(lastProcess);
				});
			},
			updateSubProcess() {
				this.subProcessMap = {};
				for (let uuid in this.process.meta.sub) {
					const subData = this.process.meta.sub[uuid];
					this.addSubProcess(uuid, subData);
				}
			},
			addSubProcess(uuid, data) {
				const process = new Process(this.process, data, this.resolveProcess);
				if (process.meta) {
					this.$set(this.subProcessMap, uuid, process);
				} else {
					console.log('节点丢失:', uuid);
				}
			},
			showInlineChoose() {
				return this.$confirm(this.$t('As inline'), {
					distinguishCancelAndClose: true,
				});
			},
			async addSubProcessData(processId, pos) {
				let meta, isInline;

				if (customs.includes(processId)) {
					let name;
					if (processId === 'custom') {//自定义过程必须输入名称
						let result = await this.$prompt(this.$t('Input custom process name'), this.$t('Alert'), {
							confirmButtonText: this.$t('Confirm'),
							cancelButtonText: this.$t('Cancel'),
							inputPattern: /^.{1,64}$/,
							inputErrorMessage: this.$t('Invalid name'),
						}).catch(e => {
						});
						if (result) {
							name = result.value;
						}
						if (!name) {
							return;
						}
					}
					try {
						await this.showInlineChoose();
						isInline = true;
					} catch (action) {
						if (action === 'close') {
							return;
						}
					}

					meta = await this.addCustomProcessMeta({masterProcess: this.process, isInline, processId, name});
				} else {
					meta = this.process.resolveMeta(processId);
				}

				if (meta.isPrefab) {
					try {
						await this.showInlineChoose();
						isInline = true;
					} catch (action) {
						if (action === 'close') {
							return;
						}
					}
					meta = await this.addProcessFromPrefab({masterProcess: this.process, isInline, meta})
				}

				let data = {
					uuid: generateUUID(),
					meta: meta.id,
					design: {
						x: pos.x,
						y: pos.y,
					},
				};
				if (!this.process.meta.hasOwnProperty('sub')) {
					this.$set(this.process.meta, 'sub', {});
				}
				this.$set(this.process.meta.sub, data.uuid, data);
				return data;
			},
			onDragOver(e) {
				let {types} = e.dataTransfer;
				if (types.includes('process')) {
					e.preventDefault();
				}
			},
			async onDrop(e) {
				if(!this.editable){
					return;
				}

				const processId = e.dataTransfer.getData('process');
				if (processId === 'entry') {
					return;
				}

				const data = await this.addSubProcessData(processId, {
					x: e.offsetX - this.boardOffset.x - 9,
					y: e.offsetY - this.boardOffset.y - 20,
				});
				if (!data) {
					return;
				}
				this.addSubProcess(data.uuid, data);
				this.makeBehaviorDirty();

				this.$nextTick(() => {
					events.$emit('update-dock-pin-pos');
				});
			},
			onPaste(e) {
				let dataStr = e.clipboardData.getData("process-data");
				if (dataStr) {
					this.addProcessFromCopy(dataStr);
				}
			},
			addProcessFromCopy(avatar) {
				if (typeof avatar === 'string') {
					avatar = JSON.parse(avatar);
				}

				avatar.uuid = generateUUID();
				avatar.design.x += 20;
				avatar.design.y += 20;
				avatar.output = {};
				delete avatar.design['output'];
				delete avatar.design['input'];
				this.$set(this.process.meta.sub, avatar.uuid, avatar);

				this.addSubProcess(avatar.uuid, avatar);
				this.$nextTick(() => {
					events.$emit('update-dock-pin-pos');
				});
				this.makeBehaviorDirty();
			},
			onResize() {
				const {x, y} = this.$el.getBoundingClientRect();
				this.drawState.boardOffset.x = x + this.boardOffset.x;
				this.drawState.boardOffset.y = y + this.boardOffset.y;
			},
			measure() {
				this.onResize();

				this.$nextTick(() => {
					events.$emit('update-dock-pin-pos');
					this.updateLines();
				});
			},
			updateLines() {
				this.lines = {};
				for (let uuid in this.subProcessMap) {
					const process = this.subProcessMap[uuid];

					const {output} = process.data;
					for (let outputType in output) {
						const outputGroup = output[outputType];
						for (let i = 0, li = outputGroup.length; i < li; i++) {
							const outputID = outputGroup[i];
							this.addLine(process.data, outputID, outputType, i);
						}
					}
				}
			},
			addLine(process, outputID, outputType, outputIndex) {
				const nextProcess = this.subProcessMap[outputID];
				if (nextProcess) {
					this.$set(this.lines, this.drawState.lineID, {
						id: this.drawState.lineID,
						prev: process,
						next: nextProcess.data,
						outputType,
            outputIndex,
					});
					this.drawState.lineID++;
				} else {
					for (let uuid in this.subProcessMap) {
						const process = this.subProcessMap[uuid];
						for (let key in process.data.output) {
							let item = process.data.output[key];
							let index = item.indexOf(outputID);
							if (index >= 0) {
								item.splice(index, 1);
							}
						}
					}
				}
			},
			onPinHover(x, y, pin) {
				this.$refs.toolTip.show(x + 10, y - 8, pin);
			},
			onPinLeave(x, y, pin) {
				this.$refs.toolTip.hide();
			},
			onPinDown(e, process, pin) {
				document.addEventListener("mousemove", this.onMouseMove);
				document.addEventListener("mouseup", this.onMouseUp);

				this.processDrawing = process;
				this.pointDrawing = pin;
				const {x, y} = process.design;
				const startPos = process.design.output[pin][0];
				this.drawingLineStart = `M${startPos.x + x - DOCK_POINT_OFFSET},${startPos.y + y + DOCK_POINT_OFFSET} C${startPos.x + x + 100},${startPos.y + y} `;
				this.lineDrawing.visible = true;
				this.drawState.drawing = true;
				this.onMouseMove(e);
			},
			onMouseMove(e) {
				const scale = this.scale;
				let x = e.x - this.drawState.boardOffset.x;
				let y = e.y - this.drawState.boardOffset.y;
				this.lineDrawing.path = this.drawingLineStart + `${x / scale},${y / scale} ${x / scale},${y / scale}`;
			},
			onMouseUp(e) {
				document.removeEventListener("mousemove", this.onMouseMove);
				document.removeEventListener("mouseup", this.onMouseUp);

				this.lineDrawing.visible = false;
				this.drawState.drawing = false;

				if (this.drawState.targetUUID && this.drawState.targetUUID !== this.processDrawing.uuid) {
					this.processDrawing.output[this.pointDrawing] = [this.drawState.targetUUID];
					this.addLine(this.processDrawing, this.drawState.targetUUID, this.pointDrawing, 0);

					this.drawState.targetUUID = null;
					this.makeBehaviorDirty();
				}
			},
			onDeleteLine(line) {
				const {prev, outputType, outputIndex, id} = line;
				prev.output[outputType].splice(outputIndex, 1);
				this.$delete(this.lines, id);
				this.makeBehaviorDirty();
			},
			onProcessNodeDelete(process, meta) {
				this.$delete(this.subProcessMap, process.uuid);
				this.$delete(this.process.meta.sub, process.uuid);

				let remain = 0;
				for (let key in this.process.meta.sub) {
					let p = this.process.meta.sub[key];
					if (p.meta === meta.id) {
						remain++;
					}
				}

				for (let id in this.lines) {
					const line = this.lines[id];
					const {prev, next} = line;

					if (prev === process || next === process) {
						this.onDeleteLine(line);
					}
				}

				if (meta.isDivider || meta.isInline) { //如果是分流节点或者内联节点还要删除对应的meta
					if (remain === 0) {
						this.deleteProcessMeta({
							meta,
							process: this.process,
						});
					}
				}
				this.makeBehaviorDirty();
			},
			onProcessNodeCopy(data, meta) {
				if (meta.isInline) {  //内联直接复制
					let avatar = clonePureObj(data);

					this.addProcessFromCopy(avatar);
				} else {
					copy(JSON.stringify(data), {format: 'process-data'});
					this.$message({
						message: this.$t('Copied process to clipboard'),
						type: 'success',
						duration: 700,
					});
				}
			},
			onProcessNodeMove(data) {
				this.makeBehaviorDirty();
			},
			onEditMeta(meta, parentMeta) {
				this.$emit('edit-meta', meta, parentMeta);
			},
			onClickTransform(process) {
				this.$emit('transform', process, this.process.meta);
			},
			editSubProcess(process) {
				if (!process.meta.isDivider && this.editable && (process.meta.type !== 'builtin' || process.meta.sub && Object.keys(process.meta.sub).length > 0)) {
					this.$emit('edit-process', process);
				}
			},
			onProcessMetaModified(process, meta) {
				let ids = Object.keys(this.lines);
				for (let id of ids) {
					const line = this.lines[id];
					const {prev,} = line;

					if (prev === process) {
						const {outputType} = line;
						if (meta.output.indexOf(outputType) < 0) {
							this.onDeleteLine(line);
						}
					}
				}
				this.makeBehaviorDirty();
			},
			selectProcessNode(process) {
				if(!process){
					return;
				}
				for (let key in this.$refs) {
					if (key.startsWith('pn_')) {
						const processNode = this.$refs[key][0];

						if (processNode) {
							let hint = 'pn_' + process.uuid === key;
              processNode.setActive(hint);
							if (hint && this.selectedProcessNode !== processNode) {
                // 相关联的线点亮
                this.setContactLine(process);
								this.selectedProcessNode = processNode;
								this.$emit('select-process-node', process);
							}
						}
					}
				}
      },
      setContactLine(process) {
        let preArr = [];
        let _fetchNode = (_uuid, type) => {
          Object.keys(this.lines).filter(_sliceKey => !preArr.includes(_sliceKey)).map(key => {
            let itemLine = this.lines[key];
            if(type === 'pre') {
              if(itemLine.next && itemLine.next.uuid === _uuid) {
                preArr.push(key)
                if(itemLine.prev && itemLine.prev.uuid) {
                  _fetchNode(itemLine.prev.uuid, 'pre')
                }
              }
            } else if(type === 'next'){
                if(itemLine.prev && itemLine.prev.uuid === _uuid) {
                preArr.push(key)
                if(itemLine.next && itemLine.next.uuid) {
                  _fetchNode(itemLine.next.uuid, 'next')
                }
              }
            }
          })
        }
    
        _fetchNode(process._data.uuid, 'pre');
        _fetchNode(process._data.uuid, 'next');
        Object.keys(this.lines).map(key => {
          this.$set(this.lines, key, { ...this.lines[key], onContact: preArr.includes(key)})
        })
      },
			updateProcessNode(metaID) {
				this.$nextTick(() => {

					for (let key in this.$refs) {
						if (key.startsWith('pn_')) {
							const processNode = this.$refs[key][0];

							if (processNode && processNode.meta.id === metaID) {
								this.onProcessMetaModified(processNode.data, processNode.meta);
								processNode.updateSize();
								processNode.updateDockPointPos();
							}
						}
					}
				});
			},
			onBoardMouseDown(e) {
				if (e.target !== this.$refs.board) {
					return;
				}
				document.addEventListener("mousemove", this.onBoardMouseMove);
				document.addEventListener("mouseup", this.onBoardMouseUp);

				const {screenX, screenY} = e;
				this.boardDragDownPos = {x: screenX, y: screenY};
				this.boardDragStartPos = {
					x: this.boardOffset.x,
					y: this.boardOffset.y,
				}
			},
			onBoardMouseMove(e) {
				if (!this.boardDragDownPos) {
					return;
				}
				const {x, y} = this.boardDragDownPos;
				const {screenX, screenY} = e;
				const offset = {
					x: screenX - x,
					y: screenY - y,
				};

				this.boardOffset.x = this.boardDragStartPos.x + offset.x / this.scale;
				this.boardOffset.y = this.boardDragStartPos.y + offset.y / this.scale;
				//console.log(this.boardOffset.x, this.boardOffset.y);
			},
			onBoardMouseUp(e) {
				if (!this.boardDragDownPos) {
					return;
				}

				this.onResize();

				this.boardDragDownPos = null;
			},
		}
	}
</script>

<style scoped>

</style>