/*
 * Copyright 2007 Sun Microsystems, Inc.
 *
 * This file is part of jVoiceBridge.
 *
 * jVoiceBridge is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation and distributed hereunder
 * to you.
 *
 * jVoiceBridge 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Sun designates this particular file as subject to the "Classpath"
 * exception as provided by Sun in the License file that accompanied this
 * code.
 */

package com.sun.voip;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.text.ParseException;

import java.util.Vector;

public class SdpManager {

    private static Vector supportedMedia = new Vector();

    private MediaInfo localMediaPreference;

    private MediaInfo transmitMediaInfo;

    private boolean isMacOS = false;

    private int maxSampleRate;
    private int maxChannels;

    private static boolean useTelephoneEvent = true;

    public SdpManager() {
        String s = System.getProperty("os.name");

        if (s.equals("Mac OS X")) {
	    isMacOS = true;

	    maxSampleRate = RtpPacket.MAC_SAMPLE_RATE;
	    maxChannels = RtpPacket.MAC_CHANNELS;
	} else {
	    maxSampleRate = RtpPacket.MAX_SAMPLE_RATE;
	    maxChannels = RtpPacket.MAX_CHANNELS;
	}
    }

    public static void useTelephoneEvent(boolean useTelephoneEvent) {
	SdpManager.useTelephoneEvent = useTelephoneEvent;
    }

    public static boolean useTelephoneEvent() {
	return useTelephoneEvent;
    }

    public SdpManager(Vector supportedMedia) {
	this.supportedMedia = supportedMedia;
    }

    public static void setSupportedMedia(Vector supportedMedia) {
	SdpManager.supportedMedia = supportedMedia;
    }

    public void setPreferredMedia(int encoding, int sampleRate, int channels)
	    throws ParseException {

	if (sampleRate == 8000 && channels == 1) {
	    encoding = RtpPacket.PCMU_ENCODING;
	}

	setPreferredMediaInfo(findMediaInfo(encoding, sampleRate, channels));
    }

    public void setPreferredMediaInfo(MediaInfo preferredMediaInfo) {
	localMediaPreference = preferredMediaInfo;

        maxSampleRate = localMediaPreference.getSampleRate();
        maxChannels = localMediaPreference.getChannels();
    }

    public MediaInfo getPreferredMediaInfo() {
	return localMediaPreference;
    }

    public void setTransmitMediaInfo(int encoding, int sampleRate,
	    int channels) throws ParseException {

	transmitMediaInfo = findMediaInfo(encoding, sampleRate, channels);
    }

    public void setTransmitMediaInfo(MediaInfo transmitMediaInfo) {
	this.transmitMediaInfo = transmitMediaInfo;
    }

    public MediaInfo getTransmitMediaInfo() {
    	return transmitMediaInfo;
    }

    public static SdpInfo parseSdp(String sdpData) throws ParseException {
	return new SdpParser().parseSdp(sdpData);
    }

    /*
     * Get supported media
     */
    private String getSupportedMedia() {
	String s = "";

	int n = 0;

	for (int i = 0; i < supportedMedia.size(); i++) {
	    MediaInfo mediaInfo = (MediaInfo) supportedMedia.elementAt(i);

	    if (mediaInfo.getSampleRate() > maxSampleRate || mediaInfo.getChannels() > maxChannels) {
	 		continue;
	    }

	    if (useTelephoneEvent == false && mediaInfo.isTelephoneEventPayload())
	    {
			continue;
	    }

	    if (n > 0) {
		s += " ";
	    }

	    s += mediaInfo.getPayload();
	    n++;
	}

	return s;
    }

    /*
     * Get supported rtpmaps
     */
    private String getRtpmaps() {
	String rtpmaps = "";

	for (int i = 0; i < supportedMedia.size(); i++)
	{
	    MediaInfo mediaInfo = (MediaInfo) supportedMedia.elementAt(i);

	    if (mediaInfo.getSampleRate() > maxSampleRate || mediaInfo.getChannels() > maxChannels)
	    {
			continue;
	    }

	    rtpmaps += generateRtpmap(mediaInfo) + "\r\n";
	}

	return rtpmaps;
    }

    /*
     * Get the rtpmap for a specific payload
     */
    private String getRtpmap(byte payload) throws ParseException {
        return generateRtpmap(findMediaInfo(payload));
    }

    /*
     * Generate an rtpmap entry for a speicifed MediaInfo.
     */
    private String generateRtpmap(MediaInfo mediaInfo) {
	if (mediaInfo.isTelephoneEventPayload()) {
	//    if (useTelephoneEvent == false) {
	//	return "";
	//    }

	    return "a=rtpmap:" + mediaInfo.getPayload()
		+ " telephone-event/8000";
	}

	return "a=rtpmap:" + mediaInfo.getPayload() + " "
		+ mediaInfo.getEncodingString() + "/"
		+ mediaInfo.getSampleRate() + "/"
		+ mediaInfo.getChannels();
    }

    /*
     * Find the MediaInfo for a specified payload
     */
    public static MediaInfo findMediaInfo(byte payload)
	    throws ParseException {

	for (int i = 0; i < supportedMedia.size(); i++) {
	    MediaInfo mediaInfo = (MediaInfo)
		supportedMedia.elementAt(i);

            if (mediaInfo.getPayload() == payload) {
                return mediaInfo;
            }
	}

	throw new ParseException("Unsupported payload " + payload, 0);
    }

    /*
     * Find the MediaInfo for specified parameters
     */
    public static MediaInfo findMediaInfo(int encoding, int sampleRate,
	int channels) throws ParseException {

	for (int i = 0; i < supportedMedia.size(); i++) {
	    MediaInfo mediaInfo = (MediaInfo)
		supportedMedia.elementAt(i);

	    if (mediaInfo.isTelephoneEventPayload()) {
		continue;	// skip this one
	    }

	    if (mediaInfo.getEncoding() == encoding &&
		    mediaInfo.getSampleRate() == sampleRate &&
		    mediaInfo.getChannels() == channels) {

		return mediaInfo;
	    }
	}

	throw new ParseException("Unsupported media " +
	    "encoding " + encoding + " sample rate " + sampleRate
	    + " channels " + channels, 0);
    }

    public String generateSdp(CallParticipant cp, String name, InetSocketAddress isa)
    {
		String toNumber = cp.getPhoneNumber();

			String sdp = "v=0\r\n"
				+ "o=" + name + " 1 1 IN IP4 "
				+ isa.getAddress().getHostAddress() + "\r\n"
				+ "s=SIP Call\r\n"
				+ "c=IN IP4 "
				+ isa.getAddress().getHostAddress() + "\r\n"
				+ "t=0 0 \r\n"
				+ "m=audio " + isa.getPort();

   			if (toNumber.indexOf("sip:") == 0)	// TODO hack for Lync DTMF
   			{
				sdp += " RTP/AVP " + "13 " + getSupportedMedia() + "\r\n"
						+ "a=rtpmap:13 CN/8000" + "\r\n";

			} else {	// Lync, add DTMF support

				sdp += " RTP/AVP " + "13 101 " + getSupportedMedia() + "\r\n"
						+ "a=rtpmap:13 CN/8000" + "\r\n"
						+ "a=rtpmap:101 telephone-event/8000" + "\r\n"
						+ "a=fmtp:101 0-16" + "\r\n";
			}

			sdp += getRtpmaps();

			if (localMediaPreference != null)
			{
				sdp += "a=PreferredPayload:" + localMediaPreference.getPayload() + "\r\n";
			}

	if (transmitMediaInfo != null) {
	    sdp += "a=transmitPayload:"
	 	+ transmitMediaInfo.getPayload() + "\r\n";
	}

        return sdp;
    }

    public String generateSdp(String name, InetSocketAddress isa, SdpInfo remoteSdpInfo) throws IOException
    {
		MediaInfo mediaInfo = null;

		if (localMediaPreference != null)
		{
			if (remoteSdpInfo.isSupported(localMediaPreference)) {
				mediaInfo = localMediaPreference;
				Logger.println("Using local media preference:  " + mediaInfo);
			}
		}

		/*
		 * Try remote media preference
		 */
		if (remoteSdpInfo.preferredMediaSpecified())
		{
			MediaInfo remoteMediaPreference = remoteSdpInfo.getMediaInfo();

			if (remoteMediaPreference.getSampleRate() <= maxSampleRate &&
				remoteMediaPreference.getChannels() <= maxChannels) {

				/*
				 * See if remote media preference is supported
				 */
				try {
					mediaInfo = findMediaInfo(remoteMediaPreference.getPayload());
					Logger.println("Using remote media preference:  " + mediaInfo);
				} catch (ParseException e) {
				}
			}
        }

		if (mediaInfo == null) {
			/*
			 * default to 8000/1 ulaw
			 */
			mediaInfo = remoteSdpInfo.findBestMediaInfo(supportedMedia,	localMediaPreference);

			Logger.println("Using best media " + mediaInfo);
		}

		remoteSdpInfo.setMediaInfo(mediaInfo);

		String payloads = "13 101 " + mediaInfo.getPayload();

		byte telephoneEventPayload = remoteSdpInfo.getTelephoneEventPayload();

		String telephoneEvent = "";

		if (useTelephoneEvent == true && telephoneEventPayload != 0) {
			try {
				MediaInfo m = findMediaInfo(telephoneEventPayload);
			payloads += " " + telephoneEventPayload;
				telephoneEvent += generateRtpmap(m) + "\r\n";
			} catch (ParseException e) {
			Logger.println("Failed to add rtpmap for telephone event " + telephoneEventPayload);
			}
		}

		String transmitMap = "";

		if (transmitMediaInfo != null) {
			transmitMap = generateRtpmap(transmitMediaInfo) + "\r\n";
		}

		String sdp =
			"v=0\r\n"
				+ "o=" + name + " 1 1 IN IP4 "
				+ isa.getAddress().getHostAddress() + "\r\n"
				+ "s=SIP Call\r\n"
				+ "c=IN IP4 "
				+ isa.getAddress().getHostAddress() + "\r\n"
				+ "t=0 0 \r\n"
				+ "m=audio " + isa.getPort()
				+ " RTP/AVP " + payloads + "\r\n"
				+ "a=rtpmap:13 CN/8000" + "\r\n"
				+ "a=rtpmap:101 telephone-event/8000" + "\r\n"
				+ "a=fmtp:101 0-16" + "\r\n"
				+ generateRtpmap(mediaInfo) + "\r\n"
			+ transmitMap
			+ telephoneEvent;

        if (transmitMediaInfo != null) {
            sdp += "a=transmitPayload:"
                + transmitMediaInfo.getPayload() + "\r\n";
        }

		return sdp;
    }

}
