/**
 * Created by rockyl on 2018/11/8.
 *
 */

import {ScillaComponent} from "../core";
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 {
	//resolveLerp(fromValue, toValue, ratio, allowOutOfBounds):any;
}

export interface TweenOptions {
	loop?: number;
	autoPlay?: boolean;
	initFields?: string[];
	clazz?: any;
	fields?: string[];
}

/**
 * 补间动画
 * @param target
 * @param override
 * @param options
 * @param plugins
 */
export function createTween(target: ScillaComponent, override = false, options?: TweenOptions, plugins = []) {
	if (override) {
		killTweens(target);
	}

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

	return tween;
}

/**
 * 移除对象上所有的Tween实例
 * @param target
 */
export function killTweens(target: ScillaComponent) {
	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 {
	target: ScillaComponent;
	loop: number;
	queue = [];

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

	t;
	startTime;

	plugins: ITweenPlugin[];
	clazz;
	fields;

	autoPlay: boolean;
	initProps: any;
	fromProps: any;
	toProps: any;
	ease: Function;
	duration: number;

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

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

		this.plugins = plugins;

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

	onUpdate = (t) => {
		this.t = t;
		switch (this.status) {
			case STATUS.DO_TO:
				var {target, startTime, fromProps, toProps, duration, ease, clazz, fields} = 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) {
						if (typeof toValue == 'object') {
							currentValue = lerpObj(fromValue, toValue, ratio, clazz, fields || Object.keys(toValue), true);
						} else {
							currentValue = lerp(fromValue, toValue, ratio, true);
						}
					} else {
						currentValue = toValue;
					}

					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;
		}
	};

	private getInitProps(fields) {
		const props = {};
		for (let field of fields) {
			if (field in this.target) {
				props[field] = this.target[field];
			}
		}

		return props;
	}

	set(props) {
		this.queue.push({action: 'set', props});
		if (this.autoPlay) {
			this._start();
		}

		return this;
	}

	to(props, duration?, ease?) {
		this.queue.push({action: 'to', props, duration, ease});
		if (this.autoPlay) {
			this._start();
		}

		return this;
	}

	wait(duration) {
		this.queue.push({action: 'wait', duration});
		if (this.autoPlay) {
			this._start();
		}

		return this;
	}

	call(func, thisObj?, params?) {
		this.queue.push({action: 'call', func, thisObj, params});
		if (this.autoPlay) {
			this._start();
		}

		return this;
	}

	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);
		}
	}

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

	stop() {
		this.status = STATUS.IDLE;

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

	_set(props) {
		this.status = STATUS.DO_SET;
		injectProp(this.target, props);
		this._doNextAction();
	}

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

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

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

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

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

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

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

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

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

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

	_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 {
			if (this.loop < 0) {
				this._doStart();
			} else if (this.loopCounting < this.loop) {
				this._doStart();
			} else {
				this.status = STATUS.IDLE;
			}
		}
	}
}