/*
 *  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.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.SdpConstants;

import net.sourceforge.jsdp.Attribute;
import net.sourceforge.jsdp.Connection;
import net.sourceforge.jsdp.Media;
import net.sourceforge.jsdp.MediaDescription;
import net.sourceforge.jsdp.Origin;
import net.sourceforge.jsdp.SdpException;
import net.sourceforge.jsdp.SdpMessageParser;
import net.sourceforge.jsdp.SessionDescription;
import net.sourceforge.jsdp.SessionName;
import net.sourceforge.jsdp.Time;
import net.sourceforge.jsdp.TimeDescription;
import net.sourceforge.jsdp.Version;

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

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

	/**
	 * 
	 * @param version
	 * @param userName
	 * @param sessionName
	 * @param host
	 * @param availableAudioFormats
	 * @param audioPort
	 * @param availableVideoFormats
	 * @param videoPort
	 * @return
	 */
	public static SessionDescription getRequestSessionDescription(int version,
			String userName, String sessionName, InetAddress host,
			List<Integer> availableAudioFormats, int audioPort,
			List<Integer> availableVideoFormats, int videoPort) {
		SessionDescription sdp = generateBasicSessionDescription(version,
				userName, sessionName, host);
		MediaDescription[] md = null;
		int cnt = 0;

		// audio description
		if (availableAudioFormats != null && availableVideoFormats != null) {
			md = new MediaDescription[2];
		} else {
			md = new MediaDescription[1];
		}
		if (availableAudioFormats != null) {
			String name = "rtpmap";
			String value;
			Iterator<Integer> iter = availableAudioFormats.iterator();
			int payload = iter.next();

			Media media = new Media("audio", audioPort, "RTP/AVP", payload + "");

			value = payload + " " + getAttributeValue(payload);
			Attribute attr = new Attribute(name, value);

			md[0] = new MediaDescription(media);

			md[0].addAttribute(attr);

			while (iter.hasNext()) {
				payload = iter.next();
				media.addMediaFormat(payload + "");
				value = payload + " " + getAttributeValue(payload);
				attr = new Attribute(name, value);
				md[0].addAttribute(attr);
				limit--;
				if (limit == 1)
					break;

			}
			limit = 3;

			cnt++;
		}

		// video description
		if (availableVideoFormats != null) {
			String name = "rtpmap";
			String value;
			Iterator<Integer> iter = availableVideoFormats.iterator();
			int payload = iter.next();
			Media media = new Media("video", videoPort, "RTP/AVP", payload + "");

			value = payload + " " + getAttributeValue(payload);
			Attribute attr = new Attribute(name, value);

			md[cnt] = new MediaDescription(media);
			md[cnt].addAttribute(attr);

			while (iter.hasNext()) {
				payload = iter.next();
				media.addMediaFormat(payload + "");
				value = payload + " " + getAttributeValue(payload);
				attr = new Attribute(name, value);
				md[cnt].addAttribute(attr);
				limit--;
				if (limit == 1)
					break;

			}
		}
		if (md != null) {
			// バグがあるっぽい
			// sdp.setMediaDescriptions(md);
		}

		String sdp_str = sdp.toString();
		if (md != null) {
			for (MediaDescription mediaDescriptor : md)
				sdp_str += mediaDescriptor.encode();
		}
		sdp = SdpMessageParser.getSessionDescription(sdp_str);
		return sdp;
	}

	/**
	 * メディア記述を除いたSDPの生成
	 * 
	 * @param version
	 * @param userName
	 * @param sessionName
	 * @param host
	 * @return
	 */
	public static SessionDescription generateBasicSessionDescription(
			int version, String userName, String sessionName, InetAddress host) {

		SessionDescription sdp = null;
		try {
			Version v = new Version(version);
			/*
			 * String addrType = localhost instanceof Inet6Address ?
			 * Connection.IP6 : Connection.IP4;
			 */
			Date date = new Date();
			long sessionVersion = date.getTime();

			Origin o = new Origin(userName, sessionVersion, host
					.getHostAddress());
			o.setSessionVersion(o.getSessionID());
			SessionName s = new SessionName(sessionName);
			TimeDescription t = new TimeDescription(new Time());

			Connection c = new Connection(host.getHostAddress());
			sdp = new SessionDescription(v, o, s, t);
			sdp.setConnection(c);
			sdp.setSessionName(s);

		} catch (SdpException e) {
			System.err
					.println("An SDP exception occurred while generating local sdp description \n"
							+ e.getMessage());
		}

		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 int[] getReceivableFormats(String[] 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(Integer
								.parseInt(supports[i]));
						if (target == null) {
							continue;
						}
						// とりあえずエンコーディングがあっているのを探す
						if (target.isSameEncoding(f)) {
							vec.add(Integer.parseInt(supports[i]));
						}
					}
				} else if (format instanceof VideoFormat) {
					VideoFormat f = (VideoFormat) format;
					for (int i = 0; i < supports.length; i++) {
						// 比較
						Format target = findCorrespondingFormat(Integer
								.parseInt(supports[i]));
						if (target == null) {
							continue;
						}
						// とりあえずエンコーディングがあっているのを探す
						if (target.isSameEncoding(f)) {
							vec.add(Integer.parseInt(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);
		return fmts;
	}

	/**
	 * Gets Media Formats of Media Description information sent in SDP.
	 * 
	 * @param inviteRequest
	 * @return supportedFormats
	 */
	private static MediaDescription[] getSupportedMedia(SessionDescription sdp) {
		int[] audio = null, video = null;
		MediaDescription[] mediaDescriptions = sdp.getMediaDescriptions();
		int audio_port = 0, video_port = 0;
		for (MediaDescription mediaDescription : mediaDescriptions) {
			Media media = mediaDescription.getMedia();
			if (media.getMediaType().equals("audio")) {
				audio = getReceivableFormats(media.getMediaFormats());
				audio_port = media.getPort();
			} else if (media.getMediaType().equals("video")) {
				video = getReceivableFormats(media.getMediaFormats());
				video_port = media.getPort();
			}
		}
		MediaDescription[] ret = null;
		int cnt = 0;
		if (audio != null && video != null) {
			ret = new MediaDescription[2];
			cnt = 2;
		} else {
			ret = new MediaDescription[1];
			cnt = 1;
		}
		for (int i = 0; i < cnt; i++) {
			int port = 0;
			String m = "audio";
			int[] target = audio;
			if (audio != null && i == 0) {
				port = audio_port;
			} else if (video != null) {
				port = video_port;
				m = "video";
				target = video;
			}
			int payload = target[0];
			String name = "rtpmap";
			String value;
			// メディア記述の追加
			Media media = new Media(m, port, "RTP/AVP", payload + "");
			value = payload + " " + getAttributeValue(payload);
			Attribute attr = new Attribute(name, value);
			MediaDescription md = new MediaDescription(media);
			md.addAttribute(attr);
			for (int j = 1; j < target.length; j++) {
				payload = target[j];
				media.addMediaFormat(payload + "");
				value = payload + " " + getAttributeValue(payload);
				attr = new Attribute(name, value);
				md.addAttribute(attr);
			}

			ret[i] = md;
		}

		return ret;
	}

	/**
	 * 
	 * @param sdp
	 * @return
	 */
	public static SessionDescription getResponseSessionDescription(
			SessionDescription sdp) {
		InetAddress addr;
		try {
			addr = InetAddress.getByName(sdp.getConnection().getAddress());
		} catch (UnknownHostException e) {
			e.printStackTrace();
			return null;
		}
		String usr = sdp.getOrigin().getUser();
		String session = sdp.getSessionName().getValue();
		SessionDescription ret = generateBasicSessionDescription(0, usr,
				session, addr);
		MediaDescription[] md = getSupportedMedia(sdp);

		String sdp_str = ret.toString();
		if (md != null) {
			for (MediaDescription mediaDescriptor : md)
				sdp_str += mediaDescriptor.encode();
		}
		ret = SdpMessageParser.getSessionDescription(sdp_str);
		return ret;
	}

}
