/*
 * 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(String name, InetSocketAddress isa) {
	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 " + "13 " + getSupportedMedia() + "\r\n"
	    + "a=rtpmap:13 CN/8000" + "\r\n"
            + 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;

		if (Logger.logLevel >= Logger.LOG_INFO) {
		    Logger.println("Using local media preference:  " + mediaInfo);
		}
	    }
	}
	 
	/*
	 * Try remote media preference
	 */
	if (mediaInfo == null && 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 " + 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"
            + generateRtpmap(mediaInfo) + "\r\n"
	    + transmitMap 
	    + telephoneEvent; 

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

	return sdp;
    }

}
