/**
 * Created by rockyl on 2018/11/8.
 *
 * 补间动画
 */

import {lerp, lerpObj} from "../tools/math";
import {injectProp} from "../tools/utils";
import HashObject from "../core/HashObject";

enum STATUS {
	IDLE,
	PENDING,
	DO_SET,
	DO_TO,
	DO_WAIT,
	DO_CALL,
}

/**
 * 补间动画插件接口
 */
export interface ITweenPlugin {
	/**
	 * 处理插值
	 * @param fromValue
	 * @param toValue
	 * @param ratio
	 * @param allowOutOfBounds
	 */
	resolveLerp(fromValue, toValue, ratio, allowOutOfBounds): any;
}

function injectChildProps(target, key, value) {
	if (typeof value === 'object') {
		injectProp(target[key], value)
	} else {
		target[key] = value;
	}
}

/**
 * 补间动画参数
 */
export interface TweenOptions {
	/**
	 * 循环次数 <0:无限循环,0:一次,>0:循环次数
	 * @default 0
	 */
	loop?: number;
	/**
	 * 是否自动播放
	 * @default true
	 */
	autoPlay?: boolean;
	/**
	 * 保存初始的字段名列表
	 */
	initFields?: string[];
	/**
	 * 支持对象展开的字段名列表
	 */
	fields?: string[];
	/**
	 * 一次循环完成回调
	 */
	onLoopComplete?: Function;
	/**
	 * 完成回调
	 */
	onComplete?: Function;
}

/**
 * 创建补间动画
 * @param host  拥有声明周期的组件
 * @param target  目标对象
 * @param override  是否覆盖该对象上的动画
 * @param options 补间动画参数
 * @param plugins 插件
 */
export function createTween(host: any, target: any, override = false, options?: TweenOptions, plugins: ITweenPlugin[] = []) {
	if (override) {
		killTweens(target);
	}

	const tween = new Tween(host, target, options);
	addTween(target, tween);

	return tween;
}

/**
 * 移除对象上所有的Tween实例
 * @param target
 */
export function killTweens(target: any) {
	let tweens: Tween[] = target['tweens'];
	if (tweens) {
		for (let tween of tweens) {
			tween.stop();
		}
		tweens.splice(0);
	}
}

function addTween(target, tween: Tween) {
	let tweens: Tween[] = target['tweens'];
	if (!tweens) {
		tweens = target['tweens'] = [];
	}

	tweens.push(tween);
}

/**
 * 补间动画类
 */
export class Tween extends HashObject {
	protected host: any;
	protected target: any;
	protected loop: number;
	protected _queue = [];

	protected loopCounting: number = 0;
	protected step: number;
	protected status: STATUS = STATUS.IDLE;

	protected t;
	protected startTime;

	protected plugins: ITweenPlugin[];
	protected fields;

	protected autoPlay: boolean;
	protected initProps: any;
	protected fromProps: any;
	protected toProps: any;
	protected ease: Function;
	protected duration: number;
	protected onLoopComplete: Function;
	protected onComplete: Function;
	protected promise: any;

	constructor(host: any, target: any, options?: TweenOptions, plugins = []) {
		super();

		this.host = host;
		this.target = target;
		this.loop = options ? options.loop : 0;
		this.autoPlay = options ? (options.hasOwnProperty('autoPlay') ? options.autoPlay : true) : true;
		this.fields = options ? options.fields : null;
		this.onLoopComplete = options ? options.onLoopComplete : null;
		this.onComplete = options ? options.onComplete : null;

		this.plugins = plugins;

		if (options && options.initFields && options.initFields.length > 0) {
			this.initProps = this.getInitProps(options.initFields);
		}
	}

	/**
	 * 获取动画链(高级功能)
	 */
	get queue() {
		return this._queue;
	}

	protected resolveLerp(fromValue, toValue, ratio) {
		let currentValue;
		if (this.plugins.length > 0) {
			for (let plugin of this.plugins) {
				currentValue = plugin.resolveLerp(fromValue, toValue, ratio, true);
			}
		} else {
			if (typeof toValue == 'object') {
				let {fields} = this;
				currentValue = lerpObj(fromValue, toValue, ratio, fields || Object.keys(toValue), true);
			} else {
				currentValue = lerp(fromValue, toValue, ratio, true);
			}
		}

		return currentValue;
	}

	protected onUpdate = (t) => {
		this.t = t;
		switch (this.status) {
			case STATUS.DO_TO:
				var {target, startTime, fromProps, toProps, duration, ease,} = this;
				var passTime = t - startTime;

				let timeRatio = Math.min(1, passTime / duration);
				let ratio = timeRatio;
				if (ease) {
					ratio = ease(ratio);
				}

				for (let key in fromProps) {
					const toValue = toProps[key];
					const fromValue = fromProps[key];

					let currentValue;
					if (timeRatio < 1) {
						currentValue = this.resolveLerp(fromValue, toValue, ratio);
					} else {
						currentValue = toValue;
					}

					if (typeof currentValue === 'object') {
						injectProp(target[key], currentValue);
					} else {
						target[key] = currentValue;
					}
				}

				if (timeRatio >= 1) {
					this._doNextAction();
				}
				break;
			case STATUS.DO_WAIT:
				var {startTime, duration} = this;
				var passTime = t - startTime;
				if (passTime > duration) {
					this._doNextAction();
				}
				break;
		}
	};

	protected getInitProps(fields) {
		const props = {};
		for (let field of fields) {
			if (field in this.target) {
				let value = this.target[field];
				if (typeof value === 'object') {
					if (value.clone) {
						value = value.clone();
					} else {
						injectProp(props[field] = {}, this.target[field]);
					}
				}
				props[field] = value;
			}
		}

		return props;
	}

	/**
	 * 设置目标的属性
	 * @param props 属性对象
	 * @param immediately 是否立刻执行，而不是在下一帧
	 */
	set(props, immediately = false) {
		this._queue.push({action: 'set', props});
		if (this.autoPlay) {
			this._start();
		}

		if (immediately) {
			this._set(props, immediately);
		}

		return this;
	}

	/**
	 * 进行一次补间动画
	 * @param props 属性对象
	 * @param duration 耗时(ms)
	 * @param ease 缓动方法
	 */
	to(props, duration?: number, ease?: Function) {
		this._queue.push({action: 'to', props, duration, ease});
		if (this.autoPlay) {
			this._start();
		}

		return this;
	}

	/**
	 * 等待一段时间
	 * @param duration 耗时(ms)
	 */
	wait(duration) {
		this._queue.push({action: 'wait', duration});
		if (this.autoPlay) {
			this._start();
		}

		return this;
	}

	/**
	 * 执行一个方法
	 * @param func 方法体
	 * @param thisObj 闭包域
	 * @param params 参数
	 */
	call(func: Function, thisObj?, params?) {
		this._queue.push({action: 'call', func, thisObj, params});
		if (this.autoPlay) {
			this._start();
		}

		return this;
	}

	/**
	 * 播放补间动画
	 * @param override 是否覆盖该对象上的动画
	 * @param delay 延迟执行时间(ms)
	 * @param resetLoopCounting 是否重置循环次数计数
	 */
	play(override = false, delay: number = 0, resetLoopCounting: boolean = true) {
		if (override) {
			killTweens(this.target);
		}

		if (delay > 0) {
			setTimeout(this._doPlay, delay, resetLoopCounting)
		} else {
			this._doPlay(resetLoopCounting);
		}
	}

	/**
	 * 终止补间动画
	 */
	stop() {
		this.status = STATUS.IDLE;

		this.host.cancelOnNextTick(this.onUpdate);
	}

	/**
	 * 返回一个Promise
	 */
	getPromise() {
		return new Promise(resolve => {
			this.promise = {resolve};
		})
	}

	private _doPlay = (resetLoopCounting) => {
		addTween(this.target, this);
		this._start(resetLoopCounting);
	};

	protected _set(props, immediately = false) {
		injectProp(this.target, props, injectChildProps);
		if (!immediately) {
			this.status = STATUS.DO_SET;
			this._doNextAction();
		}
	}

	protected _to(props, duration, ease) {
		this.status = STATUS.DO_TO;
		this.startTime = this.t;

		this.fromProps = {};
		for (let key in props) {
			let value = this.target[key];
			if (typeof value === 'object') {
				if (value.clone) {
					value = value.clone();
				} else {
					injectProp(props[key] = {}, this.target[key]);
				}
			}
			this.fromProps[key] = value;
		}
		this.toProps = {};
		injectProp(this.toProps, props);
		this.ease = ease;
		this.duration = duration;
		//this.tween = annie.Tween.to(this.target, (duration / 1000) || 0, _props)
	}

	protected _wait(duration) {
		this.status = STATUS.DO_WAIT;
		this.startTime = this.t;

		this.duration = duration;
		/*setTimeout(() => {
			this._doNextAction();
		}, duration)*/
	}

	protected _call(func, thisObj, params) {
		this.status = STATUS.DO_CALL;
		func.apply(thisObj || this.host, params);
		this._doNextAction();
	}

	protected _start(resetLoopCounting: boolean = true) {
		this.status = STATUS.PENDING;
		if (resetLoopCounting) {
			this.loopCounting = 0;
		}
		this.host.callOnNextTick(this._readyStart);

		this.host.callOnNextTick(this.onUpdate, false);
	}

	protected _readyStart = (t) => {
		this.t = t;
		this._doStart();
	};

	protected _doStart() {
		if (this.status == STATUS.IDLE) {
			return;
		}

		this.step = 0;
		this.loopCounting++;
		if (this.loopCounting > 1 && this.initProps) {
			injectProp(this.target, this.initProps, injectChildProps);
		}
		setTimeout(this._doNextAction);
	};

	protected _doNextAction = () => {
		if (this.step < this._queue.length) {
			let action = this._queue[this.step++];
			switch (action.action) {
				case 'set':
					this._set(action.props);
					break;
				case 'to':
					if (action.duration > 0) {
						this._to(action.props, action.duration, action.ease);
					} else {
						this._set(action.props);
					}
					break;
				case 'wait':
					this._wait(action.duration);
					break;
				case 'call':
					this._call(action.func, action.thisObj, action.params);
					break;
			}
		} else {
			this._onEnd();
		}
	};

	protected _onEnd() {
		this.onLoopComplete && this.onLoopComplete();
		if (this.loop < 0) {
			this._doStart();
		} else if (this.loopCounting < this.loop) {
			this._doStart();
		} else {
			this.status = STATUS.IDLE;
			this.onComplete && this.onComplete();

			this.promise && this.promise.resolve();
			this.promise = null;
		}
	}
}