/*
 * 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.net.DatagramSocket;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.net.SocketTimeoutException;
import java.net.SocketException;

import java.io.IOException;

/**
 * RtpSocket has one DatagramSocket for RTP and one for RTCP.
 * RTP and RTCP sockets come in pairs.  RTP must use an even port number
 * and RTCP uses the RTP port number plus 1.
 *
 * It would be possible to extend Datagram socket if we had a contructor
 * requiring the caller to specify an even port number.  Instead we find
 * two available DatagramSockets with an even and odd port number.
 *
 * RTCP is automatically started and received information is optionally printed.
 */
public class RtpSocket {
    /*
     * The default limit on Solaris is 256kb.  To change this limit, use 
     * ndd /dev/udp and set udp_max_buf to what you want.
     */
    public static final int MAX_SEND_BUFFER = (10 * 1024 * 1024);  // 10mb

    public static final int MAX_RECEIVE_BUFFER = (10 * 1024 * 1024);  // 10mb

    DatagramSocket rtpDatagramSocket;		// Socket for RTP
    DatagramSocket rtcpDatagramSocket;		// Socket for RTCP

    RtcpReceiver rtcpReceiver;			// handle RTCP reports

    private static int rtpTimeout = 330;	// 330 seconds (5 1/2 minutes)

    static {
    String s = System.getProperty("com.sun.voip.RTP_TIMEOUT");

    if (s != null) {
        int timeout = rtpTimeout;

        try {
            timeout = Integer.parseInt(s);

        if (timeout < 0) {
            Logger.println("Invalid RTP Timeout, using "
            + rtpTimeout);
        } else {
            rtpTimeout = timeout;
        }
        } catch (NumberFormatException e) {
        Logger.println("Invalid RTP Timeout, using "
            + rtpTimeout);
        }
    }
    }
    
    public RtpSocket(InetAddress ia, int port) throws SocketException {
    int p = port;

    if ((p & 1) != 0) {
        p++;
        Logger.println("Port number must be even, using " + p);
    }

        while (true) {
            try {
        rtpDatagramSocket = new DatagramSocket(p, ia);

                /*
                 * RTP/RTCP ports come in pairs and we need an even port number
         * for RTP.
                 */
                if ((rtpDatagramSocket.getLocalPort() & 1) != 0) {
                    continue;       // got an odd port number, try again
                }

                try {
                    rtcpDatagramSocket = new DatagramSocket(
            rtpDatagramSocket.getLocalPort() + 1, ia);

            if (Logger.logLevel >= Logger.LOG_MOREINFO) {
                        Logger.println(
                    "RtpSocket:  listening for RTP data at local port " 
                    + rtpDatagramSocket.getLocalPort());
            }
                    break;
                } catch (SocketException e) {
            /*
             * odd port number is in use, try again.
             */
            if (p != 0) {
            p += 2;
            }
                } catch (Exception e) {
                    rtpDatagramSocket.close();
                    Logger.error(
            "RtpSocket:  Unable to create control socket! " 
            + e.getMessage());
                    throw e;
                }
        } catch (SocketException e) {
            /*
             * port number is in use, try again.
             */
        if (p != 0) {
            p += 2;
        }
        continue;
            } catch (Exception e) {
                Logger.error("RtpSocket:  Unable to create RTP/RTCP sockets! " 
                + e.getMessage());
                throw new SocketException(
            "RtpSocket:  Unable to create RTP/RTCP sockets!");
            }
    }

    if (p != port) {
        System.out.println("RtpSocket:  Desired port " + port 
        + " is in use.  Using " + p + " instead.");
    }

    try {
        rtpDatagramSocket.setReceiveBufferSize(MAX_RECEIVE_BUFFER);
        } catch (SocketException e) {
            Logger.error("RtpSocket:  Unable to set receive buffer size! "
            + e.getMessage());

        throw e;
    }

    try {
        rtpDatagramSocket.setSendBufferSize(MAX_SEND_BUFFER);
        } catch (SocketException e) {
            Logger.error("RtpSocket:  Unable to set send buffer size! "
            + e.getMessage());

        throw e;
    }

    try {
        rtpDatagramSocket.setSoTimeout(0);
        } catch (SocketException e) {
            Logger.error("RtpSocket:  Unable to set socket timeout! "
            + e.getMessage());

        throw e;
    }
    }

    public void startRtcpReceiver() {
    /*
     * Start the RTCP receiver
     */
    rtcpReceiver = new RtcpReceiver(rtcpDatagramSocket, true);
    }

    /**
     * Return the local address for the RTP socket
     *
     * @return InetSocketAddress local address and port for the socket
     */
    public InetSocketAddress getInetSocketAddress() {
    return new InetSocketAddress(rtpDatagramSocket.getLocalAddress(),
        rtpDatagramSocket.getLocalPort());
    }

    public DatagramSocket getDatagramSocket() {
    return rtpDatagramSocket;
    }

    /**
     * Receives an RtpPacket from this socket. When this method returns, 
     * the RtpPacket's buffer is filled with the data received. 
     * The packet also contains the sender's IP address, and the port number 
     * on the sender's machine.
     *
     * This method blocks until data is received. The length field 
     * of the RtpPacket object contains the length of the received message. 
     * If the message is longer than the packet's length, 
     * the message is truncated.
     *
     * @param p the RtpPacket into which to place the incoming data.
     */
    public int receive(RtpPacket p) throws IOException {
    if (rtpDatagramSocket == null) {
        throw new IOException("RtpSocket receive failed, socket closed");
    }

    rtpDatagramSocket.receive(p.getDatagramPacket());
    return p.getDatagramPacket().getLength();
    }

    /**
     * Sends an RtpPacket from this socket. The RtpPacket includes 
     * information indicating the data to be sent, its length, 
     * the IP address of the remote host, and the port number 
     * on the remote host.
     *
     * @param RtpPacket the packet to be sent
     */
    public void send(RtpPacket p) throws IOException {
    if (rtpDatagramSocket == null) {
        throw new IOException("RtpSocket send failed, socket closed");
    }

    rtpDatagramSocket.send(p.getDatagramPacket());
    }

    /**
     * Send an Rtcp packet
     */
    public void send(RtcpPacket p) throws IOException {
    if (rtcpDatagramSocket == null) {
        throw new IOException("RtcpSocket send failed, socket closed");
    }

    rtcpDatagramSocket.send(p.getDatagramPacket());
    }

    /**
     * Set socket timeout
     *
     * @param timeout millisecond timeout value
     */
    public void setSoTimeout(int timeout) throws SocketException {
        if (rtpDatagramSocket == null) {
        throw new SocketException("rtpDatagramSocket is null");
    }

    rtpDatagramSocket.setSoTimeout(timeout);
    }

    public boolean isClosed() {
        if (rtpDatagramSocket == null) {
            return true;
        }

        return rtpDatagramSocket.isClosed();
    }

    public void flushSocket() {
    if (rtpDatagramSocket == null) {
        return;
    }

    flushSocket(rtpDatagramSocket);
    }

    public static void flushSocket(DatagramSocket socket) {
    /*
     * Flush the socket
     */
    int len = RtpPacket.getDataSize(
        RtpPacket.PCM_ENCODING, RtpPacket.MAX_SAMPLE_RATE, 2);

    len += RtpPacket.HEADER_SIZE;

        byte[] data = new byte[len];

    DatagramPacket packet = new DatagramPacket(data, len);

        int count = 0;

    try {
        socket.setSoTimeout(1);

        while (true) {
            try {
                    socket.receive(packet);
            count++;
            } catch (SocketTimeoutException e) {
            break;
            } catch (IOException e) {
            Logger.println("Error flushing socket " + e.getMessage());
            break;
        }
            }
    } catch (SocketException e) {
        Logger.println("Can't flush receiver socket!");
    }

    if (count > 0) {
        if (Logger.logLevel >= Logger.LOG_MOREINFO) {
            Logger.println("Packets flushed:  " + count);
        }
    }

    try {
        socket.setSoTimeout(0);
    } catch (SocketException e) {
        Logger.println("Can't set socket timeout to 0!");
    }
    }

    public static int getRtpTimeout() {
    return rtpTimeout;
    }

    public static void setRtpTimeout(int rtpTimeout) {
    RtpSocket.rtpTimeout = rtpTimeout;
    }

    public DatagramSocket getRtcpDatagramSocket() {
    return rtcpDatagramSocket;
    }
    
    /**
     * Close the RTP socket, stop the RtcpReceiver and close the RTCP socket.
     */
    public void close() {
    if (rtpDatagramSocket != null) {
        rtpDatagramSocket.close();
        rtpDatagramSocket = null;
    }

    if (rtcpReceiver != null) {
        rtcpReceiver.end();
        rtcpDatagramSocket.close();
        rtcpReceiver = null;
    }
    }

}
