/**
 * Created by rockyl on 2018-12-03.
 *
 * 配置文件解释器
 */

import {Entity} from "../core/Entity";
import {ScillaEvent} from "../core/ScillaEvent";
import {EngineConfig} from "../engine-config";
import engine from "./ScillaEngine";
import {traverse} from "./utils";

let entityMap = {};
let prefabID: number = 0;
let _getResProxy: Function;

export function setGetResProxy(func: Function) {
	_getResProxy = func;
}

/**
 * 装配场景
 * @param config
 * @param root
 * @param getResProxy
 */
export function setupScene(config: any, root: Entity, getResProxy: Function) {
	setGetResProxy(getResProxy);

	instantiateConfig(config, root);
}

/**
 * 清空实体
 * @param entity
 */
export function cleanEntity(entity: Entity) {
	entity.removeAllComponents();
	entity.removeChildren();
}

/**
 * 装配预制体
 * @param config
 */
export function instantiate(config: any): Entity {
	let pid = ++prefabID;

	const entity = instantiateConfig(config, null, pid);

	return entity.children[0];
}

/**
 * 装配树实体
 * @param config
 * @param root
 * @param pid
 */
function instantiateConfig(config, root?: Entity, pid?: number): Entity {
	let rootConfig = config.root;

	const entity = setupEntity(rootConfig, root, pid);
	setupComponent(rootConfig, entity, true);
	injectComponent(rootConfig, entity, true, pid);

	traverse(entity, child => {
		child.active();

		return false;
	});

	entityMap = {};

	return entity;
}

/**
 * 装配实体
 * @param config
 * @param root
 * @param pid
 */
function setupEntity(config, root?: Entity, pid?: number): Entity {
	let entity: Entity = null;
	if (config) {
		let {name, uuid, children} = config;
		if (pid !== undefined && uuid !== undefined) {
			uuid = pid + '_' + uuid;
		}
		entity = root || new Entity(name, uuid);

		entityMap[uuid] = entity;

		if (children) {
			for (let i = 0, li = children.length; i < li; i++) {
				let child = children[i];

				const childEntity = setupEntity(child, null, pid);
				entity.addChild(childEntity);
			}
		}

		if (!root) {
			entity.enabled = !config.disabled;
		}
	}

	return entity;
}

/**
 * 装配组件
 * @param config
 * @param root
 * @param includeSelf
 */
function setupComponent(config, root: Entity, includeSelf: boolean = false) {
	if (includeSelf) {
		instantiateComponents(root, config);
	}
	if (config && config.children) {
		for (let i = 0, li = root.children.length; i < li; i++) {
			const child = config.children[i];
			const entity = root.children[i];

			instantiateComponents(entity, child);

			setupComponent(child, entity);
		}
	}
}

/**
 * 注入组件参数
 * @param config
 * @param root
 * @param includeSelf
 * @param pid
 */
function injectComponent(config, root: Entity, includeSelf = false, pid?: number) {
	if (includeSelf) {
		injectComponents(root, config, pid);
	}
	if (config && config.children) {
		for (let i = 0, li = root.children.length; i < li; i++) {
			const child = config.children[i];
			const entity = root.children[i];

			injectComponents(entity, child, pid);

			injectComponent(child, entity, false, pid);
		}
	}
}

/**
 * 实例化组件列表
 * @param entity
 * @param config
 */
function instantiateComponents(entity: Entity, config: any) {
	if (config.components) {
		for (const component of config.components) {
			instantiateComponent(entity, component);
		}
	}
}

/**
 * 注入组件列表参数
 * @param entity
 * @param config
 * @param pid
 */
function injectComponents(entity: Entity, config: any, pid?: number) {
	if (config.components) {
		const components = entity.components;
		for (let i = 0, li = config.components.length; i < li; i++) {
			const component = config.components[i];

			injectComponentProperties(components[i], component, pid);
		}
	}
}

/**
 * 注入组件属性
 * @param component
 * @param config
 * @param pid
 */
export function injectComponentProperties(component, config, pid?: number) {
	const {properties} = config;

	if (properties) {
		injectProperties(component, properties, pid);
	}
}

/**
 * 实例化组件
 * @param entity
 * @param config
 */
export function instantiateComponent(entity: Entity, config: any) {
	const {script,} = config;

	let def = engine.getDefByName(script);

	if (!def) {
		return;
	}
	const instance: any = new def();
	instance.enabled = !config.disabled;

	entity.addComponent(instance);

	return instance;
}

const skipKeys = ['_type_', '_constructor_'];

/**
 * 属性注入
 * @param entity
 * @param propertiesConfig
 * @param pid
 */
function injectProperties(entity, propertiesConfig, pid?: number) {
	if (!entity) {
		console.warn('entity is null.');
		return;
	}
	for (const key in propertiesConfig) {
		if (skipKeys.indexOf(key) >= 0) {
			continue;
		}
		const propertyOfConfig: any = propertiesConfig[key];
		let propertyOfInstance = entity[key];
		if (typeof propertyOfConfig === 'object') {
			if (propertyOfInstance instanceof ScillaEvent) {
				if (!EngineConfig.editorMode) {
					injectEvent(propertyOfInstance, propertyOfConfig, pid);
				}
			} else if (propertyOfConfig._type_ === 'raw') {
				entity[key] = propertyOfInstance = propertyOfConfig.data;
			} else {
				if (Array.isArray(propertyOfConfig) && !propertyOfInstance) {
					entity[key] = propertyOfInstance = []
				}
				entity[key] = injectObject(propertyOfInstance, propertyOfConfig, pid);
			}
		} else {
			injectBaseType(entity, key, propertyOfConfig, pid);
		}
	}
}

function injectObject(propertyOfInstance, propertyOfConfig, pid?: number) {
	if (propertyOfInstance === undefined) {
		if (propertyOfConfig._type_) {
			let def = engine.getDefByName(propertyOfConfig._type_);
			if (def) {
				let constructorArgs = propertyOfConfig._constructor_;
				if (constructorArgs && constructorArgs.length > 0) {
					propertyOfInstance = def.constructor.apply(null, constructorArgs);
				} else {
					propertyOfInstance = new def();
				}
			}
		}
	}
	if (propertyOfInstance) {
		injectProperties(propertyOfInstance, propertyOfConfig, pid);
	}
	return propertyOfInstance;
}

function injectBaseType(entity, key, propertyOfConfig, pid?: number) {
	let propertyValue;
	if (typeof propertyOfConfig === 'string') {
		propertyValue = getLink(propertyOfConfig, pid);
	} else {
		propertyValue = propertyOfConfig;
	}

	let keyAvatar = key;
	if (propertyValue instanceof Promise) {
		keyAvatar = 'async_' + keyAvatar;
	}

	if (typeof propertyValue === 'function') {
		Object.defineProperty(entity, keyAvatar, {
			get() {
				return this[`func_${keyAvatar}`](entity, engine.dataCenter)
			},
			set(v) {
				this[`func_${keyAvatar}`] = v;
			}
		})
	}
	entity[keyAvatar] = propertyValue;
}

function injectEvent(event: ScillaEvent, config, pid?) {
	for (const {entity: entityName, component: componentIndex, method: methodName, param} of config) {
		if (entityName && componentIndex >= 0 && methodName) {
			const entity = getLink(entityName, pid);
			const component = entity.components[componentIndex];
			const method = component[methodName];
			if (method) {
				if (param == undefined) {
					event.addListener(method, component, 0);
				} else {
					event.addListener(method, component, 0, param);
				}
			}
		}
	}
}

function getLink(str: string, pid?: number) {
	let result;
	if (str.indexOf('res|') == 0) { //res uuid
		const uuid = str.substr(4);
		result = _getResProxy(uuid);
	} else if (str.indexOf('entity|') == 0) {  //entity uuid
		const uuid = transPrefabUUID(str.substr(7), pid);
		result = entityMap[uuid];
	} else if (str.indexOf('dynamic|') == 0) { //dynamic
		const [_, type, expression] = str.split('|');
		result = () => {
			return engine.dataCenter.parse(type, expression)
		};
	} else if (str.indexOf('script|') == 0) { //script
		const script = str.substr(7);
		result = wrapperScript(script);
	} else {
		result = str;
	}
	return result;
}

function transPrefabUUID(uuid, pid: number) {
	return pid ? pid + '_' + uuid : uuid;
}

function wrapperScript(script){
	try {
		return new Function('component', 'dataCenter', script);
	}catch (e) {
		console.warn('script is correct :', script);
	}
}
