package gameCanvasUtil;


import java.io.File;
import javax.sound.sampled.*;

/** サウンドのストリーミング再生行っているクラス
  * 
  * 同時に何個も音を鳴らすことは出来るけど、負荷が高いみたい･･･<br/>
  */
public class streamSound implements soundManagerInterface {

	/** サウンド数などよきに計らえ **/
	public static final int NUMBER_AUTO_CULC = -1;
	/** サウンドリストをテキストから読み込みやがれ */
	public static final int READ_LIST_FROM_TEXT = -2;
	
	/// サウンド数
	private static final int MAX_SOUND_NUM = 128;
	
	//音を読み込むStream
	private AudioInputStream audioInputStream[];
	//LINE
	private SourceDataLine line[];
	//サウンドのループ
	private boolean loopFlag[];
	
	//ラインバッファーに対する音を入れる
	private int bind[];
	
	//1ラインあたりのバッファ
	private static final int EXTERNAL_BUFFER_SIZE = 12800/3;
	//同時再生できる数(なんか同時再生きついかも・・・)
	private static final int LINE_BUFFER_NUM = 1;

	//音量を覚えておく
	private int volume[];

	//再生用バッファ
	private byte[] abData = new byte[EXTERNAL_BUFFER_SIZE];
	
	//コンストラクタで指定されたファイル名を保持
	private String fileName;
	//ファイルのリストを保持します。
	private String fileNameList[];
	
	/** コンストラクタ
	 * 
	 * @param fileName 読み込むファイル名を指定(本当は再生時にストリームするから正確には読み込まないけどね)
	 * @param number サウンド数の指定。
	 * 何ファイルになるかわからない場合は"NUMBER_AUTO_CULC"を指定するとよきに計らいます。<br />
	 * また READ_LIST_FROM_TEXT を指定すると連番ファイル読み込みではなく、引数"file"を<br />
	 * サウンドのリストが書いてあるテキストファイルとして読み込み、そのリストに従ってサウンドを読み込みます。<br />
	 * (リストは一行に1ファイルずつ書いていく感じです。)
	 */
	public streamSound(String fileName,int number){
		try{
			this.fileName = fileName;
			if(number == NUMBER_AUTO_CULC){
				this.fileNameList = new String[MAX_SOUND_NUM];
				for(int i=0;i<fileNameList.length;i++){
					fileNameList[i] = fileName + i +".wav";
				}
			}else if(number == READ_LIST_FROM_TEXT){
				this.fileNameList = saveDataUtil.readResourceStrings(fileName);
			}else{
				this.fileNameList = new String[number];
				for(int i=0;i<fileNameList.length;i++){
					fileNameList[i] = fileName + i +".wav";
				}
			}
			

			//必要なオブジェクトを生成
			this.audioInputStream = new AudioInputStream[LINE_BUFFER_NUM];
			line = new SourceDataLine[LINE_BUFFER_NUM];
			this.loopFlag = new boolean[LINE_BUFFER_NUM];
			this.bind = new int[LINE_BUFFER_NUM];
			this.volume = new int[LINE_BUFFER_NUM];
			for(int i=0;i<LINE_BUFFER_NUM;i++)
				this.bind[i] = -1;
		}catch(Exception e){
			e.printStackTrace();
		}
	}
	
	
	/** サウンドを指定したラインにロードする
	 * 
	 * @param line_id ラインの指定
	 * @param snd_id サウンドのID指定
	 */
	private void loadAndPlaySound(int line_id,int snd_id){
		try{
			audioInputStream[line_id] = AudioSystem.getAudioInputStream( 
					this.getClass().getResourceAsStream("/"+fileNameList[snd_id]) );
			
			
			AudioFormat audioFormat = audioInputStream[line_id].getFormat();
			
			// データラインの情報オブジェクトを生成します
			DataLine.Info info = new DataLine.Info(SourceDataLine.class,audioFormat);
			// 指定されたデータライン情報に一致するラインを取得します
			line[line_id] = (SourceDataLine) AudioSystem.getLine(info);
			// 指定されたオーディオ形式でラインを開きます
			line[line_id].open(audioFormat);
			// ラインでのデータ入出力を可能にします
			line[line_id].start();
			//音量をセットします
			this.setVolume( line_id , volume[line_id] );
			
			this.bind[line_id] = snd_id;
		}catch(Exception e){
			System.out.println("soundStream play Error "+e);
			e.printStackTrace();
		}
		
	}
	
	/** ストリーミング再生なので　ちょくちょく呼び出してやる
	 * 
	 * 呼び出しが途切れると、再生も途切れるのです。
	 */
	public void update(){
		int nBytesRead = 0;
		try{
			for(int i=0;i<LINE_BUFFER_NUM;i++){
				if(line[i]!=null && line[i].isOpen() ){
					// オーディオストリームからデータを読み込みます
					nBytesRead = audioInputStream[i].read(abData, 0, abData.length);
					if (nBytesRead >= 0) {
						// オーディオデータをミキサーに書き込みます
						line[i].write(abData, 0, nBytesRead);
					}else{
						if(loopFlag[i]){
							this.loadAndPlaySound(i,this.bind[i]);
						}else{
							this.stopSound(i);
						}
					}
				}
			}
		}catch(Exception e){
			e.printStackTrace();
		}
	}
	
	
	/** 開いているラインバッファーのIDを取得
	 * @return 開いているラインバッファーのIDを返す。-1の場合全て使用されています。
	 */
	private int getFreeLineBuf(){
		for(int i=0;i<LINE_BUFFER_NUM;i++){
			if( this.line[i]==null || (!this.line[i].isOpen() ) )
				return i;
		}
		return -1;
	}

	/** 指定した音を　指定したラインから鳴らす
	 * @param line_id ラインのID
	 * @param snd_id 音のID指定
	 * @param loop ループするかどうかを指定
	 */
	private void playSound(int line_id,int snd_id,boolean loop){
		this.loopFlag[line_id] = loop;
		if(this.line[line_id]!=null && this.line[line_id].isOpen()){
			this.line[line_id].start();
		}else{
			this.loadAndPlaySound(line_id,snd_id);
		}
	}
	
	/** 指定した音を鳴らす
	 * @param snd_id 音のID指定
	 * @param loop ループするかどうかを指定
	 */
	public void playSound(int snd_id,boolean loop){
		int line_id = getFreeLineBuf();
		if(line_id>=0){
			this.playSound(line_id,snd_id,loop);
		}else{
			for(int i=0;i<this.line.length;i++){
				if(bind[i]==snd_id){
					this.playSound(i,snd_id, loop);
				}
			}
		}
	}
	
	/** このオブジェクトが管理している全ての音を停止させます
	 */
	public void stopSound(){
		for(int i=0;i<LINE_BUFFER_NUM;i++)
			this.stopSound(i);
	}
	
	/** 指定した音の停止
	 * @param id 音IDの指定
	 */
	public void stopSound(int id){	
		try{
			this.line[id].drain();
			this.line[id].close();
			this.line[id] = null;
			this.bind[id] = -1;
		}catch(Exception e){
			e.printStackTrace();
		}
	}
	
	/** 指定した音を一時停止
	 * @param id 音の指定
	 */
	public void pauseSound(int id){
		try{
			this.line[id].stop();
		}catch(Exception e){
			e.printStackTrace();
		}
	}
	/** 音の大きさの変更(バッファーに覚えておくだけ)
	 * @param id どの音を変更するか？
	 * @param param　音の大きさを指定(0-100)
	 */
	public void changeVolume(int id,int param){
		this.volume[id] = param;
		this.setVolume( id , param );
	}
	
	/** 音の大きさの変更(実際に行うところ)
	 * @param id どの音を変更するか？
	 * @param param　音の大きさを指定(0-100)
	 */
	private void setVolume(int id,int param){
		try{
			FloatControl control = (FloatControl)this.line[id].getControl(FloatControl.Type.MASTER_GAIN);
			float range = control.getMaximum() - control.getMinimum();
			control.setValue( range * (float) Math.sqrt(param / 100.0f) + control.getMinimum() );
		}catch(Exception e){
			e.printStackTrace();
		}
	}
	
	
	/** 全てのサウンドを一時停止する
	 */
	public void pauseSound(){
		for(int i=0;i<LINE_BUFFER_NUM;i++)
			this.pauseSound(i);
	}
	
	/** 全てのサウンドの音量変更
	 */
	public void changeVolume(int vol){
		for(int i=0;i<LINE_BUFFER_NUM;i++)
			this.changeVolume(i,vol);
	}
}
