/*
 *  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.net.Inet6Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.Vector;

import javax.media.Format;
import javax.media.PlugInManager;
import javax.media.format.AudioFormat;
import javax.media.format.VideoFormat;
import javax.sdp.Attribute;
import javax.sdp.Connection;
import javax.sdp.Media;
import javax.sdp.MediaDescription;
import javax.sdp.Origin;
import javax.sdp.SdpConstants;
import javax.sdp.SdpException;
import javax.sdp.SdpFactory;
import javax.sdp.SessionDescription;
import javax.sdp.SessionName;
import javax.sdp.Time;
import javax.sdp.TimeDescription;
import javax.sdp.Version;

/**
 * SDPの生成に用いられるクラス。
 * 
 * @author Takashi Kasuya
 * 
 */
class SDPGenerator {

	// Payloadの最大数
	protected static int limit = 3;

	private static SdpFactory factory = SdpFactory.getInstance();

	/**
	 * 
	 * @param version
	 * @param userName
	 * @param sessionName
	 * @param host
	 * @param availableAudioFormats
	 * @param audioPort
	 * @param availableVideoFormats
	 * @param videoPort
	 * @return
	 * @throws SdpException
	 * @throws UnknownHostException
	 */

	public static SessionDescription getRequestSessionDescription(int version,
			String userName, String sessionName, String host_str,
			List<Integer> availableAudioFormats, int audioPort,
			List<Integer> availableVideoFormats, int videoPort)
			throws UnknownHostException, SdpException {
		SessionDescription sdp = generateBasicSessionDescription(version,
				userName, sessionName, host_str);
		Vector<MediaDescription> mds = new Vector<MediaDescription>();
		MediaDescription md;
		if (availableAudioFormats != null) {
			md = createMediaDescription(availableAudioFormats, "audio",
					audioPort);
			mds.add(md);
		}
		if (availableVideoFormats != null) {
			md = createMediaDescription(availableVideoFormats, "video",
					videoPort);
			mds.add(md);
		}
		sdp.setMediaDescriptions(mds);
		return sdp;
	}

	private static MediaDescription createMediaDescription(List<Integer> list,
			String media, int port) throws IllegalArgumentException,
			SdpException {
		String name = "rtpmap";
		String value;
		int[] formats = new int[list.size()];
		Vector<Attribute> attrs = new Vector<Attribute>();
		for (int i = 0; i < list.size(); i++) {
			if (i > limit) {
				break;
			}
			int num = list.get(i);
			formats[i] = num;
			value = num + " " + getAttributeValue(num);
			attrs.add(factory.createAttribute(name, value));

		}
		MediaDescription md = factory.createMediaDescription(media, port, 0,
				"RTP/AVP", formats);

		md.setAttributes(attrs);
		return md;
	}

	/**
	 * メディア記述を除いたSDPの生成
	 * 
	 * @param version
	 * @param userName
	 * @param sessionName
	 * @param host_str
	 * @return
	 * @throws UnknownHostException
	 * @throws SdpException
	 */
	public static SessionDescription generateBasicSessionDescription(
			int version, String userName, String sessionName, String host_str)
			throws UnknownHostException, SdpException {
		SessionDescription sdp = null;
		InetAddress host = InetAddress.getByName(host_str);
		sdp = factory.createSessionDescription();
		Version v = factory.createVersion(version);
		String addrType = host instanceof Inet6Address ? Connection.IP6
				: Connection.IP4;
		String networkType = "IN";
		Date date = new Date();
		long sessionVersion = date.getTime();

		Origin o = factory.createOrigin(userName, sessionVersion,
				sessionVersion, networkType, addrType, host.getHostAddress()
						.toString());
		SessionName s = factory.createSessionName(sessionName);
		Time time = factory.createTime();
		TimeDescription t = factory.createTimeDescription(time);
		Vector<TimeDescription> vec = new Vector<TimeDescription>();
		vec.add(t);
		Connection c = factory.createConnection(host.getHostAddress()
				.toString());

		sdp.setVersion(v);
		sdp.setOrigin(o);
		sdp.setTimeDescriptions(vec);
		sdp.setConnection(c);
		sdp.setSessionName(s);

		return sdp;
	}

	/**
	 * ペイロードタイプに対応したフォーマットを返す
	 * 
	 * @param payload
	 * @return
	 */
	private static String getAttributeValue(int payload) {
		switch (payload) {
		// audio
		case SdpConstants.PCMU:
			return "PCMU/8000";
		case SdpConstants.GSM:
			return "gsm/8000";
		case SdpConstants.G723:
			return "g723/8000";
		case SdpConstants.DVI4_8000:
			return "dvi4/8000";
		case SdpConstants.DVI4_16000:
			return "dvi4/16000";
		case SdpConstants.PCMA:
			return "PCMA/8000";
		case SdpConstants.G728:
			return "g728/8000";
		case SdpConstants.DVI4_11025:
			return "dvi4/11025";
		case SdpConstants.DVI4_22050:
			return "dvi4/22050";
		case SdpConstants.G729:
			return "g729/8000";

			// video
		case SdpConstants.H261:
			return "H261/90000";
		case SdpConstants.H263:
			return "H263/90000";
		case SdpConstants.MPV:
			return "MPV/90000";
		case SdpConstants.JPEG:
			return "JPEG/90000";
		default:
			return null;
		}
	}

	/**
	 * 
	 * @param sdpFormat
	 * @return
	 */
	static Format findCorrespondingFormat(int sdpFormat) {
		String format = null;
		switch (sdpFormat) {
		case SdpConstants.PCMU:
			return new AudioFormat(AudioFormat.ULAW_RTP, 8000.0d, 8, 1);
		case SdpConstants.GSM:
			return new AudioFormat(AudioFormat.GSM_RTP, 8000.0d, 8, 1);
		case SdpConstants.G723:
			return new AudioFormat(AudioFormat.G723_RTP, 8000.0d, 8, 1);
		case SdpConstants.DVI4_8000:
			return new AudioFormat(AudioFormat.DVI_RTP, 8000.0d, 8, 1);
		case SdpConstants.DVI4_16000:
			return new AudioFormat(AudioFormat.DVI_RTP, 16000.0d, 8, 1);
		case SdpConstants.PCMA:
			return new AudioFormat(AudioFormat.ALAW, 8000.0d, 8, 1);
		case SdpConstants.G728:
			return new AudioFormat(AudioFormat.G728_RTP, 8000.0d, 8, 1);
		case SdpConstants.DVI4_11025:
			return new AudioFormat(AudioFormat.DVI_RTP, 11025d, 8, 1);
		case SdpConstants.DVI4_22050:
			return new AudioFormat(AudioFormat.DVI_RTP, 22050.0, 8, 1);
		case SdpConstants.G729:
			return new AudioFormat(AudioFormat.G729_RTP, 8000.0, 8, 1);
		case SdpConstants.H263:
			format = VideoFormat.H263_RTP;
			break;
		case SdpConstants.JPEG:
			format = VideoFormat.JPEG_RTP;
			break;
		case SdpConstants.H261:
			format = VideoFormat.H261_RTP;
			break;
		case SdpConstants.MPV:
			format = VideoFormat.MPEG_RTP;
			break;
		default:
			return null;
		}
		return new VideoFormat(format);
	}

	/**
	 * 
	 * @param supports
	 * @return
	 */
	public static Vector<Integer> getReceivableFormats(int[] supports) {
		Vector codecs = PlugInManager.getPlugInList(null, null,
				PlugInManager.CODEC);
		Set<Integer> vec = new HashSet<Integer>();

		// audio
		for (Iterator iter = codecs.iterator(); iter.hasNext();) {
			String plugin = (String) iter.next();
			Format[] formats = PlugInManager.getSupportedInputFormats(plugin,
					PlugInManager.CODEC);
			for (Format format : formats) {

				if (format instanceof AudioFormat) {
					AudioFormat f = (AudioFormat) format;
					for (int i = 0; i < supports.length; i++) {
						// 比較
						Format target = findCorrespondingFormat(supports[i]);
						if (target == null) {
							continue;
						}
						// とりあえずエンコーディングがあっているのを探す
						if (target.isSameEncoding(f)) {
							vec.add(supports[i]);
						}
					}
				} else if (format instanceof VideoFormat) {
					VideoFormat f = (VideoFormat) format;
					for (int i = 0; i < supports.length; i++) {
						// 比較
						Format target = findCorrespondingFormat(supports[i]);
						if (target == null) {
							continue;
						}
						// とりあえずエンコーディングがあっているのを探す
						if (target.isSameEncoding(f)) {
							vec.add(supports[i]);
						}
					}
				}
			}
		}
		int[] fmts = new int[vec.size()];
		int fmt = 0, i = 0;
		for (Iterator<Integer> iter = vec.iterator(); iter.hasNext(); i++) {
			fmt = iter.next();
			fmts[i] = fmt;
		}
		Arrays.sort(fmts);
		Vector<Integer> ret = new Vector<Integer>();
		for (i = 0; i < fmts.length; i++) {
			ret.add(fmts[i]);
		}
		return ret;
	}

	/**
	 * Gets Media Formats of Media Description information sent in SDP.
	 * 
	 * @param inviteRequest
	 * @return supportedFormats
	 * @throws SdpException
	 */
	private static Vector<MediaDescription> getSupportedMedia(
			SessionDescription sdp, int audio_port, int video_port) throws SdpException {
		Vector<Integer> audio = null, video = null;
		Vector<MediaDescription> mediaDescriptions = sdp
				.getMediaDescriptions(true);
		//int audio_port = 0, video_port = 0;
		for (MediaDescription mediaDescription : mediaDescriptions) {
			Media media = mediaDescription.getMedia();
			Vector formats = media.getMediaFormats(true);
			int[] int_formats = new int[formats.size()];
			for (int i = 0; i < int_formats.length; i++) {
				int_formats[i] = Integer.parseInt((String) formats.get(i));
			}
			if (media.getMediaType().equals("audio")) {
				audio = getReceivableFormats(int_formats);
				//audio_port = media.getMediaPort();
			} else if (media.getMediaType().equals("video")) {
				video = getReceivableFormats(int_formats);
				//video_port = media.getMediaPort();
			}
		}
		Vector<MediaDescription> ret = new Vector<MediaDescription>();
		if (audio != null) {
			ret.add(createMediaDescription(audio, "audio", audio_port));
		}
		if (video != null) {
			ret.add(createMediaDescription(video, "video", video_port));
		}

		return ret;
	}

	/**
	 * 返信用のSDPの作成
	 * 
	 * @param user
	 *            ユーザ名
	 * @param port
	 *            送信先のポート
	 * @param sdp
	 * @return
	 * @throws UnknownHostException
	 * @throws SdpException
	 */
	public static SessionDescription getResponseSessionDescription(String user,
			int audio_port, SessionDescription sdp)
			throws UnknownHostException, SdpException {
		// String addr = sdp.getConnection().getAddress();
		String addr = InetAddress.getLocalHost().getHostAddress();
		String session = sdp.getSessionName().getValue();
		// SessionDescription ret = generateBasicSessionDescription(0, usr,
		// session, addr);
		SessionDescription ret = generateBasicSessionDescription(0, user,
				session, addr);
		Vector<MediaDescription> md = getSupportedMedia(sdp, audio_port, 0);
		ret.setMediaDescriptions(md);
		return ret;
	}

	/**
	 * 返信用のSDPの作成
	 * 
	 * @param user
	 *            ユーザ名
	 * @param port
	 *            送信先のポート
	 * @param sdp
	 * @return
	 * @throws UnknownHostException
	 * @throws SdpException
	 */
	public static SessionDescription getResponseSessionDescription(String user,
			int audio_port, int video_port, SessionDescription sdp)
			throws UnknownHostException, SdpException {
		String addr = InetAddress.getLocalHost().getHostAddress();
		String session = sdp.getSessionName().getValue();
		SessionDescription ret = generateBasicSessionDescription(0, user,
				session, addr);
		Vector<MediaDescription> md = getSupportedMedia(sdp, audio_port, video_port);
		ret.setMediaDescriptions(md);
		return ret;
	}

	public static void main(String[] args) {
		try {
			System.out.println(SDPGenerator.generateBasicSessionDescription(0,
					"hoge", "-", "localhost"));
			List<Integer> audio = new Vector<Integer>();
			audio.add(0);
			audio.add(8);
			List<Integer> video = new Vector<Integer>();
			video.add(31);
			SessionDescription sdp = SDPGenerator.getRequestSessionDescription(
					0, "hoge", "-", "localhost", audio, 10, video, 20);
			//System.out.println(SDPGenerator.getSupportedMedia(sdp));
			//sdp = SDPGenerator.getRequestSessionDescription(0, "hoge", "-",
				//	"localhost", audio, 10, null, 20);
			System.out.println(SDPGenerator.getResponseSessionDescription(
					"hogehoge", 5060, 5080, sdp));
		} catch (UnknownHostException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (SdpException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

}
