// yeffects.js: yzwlab effectsライブラリ(要jQuery)

/**
* エラー処理。
*/
function yError(e)
{
	alert(e);
}


/**
* 待機コンテナです。
*/
function YWait(duration)
{
	if(duration == undefined) {
		throw new Error(this.ILLEGAL_ARGUMENTS + [duration].toString());
	}
	if(duration < 0) {
		throw new Error(this.ILLEGAL_ARGUMENTS + [duration].toString());
	}
	
	/**
	* 待機時間を保持します。
	*/
	this._duration = duration;
	
	/**
	* 終了したかどうかを保持します。
	*/
	this._finished = false;
}

/**
* 不正な引数を示すメッセージです。
*/
YWait.prototype.ILLEGAL_ARGUMENTS = "不正な引数です: ";

/**
* コピーを生成します。
*/
YWait.prototype.copy = function()
{
	return new YWait(this._duration);
};

/**
* 完了したかどうかを判定します。
*/
YWait.prototype.isFinished = function()
{
	return this._finished;
};

/**
* 開始します。
*/
YWait.prototype.start = function()
{
	;
};

/**
* 処理を続行します。
*/
YWait.prototype.process = function(proceed)
{
	if(proceed >= this._duration) {
		this._finished = true;
	}
};


/**
* 並列コンテナです。
*/
function YPar(elements)
{
	if(elements == undefined) {
		throw new Error(this.ILLEGAL_ARGUMENTS + [elements].toString());
	}
	if(elements.length == 0) {
		throw new Error(this.ILLEGAL_ARGUMENTS + [elements].toString());
	}
	
	/**
	* 実行する要素を保持します。
	*/
	this._elements = elements;
}

/**
* 不正な引数を示すメッセージです。
*/
YPar.prototype.ILLEGAL_ARGUMENTS = "不正な引数です: ";

/**
* コピーを生成します。
*/
YPar.prototype.copy = function()
{
	var copiedElements = new Array();
	for(var i = 0; i < this._elements.length; i ++) {
		copiedElements.push(this._elements[i].copy());
	}
	return new YPar(copiedElements);
};

/**
* 完了したかどうかを判定します。
*/
YPar.prototype.isFinished = function()
{
	for(var i = 0; i < this._elements.length; i ++) {
		var elem = this._elements[i];
		if(elem.isFinished() == false) {
			return false;
		}
	}
	return true;
};

/**
* 開始します。
*/
YPar.prototype.start = function()
{
	for(var i = 0; i < this._elements.length; i ++) {
		var elem = this._elements[i];
		elem.start();
	}
};

/**
* 処理を続行します。
*/
YPar.prototype.process = function(proceed)
{
	for(var i = 0; i < this._elements.length; i ++) {
		var elem = this._elements[i];
		if(elem.isFinished() == false) {
			elem.process(proceed);
		}
	}
};


/**
* 順次コンテナです。
*/
function YSeq(elements)
{
	if(elements == undefined) {
		throw new Error(this.ILLEGAL_ARGUMENTS + [elements].toString());
	}
	if(elements.length == 0) {
		throw new Error(this.ILLEGAL_ARGUMENTS + [elements].toString());
	}
	
	/**
	* 実行する要素を保持します。
	*/
	this._elements = elements;
	
	/**
	* 現在の要素を保持します。
	*/
	this._current = -1;
	
	/**
	* 最後に実行した開始時間を保持します。
	*/
	this._lastOffset = 0;
}

/**
* 不正な引数を示すメッセージです。
*/
YSeq.prototype.ILLEGAL_ARGUMENTS = "不正な引数です: ";

/**
* コピーを生成します。
*/
YSeq.prototype.copy = function()
{
	var copiedElements = new Array();
	for(var i = 0; i < this._elements.length; i ++) {
		copiedElements.push(this._elements[i].copy());
	}
	return new YSeq(copiedElements);
};

/**
* 完了したかどうかを判定します。
*/
YSeq.prototype.isFinished = function()
{
	if(this._current >= this._elements.length) {
		return true;
	}
	if(this._current < this._elements.length - 1) {
		return false;
	}
	var elem = this._elements[this._current];
	return elem.isFinished();
};

/**
* 開始します。
*/
YSeq.prototype.start = function()
{
	this._start(0);
};

/**
* 処理を続行します。
*/
YSeq.prototype.process = function(proceed)
{
	if(this._current >= this._elements.length) {
		return;
	}
	
	var elem = this._elements[this._current];
	if(elem.isFinished() == false) {
		elem.process(proceed - this._lastOffset);
	}else{
		// 次へ
		this._start(proceed);
	}
};

/**
* 処理を開始します。
*/
YSeq.prototype._start = function(offset)
{
	this._current ++;
	if(this._current >= this._elements.length) {
		return;
	}
	var elem = this._elements[this._current];
	elem.start();
	this._lastOffset = offset;
};


/**
* アニメーション要素です。
*/
function YAnimate(selector, name, values, duration)
{
	if(selector == undefined || name == undefined || values == undefined || duration == undefined) {
		throw new Error(this.ILLEGAL_ARGUMENTS + [selector,name,values,duration].toString());
	}
	if(duration < 0) {
		throw new Error(this.ILLEGAL_ARGUMENTS + [selector,name,values,duration].toString());
	}
	if(values.length == 0) {
		throw new Error(this.ILLEGAL_VALUES + values.toString());
	}
	
	/**
	* 対象要素を決定するためのセレクタを保持します。
	*/
	this._selector = new LazyQuery(selector);
	
	/**
	* 持続時間を保持します。
	*/
	this._duration = duration;
	
	/**
	* プロパティ名を保持します。
	*/
	this._name = name;
	
	/**
	* 値リストを保持します。
	*/
	this._values = values;
	
	/**
	* 完了したかどうかを保持します。
	*/
	this._finished = false;
}

/**
* 不正なエフェクタを示すメッセージです。
*/
YAnimate.prototype.EFFECTOR_NOT_FOUND = "エフェクタが見つかりません: ";

/**
* 不正な引数を示すメッセージです。
*/
YAnimate.prototype.ILLEGAL_ARGUMENTS = "不正な引数です: ";

/**
* 不正な値を示すメッセージです。
*/
YAnimate.prototype.ILLEGAL_VALUES = "不正な値です: ";

/**
* 値の反映方法を定義します。
*/
YAnimate.prototype.EFFECTORS = {
		"background-color": function(target, name, value) {
			target.css(name, formatColor(value));
		},
		width: function(target, name, value) {
			target.css(name, value + "px");
		},
		height: function(target, name, value) {
			target.css(name, value + "px");
		},
		left: function(target, name, value) {
			target.css(name, value + "px");
		},
		top: function(target, name, value) {
			target.css(name, value + "px");
		},
		visibility: function(target, name, value) {
			target.css(name, value);
		},
		alpha: function(target, name, value) {
			target.css("filter", "alpha(opacity=" + value + ")");
			target.css("-moz-opacity", value/100);
			target.css("opacity", value/100);
		}
	};

/**
* コピーを生成します。
*/
YAnimate.prototype.copy = function()
{
	return new YAnimate(this._selector._query, this._name, this._values, this._duration);
};

/**
* 完了したかどうかを判定します。
*/
YAnimate.prototype.isFinished = function()
{
	return this._finished;
};

/**
* 開始します。
*/
YAnimate.prototype.start = function()
{
	this._setValue(this._values[0]);
};

/**
* 処理を続行します。
*/
YAnimate.prototype.process = function(proceed)
{
	var value = this._calculate(proceed);
	
	this._setValue(value);
	
	if(proceed >= this._duration) {
		this._finished = true;
	}
};

/**
* 値を設定します。
*/
YAnimate.prototype._setValue = function(value)
{
	var target = this._selector.getObject();
	
	var effector = this.EFFECTORS[this._name];
	if(effector == undefined) {
		throw new Error(this.EFFECTOR_NOT_FOUND + this._name);
	}
	effector(target, this._name, value);
};

/**
* 値を計算します。
*/
YAnimate.prototype._calculate = function(proceed)
{
	if(this._values.length == 1) {
		return this._values[0];
	}
	if(this._duration == 0) {
		return this._values[0];
	}
	if(proceed > this._duration) {
		proceed = this._duration;
	}
	
	var segs = parseInt(this._duration / (this._values.length - 1));
	var index = parseInt(proceed / segs);
	if(index >= this._values.length - 1) {
		return this._values[this._values.length - 1];
	}
	
	var v1 = this._values[index];
	var v2 = this._values[index + 1];
	
	var offset = proceed - segs * index;
	var perc = offset / segs;
	
	return this._interpolate(v1, v2, perc);
};

/**
* 値を補間します。
*/
YAnimate.prototype._interpolate = function(v1, v2, perc)
{
	if(typeof(v1) != typeof(v2)) {
		throw new Error(this.ILLEGAL_VALUES + [v1,v2].toString());
	}
	if(typeof(v1) == 'string') {
		// 文字列の場合
		if(perc < 0.5) {
			return v1;
		}
		return v2;
	}
	if(typeof(v1) == 'number') {
		// 数値の場合
		var diff = v2 - v1;
		return (diff * perc) + v1;
	}
	if(typeof(v1) == 'object' &&
	   v1.length != undefined) {
		// 配列?の場合
		if(v1.length != v2.length) {
			throw new Error(this.ILLEGAL_VALUES + [v1,v2].toString());
		}
		var arr = new Array();
		for(var i = 0; i < v1.length; i ++) {
			arr.push(this._interpolate(v1[i], v2[i], perc));
		}
		return arr;
	}
	throw new Error(this.ILLEGAL_VALUES + [v1,v2].toString());
};


/**
* Callbackコンテナです。
*/
function YCallback(func)
{
	if(func == undefined) {
		throw new Error(this.ILLEGAL_ARGUMENTS + [func].toString());
	}
	
	/**
	* Callbackを保持します。
	*/
	this._func = func;
	
	/**
	* 終了したかどうかを保持します。
	*/
	this._finished = false;
}

/**
* 不正な引数を示すメッセージです。
*/
YCallback.prototype.ILLEGAL_ARGUMENTS = "不正な引数です: ";

/**
* コピーを生成します。
*/
YCallback.prototype.copy = function()
{
	return new YCallback(this._func);
};

/**
* 完了したかどうかを判定します。
*/
YCallback.prototype.isFinished = function()
{
	return this._finished;
};

/**
* 開始します。
*/
YCallback.prototype.start = function()
{
};

/**
* 処理を続行します。
*/
YCallback.prototype.process = function(proceed)
{
	if(this._finished == false) {
		this._func(proceed);
	}
	this._finished = true;
};


/**
* 色定義を作成します。
*/
function formatColor(arr)
{
	var result = "#" + formatHex(arr[0]) + formatHex(arr[1]) + formatHex(arr[2]);
	return result;
}

/**
* 16進文字列を作成します。
*/
function formatHex(value)
{
	var vs = [(value >> 4)&0xf, value&0xf];
	var result = "";
	
	for(var i = 0; i < vs.length; i ++) {
		var v = vs[i];
		var charcode = 0;
		if(v < 10) {
			charcode = v + 48; // '0'
		}else{
			charcode = v + 55; // 'a' - 10
		}
		result += String.fromCharCode(charcode);
	}
	
	return result;
}


/**
* タイミング処理を実行するエンジンです。
*/
function YEngine(element)
{
	if(element == undefined) {
		throw new Error(this.ILLEGAL_ARGUMENTS + [element].toString());
	}
	
	/**
	* 要素を保持します。
	*/
	this._element = element;
	
	/**
	* 現在の要素を保持します。
	*/
	this._activeElement = undefined;
	
	/**
	* 開始時間を保持します。
	*/
	this._beginTime = 0;
}

/**
* アニメーションの呼び出し間隔です。
*/
YEngine.prototype.INTERVAL = 10;

/**
* 不正な状態を示すメッセージです。
*/
YEngine.prototype.ILLEGAL_STATE = "不正な状態です。";

/**
* 不正な引数を示すメッセージです。
*/
YEngine.prototype.ILLEGAL_ARGUMENTS = "不正な引数です: ";

/**
* 処理を開始します。
*/
YEngine.prototype.start = function()
{
	if(this._activeElement != undefined) {
		throw new Error(this.ILLEGAL_STATE);
	}
	
	// コピーして開始
	this._activeElement = this._element.copy();
	
	var date = new Date();
	this._beginTime = date.getTime();
	this._activeElement.start();
	
	this._setTimer();
};

/**
* 処理を継続します。
*/
YEngine.prototype.process = function()
{
	var date = new Date();
	var proceed = date.getTime() - this._beginTime;
	
	this._activeElement.process(proceed);
	
	if(this._activeElement.isFinished()) {
		this._clear();
	}else{
		this._setTimer();
	}
};

/**
* 処理中かどうかを判定します。
*/
YEngine.prototype.isRunning = function()
{
	if(this._activeElement == undefined) {
		return false;
	}
	if(this._activeElement.isFinished()) {
		return false;
	}
	return true;
};

/**
* 後始末します。
*/
YEngine.prototype._clear = function()
{
	this._activeElement = undefined;
};

/**
* タイマーを設定します。
*/
YEngine.prototype._setTimer = function()
{
	var self = this;
	setTimeout(function() {
		try{
			self.process();
		}catch(e){
			yError(e);
		}
		}, this.INTERVAL);
};


/**
* 遅延評価Queryです。
*/
function LazyQuery(query)
{
	if(query == undefined) {
		throw new Error(this.ILLEGAL_ARGUMENTS + [query].toString());
	}
	
	/**
	* Queryを保持します。
	*/
	this._query = query;
	
	/**
	* Queryの実体を保持します。
	*/
	this._cache = undefined;
	
}

/**
* 不正な引数を示すメッセージです。
*/
LazyQuery.prototype.ILLEGAL_ARGUMENTS = "不正な引数です: ";

/**
* Queryの実体を取得します。
*/
LazyQuery.prototype.getObject = function()
{
	if(this._cache == undefined) {
		this._cache = $(this._query);
	}
	
	return this._cache;
};


