/**
 * MikuMikuDance VMD形式の再生制御
 *
 * @author b2ox
 */
package org.b2ox.pv3d.MikuMikuDance
{
	import org.b2ox.betweenas3.FuncTween;
	import org.b2ox.pv3d.MikuMikuDance.*;
	import org.libspark.betweenas3.BetweenAS3;
	import org.libspark.betweenas3.tweens.ITween;

	/**
	 * VMDクラスで読み込んだVMDデータの格納＆再生制御
	 */
	public class VMDController implements IVMDController
	{
		private var _pmdController:PMDController;
		private var _frameLength:int = 0;
		private var _currentFrame:int = 0;

		private var _tween:ITween;
		private var _counterTween:ITween;
		private var _updaterTween:ITween;

		//-------------------------------------------------
		// ボーンパラメータ関連
		private var _boneNames:Vector.<String> = new Vector.<String>();
		private var _boneParamSeq:Vector.<VMDBoneParamSequence> = new Vector.<VMDBoneParamSequence>();
		public function get boneParamSequence():Vector.<VMDBoneParamSequence> { return _boneParamSeq; }
		private function getBoneParamSequenceByName(boneName:String):VMDBoneParamSequence
		{
			var i:int = _boneNames.indexOf(boneName);
			if (i < 0) { // ボーンが未登録状態で呼ばれた場合は新規登録する
				_boneNames.push(boneName);
				_boneParamSeq.push(new VMDBoneParamSequence(boneName));
				i = _boneNames.length - 1;
			}
			return _boneParamSeq[i];
		}
		private var _skipBone:Object = {};

		//-------------------------------------------------
		// スキンパラメータ関連
		private var _skinNames:Vector.<String> = new Vector.<String>();
		private var _skinParamSeq:Vector.<VMDSkinParamSequence> = new Vector.<VMDSkinParamSequence>();
		public function get skinParamSequence():Vector.<VMDSkinParamSequence> { return _skinParamSeq; }
		private function getSkinParamSequenceByName(skinName:String):VMDSkinParamSequence
		{
			var i:int = _skinNames.indexOf(skinName);
			if (i < 0) { // スキンが未登録状態で呼ばれた場合は新規登録する
				_skinNames.push(skinName);
				_skinParamSeq.push(new VMDSkinParamSequence(skinName));
				i = _skinNames.length - 1;
			}
			return _skinParamSeq[i];
		}
		private var _skipSkin:Object = {};

		//-------------------------------------------------
		/**
		 * 再生時のモデル変形用のPMDControllerを引数に取る
		 * @param	ctr
		 */
		public function VMDController(ctr:PMDController):void
		{
			_pmdController = ctr;
		}

		//-------------------------------------------------
		// 再生用のAPI

		public function get tween():ITween { return _tween; }
		public function get counterTween():ITween { return _counterTween; }
		public function get updaterTween():ITween { return _updaterTween; }

		public function get looping():Boolean { return _tween ? ! _tween.stopOnComplete : false; }
		public function set looping(loop:Boolean):void {
			if (_tween) {
				_tween.stopOnComplete = ! loop;
				_counterTween.stopOnComplete = ! loop;
				_updaterTween.stopOnComplete = ! loop;
			}
		}

		public function play():void { if (_tween) _tween.play(); }
		public function stop():void { if (_tween) _tween.stop(); }
		public function togglePause():void { if (_tween) _tween.togglePause(); }
		public function gotoAndPlay(pos:Number):void { if (_tween) _tween.gotoAndPlay(pos); }
		public function gotoAndStop(pos:Number):void { if (_tween) _tween.gotoAndStop(pos); }

		public function get playing():Boolean { return _tween ? _tween.isPlaying : false; }

		public function update():void
		{
			resetParams();
			applyCurrent();
			applyPose();
		}

		public function resetParams():void { _pmdController.resetParams(); }
		public function applyPose():void { _pmdController.update(); }

		/**
		 * 現在フレームのパラメータを適用する
		 */
		public function applyCurrent():void { applyFrame(_currentFrame); }

		//-------------------------------------------------
		/**
		 * 指定フレームのパラメータを適用する
		 * @param	frame
		 */
		public function applyFrame(frame:int):void
		{
			frame = (frame < 0) ? 0 : frame;
			frame = (frame >= _frameLength) ? _frameLength - 1 : frame;

			for each (var sp:VMDSkinParamSequence in _skinParamSeq) _pmdController.setSkinWeight(sp.skinName, sp.getSkinParam(frame));

			for each (var bp:VMDBoneParamSequence in _boneParamSeq) {
				var param:VMDBoneParam = bp.getBoneParam(frame);
				_pmdController.setVMDBoneParam(bp.boneName, param.rotation, param.move);
			}
		}

		//-------------------------------------------------
		/**
		 * 総フレーム数を取得
		 */
		public function get frameLength():int { return _frameLength; }

		/**
		 * 現在のフレーム番号
		 */
		public function get frameNo():int { return _currentFrame; }
		public function set frameNo(frame:int):void
		{
			_currentFrame = frame < 0 ? 0 : (frame % _frameLength);
		}

		//-------------------------------------------------

		/**
		 * ボーンパラメータの追加
		 * @param	boneName
		 * @param	frame
		 * @param	boneParam
		 */
		public function addBoneParam(boneName:String, frame:int, boneParam:VMDBoneParam):void
		{
			if (! _pmdController.hasBone(boneName))
			{
				// モデルに存在しないボーンのパラメータは無視する
				if (_skipBone[boneName] != true) // 何度も同じ表示をしないための工夫
				{
					trace( "存在しないボーン: " + boneName );
					_skipBone[boneName] = true;
				}
				return;
			}
			if (_frameLength <= frame) _frameLength = frame + 1;
			getBoneParamSequenceByName(boneName).addBoneParam(frame, boneParam);
		}

		/**
		 * 表情パラメータの追加
		 * @param	skinName
		 * @param	frame
		 * @param	weight
		 */
		public function addSkinParam(skinName:String, frame:int, weight:Number):void
		{
			if (skinName == "base") return; // baseはスキップする
			if (! _pmdController.hasSkin(skinName))
			{
				// モデルに存在しない表情のパラメータは無視する
				if (_skipSkin[skinName] != true) // 何度も同じ表示をしないための工夫
				{
					trace( "存在しない表情: " + skinName );
					_skipSkin[skinName] = true;
				}
				return;
			}
			if (_frameLength <= frame) _frameLength = frame + 1;
			getSkinParamSequenceByName(skinName).addSkinParam(frame, weight);
		}

		/**
		 * キーフレームの登録完了処理
		 */
		public function fixFrameParams():void
		{
			trace("総フレーム数: " + _frameLength);

			for each (var bp:VMDBoneParamSequence in _boneParamSeq) bp.fixSequence(_frameLength);
			for each (var sp:VMDSkinParamSequence in _skinParamSeq) sp.fixSequence(_frameLength);

			makeInnerTweens();
			trace("キーフレームの登録完了");
		}

		private function makeInnerTweens():void
		{
			_counterTween = BetweenAS3.tween(this, { frameNo: _frameLength - 1 }, { frameNo: 0 }, _frameLength / 30);
			_updaterTween = FuncTween.create(function (time:Number):void { applyCurrent(); }, _counterTween);

			var _reseterTween:FuncTween = FuncTween.create(function (time:Number):void { resetParams(); }, _counterTween);
			var _applyerTween:FuncTween = FuncTween.create(function (time:Number):void { applyPose(); }, _counterTween);

			_tween = BetweenAS3.parallel(_reseterTween, _counterTween, _updaterTween, _applyerTween);
			looping = false;
		}

		/**
		 * ボーンパラメータなどは共有したコピーを作る
		 * @return
		 */
		public function clone():VMDController
		{
			var vmd:VMDController = new VMDController(_pmdController);
			vmd._boneNames = _boneNames;
			vmd._boneParamSeq = _boneParamSeq;
			vmd._frameLength = _frameLength;
			vmd._skinNames = _skinNames;
			vmd._skinParamSeq = _skinParamSeq;
			vmd._skipBone = _skipBone;
			vmd._skipSkin = _skipSkin;
			vmd.makeInnerTweens();

			return vmd;
		}
	}
}