package vocaloplus.controllers
{
	import flash.errors.IOError;
	import flash.events.EventDispatcher;
	import flash.media.SoundTransform;
	
	import mx.events.ModuleEvent;
	
	import vocaloplus.models.SoundHolder;
	

	/**
	 * Vocaloplusアプリケーションが音声を再生するために必要な機能を提供するクラス。
	 * 
	 * @author shiraminekeisuke(MineAP)
	 * 
	 */
	public class SoundController
	{
		/**
		 * SoundControllerの唯一のインスタンス
		 */
		private static const _soundController:SoundController = new SoundController();
		
		/**
		 * マスター音量。初期値は1.0。
		 */
		private var _masterVol:Number = 1.0;
		
		/**
		 * フェードアウトまでの時間。初期値は1(=1000ミリ)秒。
		 */
		private var _fadeOutTime:Number = 1000;
		
		/**
		 * Voice
		 */
//		private var _voice:Voice = null;	// TODO ボイス管理は未実装
		
		/**
		 * BGMを格納する配列
		 */
		private var _bgms:Vector.<BGM> = new Vector.<BGM>();
		
		/**
		 * コンストラクタ<br />
		 * SoundControllerはシングルトンです。
		 * 必ず SoundController#instance() を使ってインスタンスを取得してください。
		 */
		public function SoundController()
		{
			if(_soundController != null){
				throw new ArgumentError("SoundControllerはインスタンス化出来ません");
			}
		}
		
		/**
		 * 
		 * @return 唯一のSoundCountrollerのインスタンス
		 * 
		 */
		public static function get instance():SoundController{
			return _soundController;
		}
		
		/* ----- ボイス再生機能 ----- */
		
		/**
		 * ボイス再生機能<br />
		 * 指定されたモジュール名に対応するボイスを再生します。
		 * @param moduleName モジュール名
		 * @param vol 音量。0.0-1.0の間で指定する。
		 * 
		 */
		public function playVoice(moduleName:String, vol:Number = 1.0):void{
			// TODO 未実装
		}
		
		/**
		 * ボイス再生機能<br />
		 * 再生中のボイスを停止します。
		 * @param fadeOut ボイス停止時にフェードアウトするかどうか。trueを指定するとフェードアウトする。
		 * 
		 */
		public function stopVoice(fadeOut:Boolean = false):void{
			// TODO 未実装
		}
		
		/**
		 * ボイス再生機能<br />
		 * 再生するボイスの音量を変更します。
		 * @param vol 変更する音量。0.0-1.0の間で指定する。
		 * 
		 */
		public function setVoiceVolume(vol:Number):void{
			// TODO 未実装
		}
		
		/* ----- BGM再生機能 ----- */
		
		/**
		 * BGM再生機能<br />
		 * BGMデータをBGM再生機能に登録します。
		 * 既に設定されているBGMのnumを指定した場合、当該BGMは上書きされます。
		 * 
		 * @param num 登録するBGM番号。0-9までの整数で指定する。
		 * @param moduleName BGM番号に指定するBGMのモジュール名
		 * 
		 */
		public function setBGM(num:int, moduleName:String):void{
			
			if(num >= 0 && num < 10){
				
				// モジュールのロード
				var soundHolder:SoundHolder = ModuleController.loadSoundByName(moduleName);
				soundHolder.addEventListener(ModuleEvent.READY, bgmReadyEventHandler);
				
				// モジュールのロード完了通知を受けた際の動作
				function bgmReadyEventHandler(event:ModuleEvent):void{
					var soundHolder:SoundHolder = SoundHolder(event.currentTarget);
					soundHolder.removeEventListener(ModuleEvent.READY, bgmReadyEventHandler);
					
					// モジュールを管理オブジェクトに格納して保持
					_bgms[num] = new BGM(soundHolder.module);
				}
				
			}
			
		}
		
		/**
		 * BGM再生機能<br />
		 * BGMデータをBGM管理機能から取り除きます。
		 *
		 * @param num BGM管理機能から取り除くBGMの番号。0-9までの整数で指定する。
		 * 
		 */
		public function removeBGM(num:int):void{
			
			// BGMを停止
			stopBGM(num, false);
			
			// モジュールが保持するSoundを閉じる
			var bgm:BGM = this._bgms[num];
			if(bgm != null && bgm.module != null && bgm.module.content != null){ 
				try{
					bgm.module.content.close();
				}catch(error:IOError){
					trace(error.getStackTrace());
				}
			}
			
			// モジュールへの参照を破棄
			delete this._bgms[num];
			
		}
		
		/**
		 * BGM再生機能<br />
		 * 指定された番号を持つBGMを再生します。
		 * 
		 * @param num 再生したいBGM番号。0-9までの整数で指定する。
		 * @param playCount 再生する回数を指定する。0を指定するとstopBGM()が呼ばれるまで無限に再生し続ける。
		 * @param vol 音量を指定する。値は0.0-1.0の間で指定する。
		 * 
		 */
		public function playBGM(num:int, playCount:int, vol:Number = 1.0):void{
			
			if(vol > 1.0){
				vol = 1.0;
			}
			if(vol < 0){
				vol = 0;
			}
			
			var bgm:BGM = this._bgms[num];
			if(bgm != null){
				bgm.play(playCount, vol, this._masterVol);
			}
			
		}
		
		/**
		 * BGM再生機能<br />
		 * 指定された番号を持つBGMを停止します。
		 * 
		 * @param num 停止したいBGM番号。0-9までの整数で指定する。
		 * @param fadeOut 停止時にフェードアウトするかどうか。trueでフェードアウトする。
		 * 
		 */
		public function stopBGM(num:int, fadeOut:Boolean = false):void{
			
			var bgm:BGM = this._bgms[num];
			if(bgm != null){
				if(fadeOut){
					bgm.stop(this._fadeOutTime);
				}else{
					bgm.stop(0.0);
				}
			}
			
		}
		
		/**
		 * BGM再生機能<br />
		 * すべてのBGMを停止します。
		 * 
		 * @param fadeOut 停止時にフェードアウトするかどうか。trueでフェードアウトする。
		 * 
		 */
		public function stopAllBGM(fadeOut:Boolean = false):void{
			
			for(var num:int=0; num>10; num++){
				
				var bgm:BGM = this._bgms[num];
				if(bgm != null){
					if(fadeOut){
						bgm.stop(this._fadeOutTime);
					}else{
						bgm.stop(0.0);
					}
				}
				
			}
			
		}
		
		/**
		 * BGM再生機能<br />
		 * 指定されたBGM番号を持つBGMの音量を変更します。
		 * 
		 * @param num 音量を変更したいBGMの番号。0-9までの整数で指定する。
		 * @param vol 指定したい音量。0.0-1.0の間で指定する。
		 * 
		 */
		public function setBgmVolume(num:int, vol:Number):void{
			
			if(vol > 1.0){
				vol = 1.0;
			}
			if(vol < 0){
				vol = 0.0;
			}
			
			var bgm:BGM = this._bgms[num];
			if(bgm != null){
				bgm.setVolume(vol, this._masterVol);
			}
			
		}
		
		/* ----- 再生機能全般設定 ----- */
		
		/**
		 * マスター音量を設定します。
		 * 
		 * @param vol 音量を設定する。0.0-1.0の間で指定する。
		 * 
		 */
		public function set masterVolume(vol:Number):void{
			
			if(vol > 1.0){
				vol = 1.0;
			}
			if(vol < 0){
				vol = 0.0
			}
			
			this._masterVol = vol;
			
			// ボイスの音量にマスター音量を反映
//			if(this._voice != null){
//				this.voiceVolume = this._voice.volume;
//			}
			
			// BGMの音量にマスター音量を反映
			for(var num:int=0; num<10; num++){
				var bgm:BGM = this._bgms[num];
				if(bgm != null){
					this.setBgmVolume(num, bgm.volume);
				}
			}
			
		}

		/**
		 * 音声を停止してからフェードアウトを開始し、音量が0になるまでの時間を設定します。
		 * 
		 * @param time 0以上のミリ秒の値を設定する。
		 * 
		 */
		public function set fadeoutTime(time:Number):void{
			if(time < 0){
				time = 0;
			}
			this._fadeOutTime = time;
		}
		
	}
	
}


/* ----- 内部クラス ----- */

import flash.events.EventDispatcher;
import flash.events.TimerEvent;
import flash.media.SoundChannel;
import flash.media.SoundTransform;
import flash.utils.Timer;

import vocaloplus.modules.ISerifModule;
import vocaloplus.modules.ISoundModule;

/**
 * BGMに関連する情報を保持するクラス。
 * 
 * @auther shiraminekeisuke(MineAP)
 */
internal class BGM{
	
	private var _module:ISoundModule;
	
	private var _volume:Number = 1.0;
	
	private var _masterVolume:Number = 1.0;
	
	private var _soundChannel:SoundChannel = null;
	
	private var _fadeOutTimer:Timer = null;
	
	/**
	 * 
	 * @param module
	 * @return 
	 * 
	 */
	public function BGM(module:ISoundModule){
		this._module = module;
	}
	
	/**
	 * 再生を開始します。
	 * 
	 * @param playCount
	 * @param localVolume
	 * @param masterVolume
	 * 
	 */
	public function play(playCount:int, localVolume:Number, masterVolume:Number):void{
		this._volume = localVolume;
		this._masterVolume = masterVolume;
		if(this._module != null && this._module.content != null){
			this._soundChannel = this._module.content.play(0, playCount, new SoundTransform(this._volume * this._masterVolume));
		}
	}
	
	/**
	 * 再生を停止します。fadeOutTimeに0ミリ秒を指定するとフェードアウトせずに停止します。
	 * 
	 * @param fadeOutTime
	 * 
	 */
	public function stop(fadeOutTime:Number):void{
		if(this._soundChannel != null){
			if(fadeOutTime == 0.0){
				this._soundChannel.stop();
			}else{
				
				this._fadeOutTimer = new Timer(10, fadeOutTime/10);
				this._fadeOutTimer.addEventListener(TimerEvent.TIMER_COMPLETE, fadeOutTimerCompleteEventHandler);
				this._fadeOutTimer.addEventListener(TimerEvent.TIMER, fadeOutTimerEventHandler);
				
				var diff:Number = this._volume/(fadeOutTime/10);
				var tempVolume:Number = this._volume;
				
				// 音量を10ミリ秒ごとに減らす
				function fadeOutTimerEventHandler(event:TimerEvent):void{
					var timer:Timer = Timer(event.currentTarget);
					
					setVolume(_volume - diff, _masterVolume);
				}
				
				// タイマーが終了したので停止 及び 音量を元に戻す
				function fadeOutTimerCompleteEventHandler(event:TimerEvent):void{
					var timer:Timer = Timer(event.currentTarget);
					timer.removeEventListener(TimerEvent.TIMER_COMPLETE, fadeOutTimerCompleteEventHandler);
					timer.removeEventListener(TimerEvent.TIMER, fadeOutTimerEventHandler);
					
					// フェードアウト無しで停止
					stop(0.0);
					
					// 音量を元に戻す
					setVolume(tempVolume, _masterVolume);
					
				}
			}
		}
	}
	
	/**
	 * 
	 * @return 
	 * 
	 */
	public function get volume():Number
	{
		return _volume;
	}
	
	/**
	 * 音量を設定します。
	 * 
	 * @param value
	 * 
	 */
	public function setVolume(volume:Number, masterVolume:Number):void
	{
		this._volume = volume;
		this._masterVolume = masterVolume;
		
		if(this._soundChannel != null && this._soundChannel.soundTransform != null){
			this._soundChannel.soundTransform.volume = (this._volume * this._masterVolume);
		}
		
	}
	
	/**
	 * 
	 * @return 
	 * 
	 */
	public function get module():ISoundModule
	{
		return _module;
	}

	/**
	 * 
	 * @return 
	 * 
	 */
	public function get soundChannel():SoundChannel
	{
		return _soundChannel;
	}

	/**
	 * 
	 * @param value
	 * 
	 */
	public function set soundChannel(value:SoundChannel):void
	{
		_soundChannel = value;
	}

	
}

