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

/*
 * Packet for Receiving RTP data
 */
public class RtpReceiverPacket extends RtpPacket {
    String id;

    private long previousRtpTimestamp;

    /*
     * Statistics
     */
    private long packetsDropped = 0;
    private int outOfSequencePackets;
    private int shortPackets = 0;
    private int wrongRtpTimestamp = 0;
    private int sequenceOffset = 0;

    public RtpReceiverPacket(String id, int encoding, int sampleRate, 
        int channels, int bufferSize) {

    super(encoding, sampleRate, channels, bufferSize);

    this.id = id;		// for Logging
    }

    public RtpReceiverPacket(String id, int encoding, int sampleRate, 
        int channels) {

    super(encoding, sampleRate, channels);

    this.id = id;		// for Logging
    }

    public void setId(String id) {
    this.id = id;
    }

    /**
     * Update RTP receiver information.  Keep statistics on packet arrival 
     * times and out-of-sequence packets.
     * Returns offset of packet sequence relative to the correct sequence
     * e.g. 0 means packet is in order, -1 means packet seq is one less 
     * than expected, +1 means packet seq is one higher than expected.
     */
    public void updateRtpHeader(int length) {
    long packetRtpTimestamp = getRtpTimestamp();
    short packetRtpSequenceNumber = getRtpSequenceNumber();

    boolean outOfSequence = false;

    sequenceOffset = 0;

    if (isMarkSet()) {
        /*
         * The MARK_BIT is supposed to be set in the first packet
         * and also the first packet with PCMU_PAYLOAD after COMFORT_PAYLOAD
         * has been received.
         * 
         * When the MARK_BIT is set, we accept the sequence number
         * and timestamp in the packet.
         *
         * Calculate what we expect the next packet to have.
         */
        setMark();		// make sure mark is set;
        rtpSequenceNumber = (short) (packetRtpSequenceNumber + 1);

        rtpTimestamp = packetRtpTimestamp + length;
    } else {
        /*
         * Normal packet without the MARK_BIT set.
         * Verify the sequenceNumber and timestamp
         *
         * Set what we expect next packet to have
         */
            if (packetRtpSequenceNumber == rtpSequenceNumber) {
            rtpSequenceNumber = ++rtpSequenceNumber;
        } else {
                outOfSequencePackets++;
        outOfSequence = true;

        sequenceOffset = packetRtpSequenceNumber - rtpSequenceNumber;

                if (Logger.logLevel >= Logger.LOG_INFO) {
                    Logger.writeFile(id + ":  PACKET OUT OF SEQUENCE!  "
            + "seq expected 0x"
                        + Integer.toHexString(rtpSequenceNumber)
                        + ", got 0x"
                        + Integer.toHexString(packetRtpSequenceNumber)
                        + ", total out of seq " + outOfSequencePackets
            + ", payload " + getRtpPayload() 
            + ", length " + getLength());
        }

        /*
         * Set the sequence number to what we expect next
         * but never lower the sequence number.
         * We have to be careful here because after
         * 0x7fff the next sequence number is negative.
         */
        if ((packetRtpSequenceNumber > 0 && rtpSequenceNumber > 0) ||
            (packetRtpSequenceNumber < 0 && rtpSequenceNumber < 0)) {

            if (packetRtpSequenceNumber > rtpSequenceNumber) {
                rtpSequenceNumber = (short)
                    (packetRtpSequenceNumber + 1); // reset seq number
            } 
        } else {
            rtpSequenceNumber = (short)
                (packetRtpSequenceNumber + 1); // reset seq number
        }

        /*
         * XXX We may want to insert packets which are missing.
         * One approach (Norco does this) is to duplicate 
         * the last packet received.  Maybe inserting a packet 
         * of silence would be ok too.
         * This is handled by the ConferenceMember.
         */
            }

            if (rtpTimestamp == packetRtpTimestamp) {
            rtpTimestamp += length; 
        } else {
        if (outOfSequence == false) {
            /*
             * Don't report wrong timestamp if packet is out of sequence.
             */
                    wrongRtpTimestamp++;

            if ((wrongRtpTimestamp % 1000) == 0) {
                        if (Logger.logLevel >= Logger.LOG_INFO) {
                            Logger.writeFile(id + " Bad packet received:  len " 
                    + length 
                    + ", ts off by " 
                    + (long)(packetRtpTimestamp - rtpTimestamp)
                                + ", total wrong ts " + wrongRtpTimestamp 
                    + ", seq " + packetRtpSequenceNumber);
            }
                    }
        }

        /*
         * XXX Should we reset the timestamp to what we just got?
         * Seems we have to...
         * It's possible some packets were lost and we're
         * never going to get the timestamp we'd like.
         */
        if (packetRtpTimestamp > rtpTimestamp) { 
            rtpTimestamp = packetRtpTimestamp + length;
        } 
            }
    }

    rtpSequenceNumber &= 0xffff;
    rtpTimestamp &= 0xffffffff;
    previousRtpTimestamp = packetRtpTimestamp;
    }

    public long getRtpTimestampDiff() {
    return rtpTimestamp - previousRtpTimestamp;
    }

    public void incrementShortPackets() {
    shortPackets++;
    }

    public int getShortPackets() {
    return shortPackets;
    }

    public void incrementOutOfSequencePackets() {
    outOfSequencePackets++;
    }

    public int getOutOfSequencePackets() {
    return outOfSequencePackets;
    }

    public void incrementWrongRtpTimestamp() {
    wrongRtpTimestamp++;
    }

    public int getWrongRtpTimestamp() {
    return wrongRtpTimestamp;
    }

    public int getSequenceOffset() {
    return sequenceOffset;
    }

    /*
     * Make sure sequence number wraps properly.
     */ 
    public static void main(String[] args) {
        RtpReceiverPacket packet = new RtpReceiverPacket(
            "Test", PCM_ENCODING, 8000, 1);

        Logger.logLevel = 5;

        short expected = packet.rtpSequenceNumber;

        while (true) {
        packet.updateRtpHeader(180);

            if (packet.rtpSequenceNumber != expected) {
                Logger.println("expected " + expected
                    + " got " + packet.rtpSequenceNumber);
            }

            expected++;
        packet.buffer[2] = (byte) ((packet.rtpSequenceNumber >> 8) & 0xff);
        packet.buffer[3] = (byte) (packet.rtpSequenceNumber & 0xff);
        }
    }

}
