/**
 * Created by rockyl on 2018/11/23.
 *
 * 引擎
 */

import {Entity, RootEntity} from "./Entity";
import {injectProp} from "../tools/utils";
import InteractContext from "./context/InteractContext";
import RenderContext, {ScaleMode} from "./context/RenderContext";
import './requestAnimationFrame';
import {AssetsManager} from "../assets-manager";
import {traverse, traversePostorder} from "./utils";
import DataCenter from "../support/DataCenter";

/**
 * 引擎类
 */
export class ScillaEngine {
	/**
	 * 默认配置
	 */
	readonly engineConfig: any = {
		fps: 60,
		designWidth: 750,
		designHeight: 1334,
		scaleMode: ScaleMode.FIXED_WIDTH,
		touchEnabled: true,
	};
	/**
	 * 自定义配置
	 */
	readonly customConfig: any = {};
	/**
	 * 数据中心配置
	 */
	readonly dataCenterConfig: any = {};

	private _root: Entity;
	private _canvasElement: HTMLCanvasElement;

	private _flush = 0;
	private _currentFlush = 0;
	private tsStart;
	private tsLast;
	private lastFPS = 0;
	private tickId;

	private _renderContext: RenderContext;
	private _interactContext: InteractContext;
	private _assetsManager: AssetsManager;
	private _dataCenter: DataCenter;

	private nextTicks = [];

	private defMap = {};
	/**
	 * 注册组件
	 * @param name
	 * @param def
	 */
	registerDef(name: string, def) {
		this.defMap[name] = def;
		def.__class__ = name;
	}

	/**
	 * 注册组件
	 * @param name
	 */
	unregisterDef(name: string) {
		delete this.defMap[name];
	}

	/**
	 * 根据名称获取定义
	 * @param name
	 * @param showWarn
	 */
	getDefByName(name: string, showWarn: boolean = true): any {
		let def;
		/*if (name.indexOf('/') >= 0) {//addition
			name = name.substr(name.lastIndexOf('/') + 1);
		}*/

		def = this.defMap[name];
		if (!def && showWarn) {
			console.warn('missing def:', name);
			return;
		}

		return def;
	}

	constructor() {
		this._assetsManager = new AssetsManager();
		this._dataCenter = new DataCenter();
	}

	/**
	 * 装配引擎
	 * @param _engineConfig
	 * @param _customConfig
	 * @param _dataCenterConfig
	 */
	setup(_engineConfig?, _customConfig?, _dataCenterConfig?) {
		injectProp(this.engineConfig, _engineConfig);
		injectProp(this.customConfig, _customConfig);
		injectProp(this.dataCenterConfig, _dataCenterConfig);

		if (this.dataCenterConfig.dataCenterRoot) {
			for (let item of this.dataCenterConfig.dataCenterRoot) {
				this._dataCenter.register(item);
			}
		}

		const {canvas, designWidth, designHeight, scaleMode, modifyCanvasSize, touchEnabled} = this.engineConfig;

		this._canvasElement = typeof canvas == 'string' ? document.getElementById(canvas) : canvas;

		this._interactContext = new InteractContext(this);
		this._interactContext.setup({
			canvas: this._canvasElement,
			touchHandlers: {
				onTouchBegin: this.onTouchBegin.bind(this),
				onTouchMove: this.onTouchMove.bind(this),
				onTouchEnd: this.onTouchEnd.bind(this),
			},
			touchEnabled,
		});

		this._renderContext = new RenderContext(this);
		this._renderContext.setup({
			canvas: this._canvasElement,
			designWidth,
			designHeight,
			scaleMode,
			modifyCanvasSize,
			onUpdateScale: this.onUpdateScale.bind(this),
		});

		this._root = new RootEntity(this);
		this._root._restrict();
	}

	/**
	 * 开始引擎
	 */
	start() {
		this._root.enabled = true;

		this.tsStart = -1;
		this.startTick();
	}

	/**
	 * 暂停引擎
	 */
	pause() {
		this._root.enabled = false;

		this.stopTick();
	}

	/**
	 * 获取根Entity
	 */
	get root(): Entity {
		return this._root;
	}

	/**
	 * 获取实体路径
	 * @param entity
	 */
	getEntityPath(entity?: Entity): string {
		let path = '';
		let current = entity || this._root;
		while (current.parent) {
			path = current.parent.children.indexOf(current) + (path.length > 0 ? '|' : '') + path;
			current = current.parent;
		}
		return path;
	}

	/**
	 * 根据实体路径获取实体
	 * @param path
	 */
	getEntityByPath(path?: string): Entity {
		let target = this._root;

		if (path.length > 0) {
			let arr = path.split('|');
			for (let item of arr) {
				target = target.children[item];
				if (!target) {
					target = null;
					break;
				}
			}
		}
		return target;
	}

	/**
	 * 获取当前帧率
	 */
	getFPS() {
		return this.lastFPS;
	}

	/**
	 * 获取渲染上下文
	 */
	get renderContext(): RenderContext {
		return this._renderContext;
	}

	/**
	 * 获取交互上下文
	 */
	get interactContext(): InteractContext {
		return this._interactContext;
	}

	/**
	 * 获取资源管理
	 */
	get assetsManager(): AssetsManager {
		return this._assetsManager;
	}

	get canvasElement(): HTMLCanvasElement {
		return this._canvasElement;
	}

	/**
	 * 获取数据中心实例
	 */
	get dataCenter(): DataCenter {
		return this._dataCenter;
	}

	private onUpdateScale(scaleX, scaleY, rotation) {
		this._interactContext.updateScale(scaleX, scaleY, rotation)
	}

	/**
	 * 开始时钟
	 */
	private startTick() {
		this._flush = 60 / this.engineConfig.fps - 1 >> 0;
		if (this._flush < 0) {
			this._flush = 0;
		}

		this.tickId = requestAnimationFrame(this.flush);
	}

	/**
	 * 停止时钟
	 */
	private stopTick() {
		cancelAnimationFrame(this.tickId);
	}

	/**
	 * 时钟触发
	 */
	private flush = (tsNow): void => {
		if (this._flush == 0) {
			this.onFrameTick(tsNow);
		} else {
			if (this._currentFlush == 0) {
				this.onFrameTick(tsNow);
				this._currentFlush = this._flush;
			} else {
				this._currentFlush--;
			}
		}

		this.tickId = requestAnimationFrame(this.flush);
	}

	/**
	 * 下一帧执行
	 * @param func
	 * @param tickCount
	 */
	nextTick(func, tickCount = 1) {
		this.nextTicks.push({func, tickCount});
	}

	private onFrameTick(tsNow) {
		if(this.tsStart < 0){
			this.tsStart = tsNow;
		}

		this._renderContext.clear();
		this.lastFPS = Math.floor(1000 / (tsNow - this.tsLast));
		this.tsLast = tsNow;
		const ts = tsNow - this.tsStart;
		traverse(this._root, function (child) {
			if (!child.isFree && child.enabled) {
				child.$onUpdate(ts);
			} else {
				return true;
			}
		}, -1, true, function (current) {
			current.$afterUpdate();
		});

		for (let i = 0, li = this.nextTicks.length; i < li; i++) {
			const item = this.nextTicks[i];

			item.tickCount--;
			if (item.tickCount <= 0) {
				item.func(ts);

				this.nextTicks.splice(i, 1);
				i--;
				li--;
			}
		}
	}

	/**
	 * 代理出来的onTouchBegin方法
	 * @param event
	 */
	private onTouchBegin(event) {
		traversePostorder(this._root, function (child) {
			return child.onInteract(0, event);
		})
	}

	/**
	 * 代理出来的onTouchMove方法
	 * @param event
	 */
	private onTouchMove(event) {
		traversePostorder(this._root, function (child) {
			return child.onInteract(1, event);
		})
	}

	/**
	 * 代理出来的onTouchEnd方法
	 * @param event
	 */
	private onTouchEnd(event) {
		traversePostorder(this._root, function (child) {
			return child.onInteract(2, event);
		})
	}

	/**
	 * 射线测试获取实体
	 * 注：暂时只用在编辑器内
	 */
	getEntitiesByRayTest(x, y, ignoreMask = true) {
		const entities = [];
		const localPos: any = {};
		let rendererDef = this.getDefByName('components/renderer/Renderer');
		traversePostorder(this._root, function (child) {
			const transform = child.components[0];
			const matrix = transform['getMatrix'](true, true, true);
			matrix.transformPoint(x, y, localPos);

			const renderers = child.getComponents(rendererDef);
			let result = false;
			for (let renderer of renderers) {
				if ((ignoreMask || !renderer['isUsedToMask']) && renderer['hitTest'](localPos.x, localPos.y)) {
					result = true;
					break
				}
			}
			if (result) {
				entities.push(child);
			}
			return false;
		});

		return entities;
	}

	/**
	 * 页面坐标转画布坐标
	 * @param pageX
	 * @param pageY
	 * @param identifier
	 * @param isLocalPos
	 */
	pagePosToCanvasPos(pageX, pageY, identifier?, isLocalPos: boolean = false) {
		const {scaleX, scaleY, rotation} = this.renderContext;

		let x = pageX, y = pageY;
		let box = this._canvasElement.getBoundingClientRect();
		if (!isLocalPos) {
			let doc = document.documentElement;
			let left = box.left + window.pageXOffset - doc.clientLeft;
			let top = box.top + window.pageYOffset - doc.clientTop;
			x = pageX - left;
			y = pageY - top;
		}

		let newX = x;
		let newY = y;

		if (rotation === 90) {
			newX = y;
			newY = box.width - x;
		} else if (rotation === -90) {
			newX = box.height - y;
			newY = x;
		}
		newX = newX / scaleX;
		newY = newY / scaleY;
		return {
			x: Math.round(newX),
			y: Math.round(newY),
			identifier: identifier || 0,
		};
	}

	/**
	 * 画布坐标转页面坐标
	 * @param x
	 * @param y
	 * @param isLocalPos
	 */
	canvasPosToPagePos(x, y, isLocalPos: boolean = true) {
		const {scaleX, scaleY, rotation} = this.renderContext;

		x = x * scaleX;
		y = y * scaleY;

		if (!isLocalPos) {
			let box = this._canvasElement.getBoundingClientRect();
			let doc = document.documentElement;
			let left = box.left + window.pageXOffset - doc.clientLeft;
			let top = box.top + window.pageYOffset - doc.clientTop;
			x += left;
			y += top;
		}

		return {
			x, y,
		}
	}
}

const engine: ScillaEngine = new ScillaEngine();
export default engine;
