/*
 *  Copyright (C) 2006  Takashi Kasuya <kasuya@sfc.keio.ac.jp>
 *
 * This library is free software; you can redistribute it and/or
 *　modify it under the terms of the GNU Lesser General Public
 *　License as published by the Free Software Foundation; either
 *　version 2.1 of the License, or (at your option) any later version.
 *　This library is distributed in the hope that it will be useful,
 *　but WITHOUT ANY WARRANTY; without even the implied warranty of
 *　MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *　Lesser General Public License for more details.
 *
 *　You should have received a copy of the GNU Lesser General Public
 *　License along with this library; if not, write to the Free Software
 *　Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

package jp.ac.naka.ec.media;

import java.awt.Component;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;

import javax.media.ControllerEvent;
import javax.media.ControllerListener;
import javax.media.DataSink;
import javax.media.Manager;
import javax.media.MediaException;
import javax.media.MediaLocator;
import javax.media.NoDataSinkException;
import javax.media.NotRealizedError;
import javax.media.Player;
import javax.media.Processor;
import javax.media.protocol.DataSource;
import javax.sdp.Media;
import javax.sdp.MediaDescription;
import javax.sdp.SdpException;
import javax.sdp.SessionDescription;
import javax.swing.JFrame;

/**
 * 指定のポートで音声の再生を行う。
 * 
 * @author Takashi Kasuya
 * 
 */
public class AudioReceiver {

	MediaLocator locator;
	Processor player;
	Processor processor;
	SessionDescription sdp;
	WaitingListener listener;
	private List<MediaListener> listeners = new ArrayList<MediaListener>();
	private boolean storing = false;

	/**
	 * 間違ってるっぽい
	 * 
	 * @param sdp
	 * @throws MediaException
	 * @throws SdpException
	 */
	public void receiveMedia(SessionDescription sdp) throws MediaException,
			SdpException {
		try {
			// MediaLocatorの作成
			int port = 0;
			Vector<MediaDescription> mls = sdp.getMediaDescriptions(true);
			for (MediaDescription md : mls) {
				Media media = md.getMedia();
				if (media.getMediaType().equals("audio")) {
					port = media.getMediaPort();
					break;
				}
			}
			String url = "rtp://:" + port + "/audio";
			locator = new MediaLocator(url);
			createPlayer();
			player.prefetch();
			player.start();
			listener.waitForEvent(Player.Started);
			MediaReceiveEvent evt = new MediaReceiveEvent(this,
					MediaReceiveEvent.EventType.RECEIVE);
			dispatchEvent(evt);
			this.sdp = sdp;
		} catch (IOException e) {
			player.close();
			throw new MediaException(e.getMessage());
		}
	}

	public void receiveMedia(int port) throws MediaException {
		try {
			// MediaLocatorの作成

			String url = "rtp://:" + port + "/audio";
			locator = new MediaLocator(url);
			createPlayer();
			player.prefetch();
			player.start();
			MediaReceiveEvent evt = new MediaReceiveEvent(this,
					MediaReceiveEvent.EventType.RECEIVE);
			dispatchEvent(evt);
			// テストの時は書かないとダメっぽい
			// listener.waitForEvent(Player.Started, player);
		} catch (IOException e) {
			player.close();
			throw new MediaException(e.getMessage());
		}
	}

	/**
	 * 再生中かどうか
	 * 
	 * @return
	 */
	public boolean isPlaying() {
		// System.out.println(Player.Started + "," + player.getState());
		return (player.getState() == Player.Started) ? true : false;
	}

	/**
	 * オーディオ受信を終了
	 * 
	 */
	public void stop() {
		player.close();
		try {
			stopStore();
		} catch (IOException e) {
			e.printStackTrace();
		}
		MediaReceiveEvent evt = new MediaReceiveEvent(this,
				MediaReceiveEvent.EventType.TERMINATE);
		dispatchEvent(evt);
	}

	/**
	 * awtコンポーネントの取得
	 * 
	 * @return
	 */
	public Component getControlPanelComponent() {
		if (player != null) {
			return player.getControlPanelComponent();
		} else {
			throw new NullPointerException("Playerが作られていません");
		}
	}

	private void createPlayer() throws IOException, MediaException {
		if (locator == null)
			throw new NullPointerException("Locator is null");

		DataSource dataSource;
		dataSource = Manager.createDataSource(locator);
		player = Manager.createProcessor(dataSource);
		// DataSource clone = Manager.createCloneableDataSource(dataSource);
		// processor = Manager.createProcessor(d);
		
		listener = new WaitingListener();
		player.addControllerListener(listener);
		// processor.realize();
		player.configure();
		listener.waitForEvent(Processor.Configured);
		player.setContentDescriptor(null);
		player.realize();
		listener.waitForEvent(Player.Realized);

	}

	class WaitingListener implements ControllerListener {

		Thread th;
		int state;
		int previous;

		public void waitForEvent(final int state) {
			this.state = state;
			th = new Thread() {
				public void run() {
					try {
						synchronized (this) {
							if (previous < state)
								wait();
						}
					} catch (InterruptedException e) {
						;
					}
				}
			};
			th.start();
			try {
				th.join();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}

		public void controllerUpdate(ControllerEvent evt) {
			System.out.println(evt.toString());
			Player p = (Player) evt.getSourceController();
			// System.out.println(p.getState());
			previous = p.getState();
			if (th == null)
				return;
			if (p.getState() >= state) {
				synchronized (th) {
					th.notifyAll();
				}
			}

		}

	}

	public void addMediaListener(MediaListener listener) {
		listeners.add(listener);
	}

	public boolean removeMediaListener(MediaListener listener) {
		return listeners.remove(listener);
	}

	private void dispatchEvent(MediaReceiveEvent e) {
		for (MediaListener listener : listeners) {
			switch (e.type) {
			case RECEIVE:
				listener.receiveMedia(e);
				break;
			case TERMINATE:
				listener.terminateMedia(e);
			}
		}
	}

	DataSink sink;

	public void store(String path) throws NoDataSinkException,
			NotRealizedError, SecurityException, IOException {
		if (!isPlaying() && storing)
			return;
		MediaLocator dest = new MediaLocator(path);
		sink = Manager.createDataSink(processor.getDataOutput(), dest);
		sink.open();
		sink.start();
		storing = true;
	}

	public void stopStore() throws IOException {
		if (!storing) {
			return;
		}
		sink.stop();
		sink.close();
	}

	public static void main(String args[]) {
		AudioReceiver receiver = new AudioReceiver();
		try {
			receiver.receiveMedia(22224);
			JFrame jf = new JFrame();
			jf.add(receiver.getControlPanelComponent());
			// receiver.store("test.wav");
			jf.setVisible(true);
			jf.pack();
			jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

}
