package gnu.javax.sound.sampled.wce;

import java.util.ArrayList;
import java.util.List;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioPermission;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.Control;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.Line;
import javax.sound.sampled.LineEvent;
import javax.sound.sampled.LineListener;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.Mixer;
import javax.sound.sampled.SourceDataLine;
import javax.sound.sampled.TargetDataLine;

public class WCEMixer extends WCELine implements Mixer {
	/**
	 * イベントキュー
	 */
	private final static WCEEventQueue eventQueue;
	
	static {
		eventQueue = new WCEEventQueue();
		// イベントキュースレッドを開始する
		Thread t = new Thread(eventQueue);
		t.start();
		
		// コールバックスレッドを開始する
		WCECallbackWindowThread callbackWindowThread
			= new WCECallbackWindowThread(eventQueue);
		callbackWindowThread.start();

		// コールバックウインドウが初期化されるまで待つ
		try {
			callbackWindowThread.waitForInitialized();
		} catch (InterruptedException ie) {
			ie.printStackTrace();
		}
	}
	
	// デバイスがサポートしているフォーマットを
	// 表す定数
	private static int WAVE_FORMAT_1M08   = 0x00000001;       /* 11.025 kHz, Mono,   8-bit  */
	private static int WAVE_FORMAT_1S08   = 0x00000002;       /* 11.025 kHz, Stereo, 8-bit  */
	private static int WAVE_FORMAT_1M16   = 0x00000004;       /* 11.025 kHz, Mono,   16-bit */
	private static int WAVE_FORMAT_1S16   = 0x00000008;       /* 11.025 kHz, Stereo, 16-bit */
	private static int WAVE_FORMAT_2M08   = 0x00000010;       /* 22.05  kHz, Mono,   8-bit  */
	private static int WAVE_FORMAT_2S08   = 0x00000020;       /* 22.05  kHz, Stereo, 8-bit  */
	private static int WAVE_FORMAT_2M16   = 0x00000040;       /* 22.05  kHz, Mono,   16-bit */
	private static int WAVE_FORMAT_2S16   = 0x00000080;       /* 22.05  kHz, Stereo, 16-bit */
	private static int WAVE_FORMAT_4M08   = 0x00000100;       /* 44.1   kHz, Mono,   8-bit  */
	private static int WAVE_FORMAT_4S08   = 0x00000200;       /* 44.1   kHz, Stereo, 8-bit  */
	private static int WAVE_FORMAT_4M16   = 0x00000400;       /* 44.1   kHz, Mono,   16-bit */
	private static int WAVE_FORMAT_4S16   = 0x00000800;       /* 44.1   kHz, Stereo, 16-bit */

	/**
	 * Mixer.Info
	 */
	private final Mixer.Info mixerInfo;
	
	/**
	 * オープン中のSourceDataLine
	 */
	private final List sourceLineList = new ArrayList();
	
	/**
	 * オープン中のTargetDataLine
	 */
	private final List targetLineList = new ArrayList();
	
	/**
	 * Windows CE用Mixerのインスタンスを作成する。
	 */
	public WCEMixer(Mixer.Info info) {
		super(new Line.Info(Mixer.class));
		this.mixerInfo = info;
	}
	
	public Mixer.Info getMixerInfo() {
		return this.mixerInfo;
	}
	
	
	/**
	 * ネイティブフォーマット情報を、AudioFormatに変換する
	 *
	 * @see #getNativeSourceLineFormats()
	 * @see #getNativeTargetLineFormats()
	 */
	private AudioFormat[] toAudioFormatArray(int nativeFormats) {
		List list = new ArrayList();
		if ((nativeFormats & WAVE_FORMAT_1M08) != 0) {
			// 11.025 kHz, mono, 8-bit 
			list.add(new AudioFormat(11025.0f,
									 8,
									 1,		// mono
									 false,	// unsigned
									 false));
		}
		if ((nativeFormats & WAVE_FORMAT_1M16) != 0) {
			// 11.025 kHz, mono, 16-bit 
			list.add(new AudioFormat(11025.0f,
									 16,
									 1,		// mono
									 true,	// signed
									 false));
		}
		if ((nativeFormats & WAVE_FORMAT_1S08) != 0) {
			// 11.025 kHz, stereo, 8-bit 
			list.add(new AudioFormat(11025.0f,
									 8,
									 2,		// stereo
									 false,	// unsigned
									 false));
		}
		if ((nativeFormats & WAVE_FORMAT_1S16) != 0) {
			// 11.025 kHz, stereo, 16-bit 
			list.add(new AudioFormat(11025.0f,
									 16,
									 2,		// stereo
									 true,	// signed
									 false));
		}
		if ((nativeFormats & WAVE_FORMAT_2M08) != 0) {
			// 22.05 kHz, mono, 8-bit 
			list.add(new AudioFormat(22050.0f,
									 8,
									 1,		// mono
									 false,	// unsigned
									 false));
		}
		if ((nativeFormats & WAVE_FORMAT_2M16) != 0) {
			// 22.05 kHz, mono, 16-bit 
			list.add(new AudioFormat(22050.0f,
									 16,
									 1,		// mono
									 true,	// signed
									 false));
		}
		if ((nativeFormats & WAVE_FORMAT_2S08) != 0) {
			// 22.05 kHz, stereo, 8-bit
			list.add(new AudioFormat(22050.0f,
									 8,
									 2,		// stereo
									 false,	// unsigned
									 false));
		}
		if ((nativeFormats & WAVE_FORMAT_2S16) != 0) {
			// 22.05 kHz, stereo, 16-bit
			list.add(new AudioFormat(22050.0f,
									 16,
									 2,		// stereo
									 true,	// signed
									 false));
		}
		if ((nativeFormats & WAVE_FORMAT_4M08) != 0) {
			// 44.1 kHz, mono, 8-bit 
			list.add(new AudioFormat(44100.0f,
									 8,
									 1,		// mono
									 false,	// unsigned
									 false));
		}
		if ((nativeFormats & WAVE_FORMAT_4M16) != 0) {
			// 44.1 kHz, mono, 16-bit 
			list.add(new AudioFormat(44100.0f,
									 16,
									 1,		// mono
									 true,	// signed
									 false));
		}
		if ((nativeFormats & WAVE_FORMAT_4S08) != 0) {
			// 44.1 kHz, stereo, 8-bit 
			list.add(new AudioFormat(44100.0f,
									 8,
									 2,		// stereo
									 false,	// unsigned
									 false));
		}
		if ((nativeFormats & WAVE_FORMAT_4S16) != 0) {
			// 44.1 kHz, stereo, 16-bit 
			list.add(new AudioFormat(44100.0f,
									 16,
									 2,		// stereo
									 true,	// signed
									 false));
		}
		
		return (AudioFormat[]) list.toArray(new AudioFormat[0]);
	}
	
	/**
	 * Windows CE音声出力デバイスがサポートするフォーマットと
	 * チャンネル数を返す。
	 * 戻り値は、WAVEOUTCAPS.dwFormats の値である
	 */
	private native int getNativeSourceLineFormats();
	
	public Line.Info[] getSourceLineInfo() {
		int nativeFormats = getNativeSourceLineFormats();
		AudioFormat[] formats = toAudioFormatArray(nativeFormats);
		return new Line.Info[] {
			new DataLine.Info(SourceDataLine.class,
							 formats,
							 AudioSystem.NOT_SPECIFIED,
							 AudioSystem.NOT_SPECIFIED) };
	}
	
	/**
	 * Windows CE音声入力デバイスがサポートする
	 * フォーマットの一覧を返す。
	 */
	private native int getNativeTargetLineFormats();
	
	public Line.Info[] getTargetLineInfo() {
		int nativeFormats = getNativeTargetLineFormats();
		AudioFormat[] formats = toAudioFormatArray(nativeFormats);
		return new Line.Info[] {
			new DataLine.Info(TargetDataLine.class,
							 formats,
							 AudioSystem.NOT_SPECIFIED,
							 AudioSystem.NOT_SPECIFIED) };
	}
	
	public Line.Info[] getSourceLineInfo(Line.Info info) {
		List list = new ArrayList();
		Line.Info[] allInfo = getSourceLineInfo();
		for (int i = 0; i < allInfo.length; ++i) {
			if (allInfo[i].matches(info)) {
				list.add(allInfo[i]);
			}
		}
		return (Line.Info[]) list.toArray(new Line.Info[0]);
	}
	
	public Line.Info[] getTargetLineInfo(Line.Info info) {
		List list = new ArrayList();
		Line.Info[] allInfo = getTargetLineInfo();
		for (int i = 0; i < allInfo.length; ++i) {
			if (allInfo[i].matches(info)) {
				list.add(allInfo[i]);
			}
		}
		return (Line.Info[]) list.toArray(new Line.Info[0]);
	}
	
	/**
	 * 指定されたLine.Infoに適合するLineがサポートされているかを
	 * 返す。
	 */
	public boolean isLineSupported(Line.Info info) {
		// ToDo: ネイティブライブラリ側で判断するように修正する
		boolean result = true;
		try {
			getLine(info);
		} catch (Exception e) {
			result = false;
		}
		return result;
	}
	
	/**
	 * 指定されたLine.Infoに適合するLineを返す。
	 */
	public Line getLine(Line.Info info)
             throws LineUnavailableException {
		SecurityManager sm = System.getSecurityManager();
		
		Line result = null;
		if (info instanceof DataLine.Info) {
			DataLine.Info dinfo = (DataLine.Info) info;
			if (SourceDataLine.class.equals(dinfo.getLineClass())) {
				if (sm != null) {
					// セキュリティチェック
					sm.checkPermission(new AudioPermission("play"));
				}
				result = new WCESourceDataLine(dinfo);
			} else if (TargetDataLine.class.equals(dinfo.getLineClass())) {
				if (sm != null) {
					// セキュリティチェック
					sm.checkPermission(new AudioPermission("record"));
				}
				result = new WCETargetDataLine(dinfo);
			} else if (Clip.class.equals(dinfo.getLineClass())) {
				if (sm != null) {
					// セキュリティチェック
					sm.checkPermission(new AudioPermission("play"));
				}
				result = new WCEClip(dinfo);
			}
		}
		
		if (result == null) {
			throw new LineUnavailableException(info.toString());
		}
		
		// リスナを追加する
		result.addLineListener(new LineListener() {
			public void update(LineEvent e) {
				LineEvent.Type type = e.getType();
				boolean open = (type == LineEvent.Type.OPEN);
				boolean close = (type == LineEvent.Type.CLOSE);
				Line line = e.getLine();
				if (open) {
					if (e instanceof SourceDataLine) {
						sourceLineList.add(line);
					} else if (e instanceof TargetDataLine) {
						targetLineList.add(line);
					}
				} else if (close) {
					if (e instanceof SourceDataLine) {
						sourceLineList.remove(line);
					} else if (e instanceof TargetDataLine) {
						targetLineList.remove(line);
					}
				}
			}
		});
					
		return result;
	}
	
	public int getMaxLines(Line.Info info) {
		// ToDo: 実装する
		return AudioSystem.NOT_SPECIFIED;
	}
	
	/**
	 * すでにオープンされているSourceDataLineの配列を返す
	 */
	public Line[] getSourceLines() {
		// セキュリティチェック
		SecurityManager sm = System.getSecurityManager();
		if (sm != null) {
			sm.checkPermission(new AudioPermission("play"));
		}
		
		return (Line[]) this.sourceLineList.toArray(new Line[0]);
	}
	
	/**
	 * すでにオープンされているTargetDataLienの配列を返す。
	 */
	public Line[] getTargetLines() {
		// セキュリティチェック
		SecurityManager sm = System.getSecurityManager();
		if (sm != null) {
			sm.checkPermission(new AudioPermission("record"));
		}

		return (Line[]) this.targetLineList.toArray(new Line[0]);
	}

	public void synchronize(Line[] lines,
                 boolean maintainSync) {
		// ToDo: 実装する
		throw new IllegalArgumentException();
	}
	
	public void unsynchronize(Line[] lines) {
		// ToDo: 実装する
	}
	
	public boolean isSynchronizationSupported(Line[] lines,
											  boolean maintainSync) {
		// ToDo: 実装する
		return false;
	}
	
	public Control[] getControls() {
		// ToDo: 実装する
		return new Control[0];
	}
	
	public boolean isControlSupported(Control.Type control) {
		// ToDo: 実装する
		return false;
	}
	
	public Control getControl(Control.Type control) {
		// ToDo: 実装する
		throw new IllegalArgumentException();
	}

}
