package brn.swans.app.rtcp;

import jist.swans.Constants;
import brn.swans.app.rtp.msg.RtpMessage;

/**
 * This class, a subclass of RTPParticipantInfo, is used for storing information
 * about other rtp endsystems. This class processes rtp packets, rtcp sender
 * reports and rtcp sdes chunks originating from this endsystem.
 */
public class RtpReceiverInfo extends RtpParticipantInfo {

  // ////////////////////////////////////////////////
  // constants
  //

  // ////////////////////////////////////////////////
  // locals
  //

  /**
   * The sequence number of the first RtpMessage received (unsigned).
   */
  protected short sequenceNumberBase;

  /**
   * The highest sequence number of an RtpMessage received (unsigned).
   */
  protected short highestSequenceNumber;

  /**
   * The highest sequence number of an RtpMessage received before the beginning
   * of the current rtcp interval (unsigned).
   */
  protected int highestSequenceNumberPrior;

  /**
   * The number of sequence number wrap arounds (unsigned).
   */
  protected int sequenceNumberCycles;

  /**
   * How many rtp packets from this source have been received (unsigned).
   */
  protected int packetsReceived;

  /**
   * How many rtp packets have been received from this source before the current
   * rtcp interval began (unsigned).
   */
  protected int packetsReceivedPrior;

  /**
   * The interarrival jitter. See rtp rfc for details.
   */
  protected double jitter;

  /**
   * The clock rate (in ticks per second) the sender increases the rtp
   * timestamps. It is calculated when two sender reports have been received.
   */
  protected int clockRate;

  /**
   * The rtp time stamp of the last SenderReport received from this sender
   * (unsigned).
   */
  protected int lastSenderReportRTPTimeStamp;

  /**
   * The ntp time stamp of the last SenderReport received from this sender
   * (unsigned).
   */
  protected long lastSenderReportNTPTimeStamp;

  /**
   * The rtp time stamp of the last RtpMessage received from this sender
   * (unsigned). Needed for calculating the jitter.
   */
  protected int lastPacketRTPTimeStamp;

  /**
   * The arrival time of the last RtpMessage received from this sender. Needed
   * for calculating the jitter.
   */
  protected long lastPacketArrivalTime;

  /**
   * The arrival time of the last SenderReport received from this sender.
   */
  protected long lastSenderReportArrivalTime;

  /**
   * The consecutive number of rtcp intervals this rtcp end system hasn't sent
   * anything.
   */
  protected int inactiveIntervals;

  /**
   * The time when this rtp end system has been inactive for five consecutive
   * rtcp intervals.
   */
  protected long startOfInactivity;

  /**
   * The number of rtp and rtcp packets received from this rtp end system.
   */
  protected int itemsReceived;

  // ////////////////////////////////////////////////
  // initialization
  //

  public RtpReceiverInfo(int ssrc) {
    super(ssrc);

    this.sequenceNumberBase = 0;
    this.highestSequenceNumber = 0;
    this.highestSequenceNumberPrior = 0;
    this.sequenceNumberCycles = 0;

    this.packetsReceived = 0;
    this.packetsReceivedPrior = 0;

    this.jitter = 0.0;
    this.clockRate = 0;
    this.lastSenderReportRTPTimeStamp = 0;
    this.lastSenderReportNTPTimeStamp = 0;
    this.lastPacketRTPTimeStamp = 0;

    this.lastPacketArrivalTime = 0;
    this.lastSenderReportArrivalTime = 0;

    this.inactiveIntervals = 0;
    this.startOfInactivity = 0;
    this.itemsReceived = 0;
  }

  // ////////////////////////////////////////////////
  // accessors
  //

  // ////////////////////////////////////////////////
  // overwrites
  //

  public String toString() {
    // TODO Auto-generated method stub
    return super.toString();
  }

  // ////////////////////////////////////////////////
  // Implementation
  //

  /**
   * Extracts information of the given RtpMessage. Also sets
   * this.inactiveIntervals to 0.
   */
  public void processRTPPacket(RtpMessage packet, long arrivalTime) {

    // this endsystem sends, it isn't inactive
    this.inactiveIntervals = 0;

    this.packetsReceived++;
    this.itemsReceived++;

    if (this.packetsReceived == 1) {
      this.sequenceNumberBase = packet.getSequenceNumber();
    } else {
      if (packet.getSequenceNumber() > this.highestSequenceNumber) {
        // it is possible that this is a late packet from the
        // previous sequence wrap
        if (!(packet.getSequenceNumber() > 0xFFEF && this.highestSequenceNumber < 0x10))
          this.highestSequenceNumber = packet.getSequenceNumber();
      } else {
        // is it a sequence number wrap around 0xFFFF to 0x0000 ?
        if (packet.getSequenceNumber() < 0x10
            && this.highestSequenceNumber > 0xFFEF) {
          this.sequenceNumberCycles += 0x00010000;
          this.highestSequenceNumber = packet.getSequenceNumber();
        }
      }
      // calculate interarrival jitter
      if (this.clockRate != 0) {
        double d = (double) (packet.getTimeStamp() - this.lastPacketRTPTimeStamp)
            - (arrivalTime - this.lastPacketArrivalTime)
            * (double) this.clockRate / (double) Constants.SECOND;
        if (d < 0)
          d = -d;
        this.jitter = this.jitter + (d - this.jitter) / 16;
      }
      this.lastPacketRTPTimeStamp = packet.getTimeStamp();
      this.lastPacketArrivalTime = arrivalTime;
    }

    super.processRTPPacket(packet, arrivalTime);

  }

  /**
   * Extracts information of the given SenderReport.
   */
  public void processSenderReport(SenderReport report, long arrivalTime) {

    this.lastSenderReportArrivalTime = arrivalTime;
    if (this.lastSenderReportRTPTimeStamp == 0) {
      this.lastSenderReportRTPTimeStamp = report.getRtpTimeStamp();
      this.lastSenderReportNTPTimeStamp = report.getNtpTimeStamp();
    } else if (this.clockRate == 0) {
      int rtpTicks = report.getRtpTimeStamp()
          - this.lastSenderReportRTPTimeStamp;
      long ntpDifference = report.getNtpTimeStamp()
          - this.lastSenderReportNTPTimeStamp;
      double ntpSeconds = (double) ntpDifference / (double) (0xFFFFFFFF);
      this.clockRate = (int) ((double) rtpTicks / ntpSeconds);
    }

    this.inactiveIntervals = 0;
    this.itemsReceived++;

    report = null;
  }

  /**
   * Extracts information of the given SDESChunk.
   */
  public void processSDESChunk(SDESChunk sdesChunk, long arrivalTime) {
    super.processSDESChunk(sdesChunk, arrivalTime);
    this.itemsReceived++;
    this.inactiveIntervals = 0;
  }

  /**
   * Returns a ReceptionReport if this rtp end system is a sender, null
   * otherwise.
   */
  public ReceptionReport receptionReport(long now) {

    if (isSender()) {
      ReceptionReport receptionReport = new ReceptionReport();
      receptionReport.setSsrc(getSsrc());

      long packetsExpected = this.sequenceNumberCycles
          + (long) this.highestSequenceNumber - (long) this.sequenceNumberBase
          + (long) 1;
      long packetsLost = packetsExpected - this.packetsReceived;

      int packetsExpectedInInterval = this.sequenceNumberCycles
          + this.highestSequenceNumber - this.highestSequenceNumberPrior;
      int packetsReceivedInInterval = this.packetsReceived
          - this.packetsReceivedPrior;
      int packetsLostInInterval = packetsExpectedInInterval
          - packetsReceivedInInterval;
      byte fractionLost = 0;
      if (packetsLostInInterval > 0) {
        fractionLost = (byte) ((packetsLostInInterval << 8) / packetsExpectedInInterval);
      }
      ;

      receptionReport.setFractionLost(fractionLost);
      receptionReport.setPacketsLostCumulative((int) packetsLost);
      receptionReport.setSequenceNumber(this.sequenceNumberCycles
          + this.highestSequenceNumber);

      receptionReport.setJitter((int) this.jitter);

      // the middle 32 bit of the ntp time stamp of the last sender report
      receptionReport
          .setLastSR((int) ((this.lastSenderReportNTPTimeStamp >> 16) & 0xFFFFFFFF));

      // the delay since the arrival of the last sender report in units
      // of 1 / 65536 seconds
      // 0 if no sender report has ben received

      receptionReport
          .setDelaySinceLastSR(this.lastSenderReportArrivalTime == 0.0 ? 0
              : (int) ((now - this.lastSenderReportArrivalTime) * 65536.0));

      return receptionReport;
    } else
      return null;
  }

  /**
   * Informs this RTPReceiverInfo that one rtcp interval has past.
   */
  public void nextInterval(long now) {
    this.inactiveIntervals++;
    if (this.inactiveIntervals == 5) {
      this.startOfInactivity = now;
    }
    this.highestSequenceNumberPrior = this.highestSequenceNumber
        + this.sequenceNumberCycles;
    this.packetsReceivedPrior = this.packetsReceived;
    super.nextInterval(now);
  }

  /**
   * Returns true if this rtp end system is regarded active.
   */
  protected boolean active() {
    return (this.inactiveIntervals < 5);
  }

  /**
   * Returns true if this rtp end system is regarded valid.
   */
  protected boolean valid() {
    return (this.itemsReceived >= 5);
  }

  /**
   * Returns true if this rtp end system should be deleted from the list of
   * known rtp session participant. This method should be called directly after
   * nextInterval().
   */
  public boolean toBeDeleted(long now) {
    // an rtp system should be removed from the list of known systems
    // when it hasn't been validated and hasn't been active for
    // 5 rtcp intervals or if it has been validated and has been
    // inactive for 30 minutes
    return (!valid() && !active())
        || (valid() && !active() && (now - this.startOfInactivity > 60.0 * 30.0));
  }

  /* (non-Javadoc)
   * @see java.lang.Object#hashCode()
   */
  public int hashCode() {
    final int PRIME = 31;
    int result = super.hashCode();
    result = PRIME * result + clockRate;
    result = PRIME * result + highestSequenceNumber;
    result = PRIME * result + highestSequenceNumberPrior;
    result = PRIME * result + inactiveIntervals;
    result = PRIME * result + itemsReceived;
    long temp;
    temp = Double.doubleToLongBits(jitter);
    result = PRIME * result + (int) (temp ^ (temp >>> 32));
    result = PRIME * result + (int) (lastPacketArrivalTime ^ (lastPacketArrivalTime >>> 32));
    result = PRIME * result + lastPacketRTPTimeStamp;
    result = PRIME * result + (int) (lastSenderReportArrivalTime ^ (lastSenderReportArrivalTime >>> 32));
    result = PRIME * result + (int) (lastSenderReportNTPTimeStamp ^ (lastSenderReportNTPTimeStamp >>> 32));
    result = PRIME * result + lastSenderReportRTPTimeStamp;
    result = PRIME * result + packetsReceived;
    result = PRIME * result + packetsReceivedPrior;
    result = PRIME * result + sequenceNumberBase;
    result = PRIME * result + sequenceNumberCycles;
    result = PRIME * result + (int) (startOfInactivity ^ (startOfInactivity >>> 32));
    return result;
  }

  /* (non-Javadoc)
   * @see java.lang.Object#equals(java.lang.Object)
   */
  public boolean equals(Object obj) {
    if (this == obj)
      return true;
    if (!super.equals(obj))
      return false;
    if (getClass() != obj.getClass())
      return false;
    final RtpReceiverInfo other = (RtpReceiverInfo) obj;
    if (clockRate != other.clockRate)
      return false;
    if (highestSequenceNumber != other.highestSequenceNumber)
      return false;
    if (highestSequenceNumberPrior != other.highestSequenceNumberPrior)
      return false;
    if (inactiveIntervals != other.inactiveIntervals)
      return false;
    if (itemsReceived != other.itemsReceived)
      return false;
    if (Double.doubleToLongBits(jitter) != Double.doubleToLongBits(other.jitter))
      return false;
    if (lastPacketArrivalTime != other.lastPacketArrivalTime)
      return false;
    if (lastPacketRTPTimeStamp != other.lastPacketRTPTimeStamp)
      return false;
    if (lastSenderReportArrivalTime != other.lastSenderReportArrivalTime)
      return false;
    if (lastSenderReportNTPTimeStamp != other.lastSenderReportNTPTimeStamp)
      return false;
    if (lastSenderReportRTPTimeStamp != other.lastSenderReportRTPTimeStamp)
      return false;
    if (packetsReceived != other.packetsReceived)
      return false;
    if (packetsReceivedPrior != other.packetsReceivedPrior)
      return false;
    if (sequenceNumberBase != other.sequenceNumberBase)
      return false;
    if (sequenceNumberCycles != other.sequenceNumberCycles)
      return false;
    if (startOfInactivity != other.startOfInactivity)
      return false;
    return true;
  }
}
