/*
 *  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.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Vector;

import javax.media.Format;
import javax.media.MediaException;
import javax.media.Player;
import javax.media.control.BufferControl;
import javax.media.format.AudioFormat;
import javax.media.protocol.DataSource;
import javax.media.rtp.Participant;
import javax.media.rtp.RTPControl;
import javax.media.rtp.RTPManager;
import javax.media.rtp.ReceiveStream;
import javax.media.rtp.ReceiveStreamListener;
import javax.media.rtp.event.ByeEvent;
import javax.media.rtp.event.NewReceiveStreamEvent;
import javax.media.rtp.event.ReceiveStreamEvent;
import javax.media.rtp.event.RemotePayloadChangeEvent;
import javax.media.rtp.event.StreamMappedEvent;
import javax.sdp.Connection;
import javax.sdp.Media;
import javax.sdp.MediaDescription;
import javax.sdp.SdpConstants;
import javax.sdp.SdpException;
import javax.sdp.SessionDescription;
import javax.swing.JFrame;

/**
 * JMFによるストリーミングビデオの再生に用いる。
 * 
 * @author 粕谷
 * 
 */
public class MediaReceiver implements ReceiveStreamListener {

	protected int[] receivableJmfVideoFormats = new int[] { SdpConstants.H263, // javax.media.format.VideoFormat.H263_RTP
			SdpConstants.JPEG, // javax.media.format.VideoFormat.JPEG_RTP
			SdpConstants.H261, // javax.media.format.VideoFormat.H261_RTP
	};

	protected int[] receivableJmfAudioFormats = new int[] {
	// sdp format // corresponding JMF Format
			SdpConstants.G723, // javax.media.format.AudioFormat.G723_RTP
			SdpConstants.GSM, // javax.media.format.AudioFormat.GSM_RTP;
			SdpConstants.PCMU, // javax.media.format.AudioFormat.ULAW_RTP;
			SdpConstants.DVI4_8000, // javax.media.format.AudioFormat.DVI_RTP;
			SdpConstants.DVI4_16000, // javax.media.format.AudioFormat.DVI_RTP;
			SdpConstants.PCMA, // javax.media.format.AudioFormat.ALAW;
			SdpConstants.G728 // , //

	};

	static List<Integer> availableVideoFormats = new ArrayList<Integer>();
	static List<Integer> availableAudioFormats = new ArrayList<Integer>();

	private Player video_player, audio_player;
	protected RTPManager mgrs[];
	protected RTPSocketAdapter[] adapter;

	protected Object dataSync = new Object();
	protected WaitingListener listener = new WaitingListener();

	protected boolean dataReceived = false;
	SessionInformation[] info = null;

	private int audio_port = 22222;
	private int video_port = 44444;

	protected boolean playing;

	/**
	 * コンストラクタ
	 * 
	 */
	public MediaReceiver() {
		Arrays.sort(receivableJmfVideoFormats);

		for (int format : receivableJmfVideoFormats) {
			availableVideoFormats.add(format);
		}

		Arrays.sort(receivableJmfAudioFormats);
		for (int format : receivableJmfAudioFormats) {
			availableAudioFormats.add(format);
		}
	}

	/**
	 * 指定のポートでRTPメディアの受信、再生を行う。ビデオのみの再生で音声は再生しない。
	 * 
	 * @param port
	 * @throws UnknownHostException
	 * @throws MediaException
	 */
	public MediaReceiver(int port) throws UnknownHostException {
		info = new SessionInformation[1];
		InetAddress addr = InetAddress.getByName("localhost");
		info[0] = new SessionInformation(addr.getHostAddress(), port);
	}

	/**
	 * 指定のSDPから再生
	 * 
	 * @param sdp
	 * @throws UnknownHostException
	 * @throws SdpException
	 */
	public MediaReceiver(SessionDescription sdp) throws UnknownHostException,
			SdpException {
		info = getSessionInformations(sdp);
		// initialize(info);
	}

	public void receiveMedia() throws MediaException, IOException {
		initialize(info);
		play();
	}

	public void receiveMedia(SessionDescription sdp) throws MediaException,
			IOException, SdpException {
		if (isPlaying() && playing) {
			setNewSessionDescription(sdp);
			return;
		}
		info = getSessionInformations(sdp);
		initialize(info);
		play();
	}

	private void initialize(SessionInformation[] sessions) throws IOException,
			MediaException {
		playing = true;
		mgrs = new RTPManager[sessions.length];

		adapter = new RTPSocketAdapter[sessions.length];
		for (int i = 0; i < sessions.length; i++) {
			SessionInformation session = sessions[i];
			System.err.println("  - Open RTP session for: addr: "
					+ session.addr + " port: " + session.port + " ttl: "
					+ session.ttl);
			mgrs[i] = (RTPManager) RTPManager.newInstance();
			// mgrs[i].addSessionListener(this);
			mgrs[i].addReceiveStreamListener(this);

			// Initialize the RTPManager with the RTPSocketAdapter
			adapter[i] = new RTPSocketAdapter(InetAddress.getLocalHost(),
					session.port, session.ttl);
			mgrs[i].initialize(adapter[i]);

			// You can try out some other buffer size to see
			// if you can get better smoothness.
			BufferControl bc = (BufferControl) mgrs[i]
					.getControl("javax.media.control.BufferControl");
			if (bc != null)
				bc.setBufferLength(350);
		}

		// データの到着を待つ
		long then = System.currentTimeMillis();
		long waitingPeriod = 30000; // 最高３０秒待つ

		try {
			synchronized (dataSync) {
				while (!dataReceived
						&& (System.currentTimeMillis() - then) < waitingPeriod) {
					if (!dataReceived)
						System.err
								.println("  - Waiting for RTP data to arrive...");
					dataSync.wait(1000);
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}

		if (!dataReceived) {
			close();
			throw new MediaException("No RTP data was received.");
		} else {
			// 受信完了
			MediaReceiveEvent evt = new MediaReceiveEvent(this,
					MediaReceiveEvent.EventType.RECEIVE);
			dispatchEvent(evt);
		}
	}

	/**
	 * 新しいSDPを使ってセッションを張りなおす (多少問題あり)
	 * 
	 * @param sdp
	 * @throws MediaException
	 * @throws IOException
	 * @throws SdpException
	 */
	public void setNewSessionDescription(SessionDescription sdp)
			throws IOException, MediaException, SdpException {
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		if (isPlaying()) {
			// BYEがこないとき
			close();
		}
		SessionInformation[] info = getSessionInformations(sdp);
		initialize(info);
	}

	private SessionInformation[] getSessionInformations(SessionDescription sdp)
			throws UnknownHostException, SdpException {
		Vector<MediaDescription> media = sdp.getMediaDescriptions(true);
		SessionInformation[] info = new SessionInformation[media.size()];
		Connection c = sdp.getConnection();
		InetAddress addr = InetAddress.getByName(c.getAddress());
		int i = 0, port;

		for (MediaDescription md : media) {
			Media m = md.getMedia();
			port = m.getMediaPort();
			info[i++] = new SessionInformation(addr.getHostAddress(), port);
		}
		return info;
	}

	/**
	 * メディアの再生。通常は自動的に行われる。
	 * 
	 */
	public void play() {
		if (video_player != null) {
			video_player.start();
		}
		if (audio_player != null) {
			audio_player.start();
		}
	}

	/**
	 * メディアの再生を停止。
	 * 
	 */
	public void stop() {
		playing = false;
		if (video_player != null) {
			video_player.stop();
		}
		if (audio_player != null) {
			audio_player.stop();
		}

	}

	/**
	 * 動画の再生中かどうか？
	 * 
	 * @return
	 */
	public boolean isPlaying() {
		if (video_player != null) {
			// System.out.println("State : " +video_player.getState());
			boolean a = (video_player.getState() == Player.Started) ? true
					: false;
			return a;
		} else {
			return false;
		}
	}

	/**
	 * コントローラコンポーネントの取得。オーディオの方。
	 * 
	 * @return
	 */
	public Component getControlPanelComponent() {
		if (audio_player != null) {
			return audio_player.getControlPanelComponent();
		} else if (video_player != null) {
			return video_player.getControlPanelComponent();
		} else {
			throw new NullPointerException("Playerが作られていません");
		}
	}

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

	/**
	 * SessionManagerをすべて閉じる
	 */
	synchronized void close() {
		dataReceived = false;
		// close the RTP session.
		for (int i = 0; i < mgrs.length; i++) {
			if (mgrs[i] != null) {
				mgrs[i].removeTargets("Closing session from VideoPlayer");
				// System.out.println(mgrs[i].toString());
				mgrs[i].dispose();
				mgrs[i] = null;
			}
		}
		if (adapter != null) {
			for (RTPSocketAdapter a : adapter) {
				if (a != null) {
					a.close();
					a = null;
				}
			}
		}

		if (video_player != null) {
			video_player.close();
			video_player.deallocate();
		}
		if (audio_player != null) {
			audio_player.close();
			audio_player.deallocate();
		}
		MediaReceiveEvent evt = new MediaReceiveEvent(this,
				MediaReceiveEvent.EventType.TERMINATE);
		dispatchEvent(evt);
	}

	public synchronized void update(ReceiveStreamEvent evt) {

		Participant participant = evt.getParticipant(); // could be null.
		ReceiveStream stream = evt.getReceiveStream(); // could be null.

		if (evt instanceof RemotePayloadChangeEvent) {
			System.err.println("  - Received an RTP PayloadChangeEvent.");
			System.err.println("Sorry, cannot handle payload change.");
			close();
			return;
		}

		else if (evt instanceof NewReceiveStreamEvent) {
			try {
				stream = ((NewReceiveStreamEvent) evt).getReceiveStream();
				DataSource ds = stream.getDataSource();

				// Find out the formats.
				RTPControl ctl = (RTPControl) ds
						.getControl("javax.media.rtp.RTPControl");
				if (ctl != null) {
					System.err.println("  - Recevied new RTP stream: "
							+ ctl.getFormat());
				} else
					System.err.println("  - Recevied new RTP stream");

				if (participant == null)
					System.err
							.println("      The sender of this stream had yet to be identified.");
				else {
					System.err.println("      The stream comes from: "
							+ participant.getCNAME());
				}

				Format receivedMediaFormat = ctl.getFormat();
				Player p = javax.media.Manager.createPlayer(ds);
				if (p == null)
					return;
				if (receivedMediaFormat instanceof AudioFormat) {
					audio_player = p;
				} else {
					video_player = p;
				}

				p.realize();
				p.addControllerListener(listener);
				synchronized (listener) {
					listener.waitForEvent(Player.Realized);
				}
				// Notify intialize() that a new stream had arrived.
				synchronized (dataSync) {
					dataReceived = true;
					dataSync.notifyAll();
				}

			} catch (Exception e) {
				System.err.println("NewReceiveStreamEvent exception "
						+ e.getMessage());
				return;
			}

		}

		else if (evt instanceof StreamMappedEvent) {
			if (stream != null && stream.getDataSource() != null) {
				DataSource ds = stream.getDataSource();
				// Find out the formats.
				RTPControl ctl = (RTPControl) ds
						.getControl("javax.media.rtp.RTPControl");
				System.err.println("  - The previously unidentified stream ");
				if (ctl != null)
					System.err.println("      " + ctl.getFormat());
				System.err.println("      had now been identified as sent by: "
						+ participant.getCNAME());
			}
		}

		else if (evt instanceof ByeEvent) {
			System.err.println("  - Got \"bye\" from: "
					+ participant.getCNAME());
			close();
		}
	}

	private List<MediaListener> listeners = new ArrayList<MediaListener>();

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

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

	protected void dispatchEvent(MediaReceiveEvent e) {
		for (MediaListener listener : listeners) {
			switch (e.type) {
			case RECEIVE:
				listener.receiveMedia(e);
				break;
			case TERMINATE:
				listener.terminateMedia(e);
			}
		}
	}
	
	/**
	 * レスポンス用のSDPの取得
	 * @param user
	 * @param video_port
	 * @param sdp
	 * @return
	 * @throws UnknownHostException
	 * @throws SdpException
	 */
	public SessionDescription getResponseSessionDescription(String user,
			int video_port, SessionDescription sdp)
			throws UnknownHostException, SdpException {
		SessionDescription ret = SDPGenerator.getResponseSessionDescription(
				user, 0, video_port, sdp);
		return ret;
	}

	/**
	 * レスポンス用のSDPの取得
	 * 
	 * @param user
	 * @param audio_port
	 * @param video_port
	 * @param sdp
	 * @return
	 * @throws SdpException
	 * @throws UnknownHostException
	 */
	public SessionDescription getResponseSessionDescription(String user,
			int audio_port, int video_port, SessionDescription sdp)
			throws UnknownHostException, SdpException {
		SessionDescription ret = SDPGenerator.getResponseSessionDescription(
				user, audio_port, video_port, sdp);
		return ret;
	}

	/**
	 * @param args
	 * @throws IOException
	 * @throws FileNotFoundException
	 */
	public static void main(String[] args) throws FileNotFoundException,
			IOException {
		try {
			MediaReceiver receiver = new MediaReceiver(44444);

			receiver.addMediaListener(new MediaListener() {

				public void receiveMedia(MediaReceiveEvent e) {
					JFrame jf = new JFrame();
					MediaReceiver receiver = (MediaReceiver) e.getSource();
					jf.setLayout(new BorderLayout());
					Component video = receiver.getVisualComponent();
					video.setPreferredSize(new Dimension(640, 480));
					jf.add(video, BorderLayout.CENTER);
					jf.add(receiver.getControlPanelComponent(),
							BorderLayout.SOUTH);
					jf.setVisible(true);
					jf.pack();
					jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
				}

				public void terminateMedia(MediaReceiveEvent e) {
					System.out.println("Bye");
					System.exit(1);
				}

			});
			receiver.receiveMedia();

		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	/**
	 * リクエストの為のSDPの取得
	 * 
	 * @return
	 * @throws UnknownHostException
	 * @throws SdpException
	 */
	public SessionDescription getRequestSessionDescription()
			throws UnknownHostException, SdpException {
		InetAddress addr = InetAddress.getLocalHost();
		List<Integer> audio = null;

		SessionDescription sdp = SDPGenerator.getRequestSessionDescription(0,
				"media_sender", "-", addr.getHostAddress(), audio,
				getAudioPort(), availableVideoFormats, getVideoPort());
		return sdp;

	}

	public Player getVideoPlayer() {
		return video_player;
	}

	public int getAudioPort() {
		return audio_port;
	}

	public void setAudioPort(int audio_port) {
		this.audio_port = audio_port;
	}

	public int getVideoPort() {
		return video_port;
	}

	public void setVideoPort(int video_port) {
		this.video_port = video_port;
	}

}
