/**
 * Created by rockyl on 2018/11/5.
 *
 * 波动组件
 */

import {raw, ScillaEvent, decorators, utils} from "scilla";
import ScillaComponent from "../base/ScillaComponent";

const PI2 = Math.PI * 2;
const {dirtyFieldTrigger} = decorators;
const oldPropFields = {
	position: ['x', 'y'],
	scale: ['x', 'y'],
	alpha: 'alpha',
	rotation: 'rotation',
};

export default class Wave extends ScillaComponent {
	duration: number = 1000;
	@dirtyFieldTrigger
	waveMethod: WaveMethod;
	waveParams: raw;
	loop: number = -1;
	autoPlay: boolean = true;

	onComplete: ScillaEvent = new ScillaEvent();
	onLoopComplete: ScillaEvent = new ScillaEvent();

	private _playing;
	private _waveAlgorithm;
	private _startTime;
	private _oldProps: any = {};
	private _loopCounting;

	get waveAlgorithm(){
		return this._waveAlgorithm;
	}
	set waveAlgorithm(v: Function){
		this._waveAlgorithm = v;
	}

	protected onModify(value, key, oldValue) {
		super.onModify(value, key, oldValue);

		switch(key){
			case 'waveMethod':
				this._waveAlgorithm = waveLibs[this.waveMethod];
				break;
		}
	}

	onAwake() {
		super.onAwake();

		this._startTime = 0;

		utils.copyProp(this._oldProps, this.transform, oldPropFields);

		if (this.autoPlay) {
			this.play();
		}
	}

	onUpdate(t) {
		super.onUpdate(t);

		if (this._playing) {
			if (!this._startTime) {
				this._startTime = t;
			}

			const {duration, waveParams, _waveAlgorithm, transform, transform: {position, scale}, _oldProps} = this;

			let pass = (t - this._startTime) % duration;
			let r = pass / duration * PI2;

			let loopCounting = Math.floor((t - this._startTime) / duration);
			if(loopCounting != this._loopCounting){
				this._loopCounting = loopCounting;
				if(this.onLoopEnd()){
					r = PI2;
				}
			}

			let params = waveParams || [];
			let props = _waveAlgorithm(...params, r);

			if (props.hasOwnProperty('x')) {
				position.x = (props.x || 0) + _oldProps.position.x;
			}
			if (props.hasOwnProperty('y')) {
				position.y = (props.y || 0) + _oldProps.position.y;
			}
			if (props.hasOwnProperty('sx')) {
				scale.x = props.sx;
			}
			if (props.hasOwnProperty('sy')) {
				scale.y = props.sy;
			}
			if (props.hasOwnProperty('r')) {
				transform.rotation = props.r;
			}
		}
	}

	private onLoopEnd(){
		if (this.loop < 0) {
			this.onLoopComplete.invoke();
		} else if (this._loopCounting < this.loop) {
			this.onLoopComplete.invoke();
		} else {
			this._playing = false;
			this.onComplete.invoke();
			return true;
		}
	}

	play() {
		this._loopCounting = 0;
		this._playing = true;
		this._startTime = 0;
	}

	/**
	 * 停止动画
	 * @param revert 是否恢复初始状态
	 */
	stop(revert = false) {
		this._playing = false;
		if(revert){
			for(let key in this._oldProps){
				let prop = this._oldProps[key];
				if(typeof prop === 'object'){
					utils.injectProp(this.transform[key], prop);
				}else{
					this.transform[key] = prop;
				}
			}
		}
	}
}

export enum WaveMethod {
	/**
	 * 公转
	 */
	round = 'round',
	/**
	 * 自转
	 */
	rotate = 'rotate',
	/**
	 * 缩放
	 */
	zoom = 'zoom',
	/**
	 * 透明渐变
	 */
	fade = 'fade',
	/**
	 * 横向波动
	 */
	cosWave = 'cosWave',
	/**
	 * 纵向波动
	 */
	sinWave = 'sinWave',
	/**
	 * 抖动
	 */
	shake = 'shake',
	/**
	 * 呼吸
	 */
	breath = 'breath',
}

const {cos, sin, PI} = Math;

const waveLibs = {
	round: function (radius: number, t: number): any {
		return {x: cos(t) * radius, y: sin(t) * radius};
	},

	cosWave: function (h: number, t: number): any {
		return {x: cos(t) * h, y: 0};
	},

	sinWave: function (h: number, t: number): any {
		h = h || 1;
		return {x: 0, y: sin(t) * h};
	},

	rotate: function (t: number): any {
		return {r: 360 * t / PI / 2};
	},

	shake: function (angle: number, count: number, t: number): any {
		return {r: sin(t * count) * angle};
	},

	breath: function (scale: number = 0.1, t: number): any {
		return {sx: sin(t) * scale + 1, sy: -sin(t + PI / 4) * scale + 1};
	},

	zoom: function (scale: number = 0.1, t: number): any {
		return {sx: sin(t) * scale + 1, sy: sin(t) * scale + 1};
	},

	fade: function (base = 1, t: number): any {
		return {alpha: (sin(t) + 1) * 0.5 + base};
	},
};

