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

import {Entity, Scene, ScillaEvent} from "../core";
import {getRes} from "../assets-manager";

let entityCache = {};
let entityCacheConfig;
const defMap = {};
let prefabID: number = 0;

export function registerDef(name, def) {
	defMap[name] = def;
	def.__class__ = name;
}

function getEntityCacheConfig(config) {
	return config['entity-cache'] ? config['entity-cache'].concat() : [];
}

/**
 * 装配场景
 * @param scene
 * @param root
 */
export function setupScene(scene: Scene, root: Entity): Scene {
	scene.root = root;
	const config = scene.config;
	entityCacheConfig = getEntityCacheConfig(config);
	instantiateConfig(config.root, root);

	entityCache = {};

	return scene;
}

/**
 * 销毁场景
 * @param scene
 */
export function destroyScene(scene: Scene) {
	const root = scene.root;
	root.removeAllComponents();
	root.removeChildren();
}

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

	entityCacheConfig = getEntityCacheConfig(config);
	for (let i = 0, li = entityCacheConfig.length; i < li; i++) {
		entityCacheConfig[i] = pid + '_' + entityCacheConfig[i];
	}

	let rootConfig = config.root;
	const entity = setupEntity(rootConfig, null, pid);
	setupComponent(rootConfig, entity, true);
	injectComponent(rootConfig, entity, true, pid);

	entityCache = {};

	return entity.children[0];
}

/**
 * 装配树节点
 * @param config
 * @param root
 */
function instantiateConfig(config, root?: Entity): Entity {
	const entity = setupEntity(config, root);
	setupComponent(config, entity, true);
	injectComponent(config, entity, true);

	return entity;
}

/**
 * 装配实体
 * @param config
 * @param root
 * @param pid
 */
function setupEntity(config, root?: Entity, pid?): 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);

		if (entityCacheConfig.indexOf(uuid) >= 0) {
			entityCache[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 = 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?) {
	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?) {
	if (config.components) {
		const components = entity.components;
		for (let i = 0, li = config.components.length; i < li; i++) {
			const component = config.components[i];
			const {properties} = component;

			if (properties) {
				injectProperties(components[i], properties, pid);
			}
		}
	}
}

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

	let def = getDefByName(script);

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

	entity.addComponent(instance);

}

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

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

	return def;
}

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

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

function injectObject(propertyOfInstance, propertyOfConfig, pid?) {
	if (propertyOfInstance === undefined) {
		if (propertyOfConfig._type_) {
			let def = 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(node, key, propertyOfConfig, pid?) {
	let propertyValue;
	if (typeof propertyOfConfig === 'string') {
		propertyValue = getLink(propertyOfConfig, pid);
	} else {
		propertyValue = propertyOfConfig;
	}

	node[key] = 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?) {
	let result;
	if (str.indexOf('res|') == 0) { //res uuid
		const uuid = str.substr(4);
		result = getRes(uuid);
	} else if (str.indexOf('entity|') == 0) {  //entity uuid
		const uuid = transPrefabUUID(str.substr(7), pid);
		result = entityCache[uuid];
	} else {
		result = str;
	}
	return result;
}

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