package nxm.net.lib;

import nxm.sys.inc.InternalUseOnly;
import nxm.sys.inc.ProvisionalUseOnly;
import nxm.sys.lib.DataFile;

/** Holds RTP Packets in a cache to handle out-of-order packets
 *  <br>
 *  Note 1: Currently this class is not used independently, but only via
 *  RTPICEPacketsCache.
 *  <br>
 *  Note 2: This class may eventually be expanded to handle re-ordering of
 *  packet streams that do not have any ICE Headers.
 *  We may not allow a fill option, as that is generally associated with frames.
 *  If we did, we would need to know that the data per packet was always a
 *  constant.
 *
 * @author Jeanette McCall
 * @since  NeXtMidas 3.8.1
 *
 */
public class RTPPacketsCache {

  /** Default Packet Cache Size */
  final static int DEFAULT_PACKET_CACHE_SIZE = 8;
  final static double LARGEST_RANGE_BEFORE_FILL_AS_FRACTION_CACHE = 0.75;
  final static byte DEFAULT_MISSING_PACKET_FILL_VALUE = 0;  // good for I/Q data

  /** Value is not intialized */
  protected final static int NOT_INITIALIZED = -1;

  /** Maximum sequence range saving before assuming packets are missing and starting
   *  the fill and write process to clear older cached packets
   */
  volatile int maxAheadBeforeAssumeSomeMissing;

  /** The size of the circular buffers */
  int cacheSize;
  DatagramData dgd;
  byte[][] storeBuf; // saved circular buffer of read buffers (i.e. buf)
  long[] packetInStore; // packet number stored at each index in data store
  boolean[] viablePackets; // true if data is still under consideration for being put out
  protected DataFile pipeOut; // pipe outputrtppacket May need to create a way to reset this

  /** sequence number of lowest valid packet */
  int lowestValidPkt = NOT_INITIALIZED;
  /** sequence number of highest valid packet */
  int highestPkt = NOT_INITIALIZED;
  /** sequence number of last packet output */
  int lastPacketOutput = NOT_INITIALIZED;

  /** Fill value to use for missing data */
  protected byte missingFillValue = DEFAULT_MISSING_PACKET_FILL_VALUE;  // Good for I/Q - subclasses can change

/** Creates an RTP Packets Cache to hold on to packets in hopes of reordering them
 *  @param dgd     the DatagramData instance
 *  @param pipeOut the output pipe
 */
  @ProvisionalUseOnly("API may change")
  public RTPPacketsCache (DatagramData dgd, DataFile pipeOut) {
    this(dgd, pipeOut, DEFAULT_PACKET_CACHE_SIZE);
  }

  /** Creates an RTP Packets Cache to hold on to packets in hopes of reordering them
   *  @param dgd          the DatagramData instance
   *  @param pipeOut      the output pipe
   *  @param numPktsStore cache size (max number of packets in cache)
   */
  @ProvisionalUseOnly("API may change")
  public RTPPacketsCache (DatagramData dgd, DataFile pipeOut, int numPktsStore) {
    this(dgd, pipeOut, DEFAULT_PACKET_CACHE_SIZE, DEFAULT_MISSING_PACKET_FILL_VALUE);
  }

  /** Creates an RTP Packets Cache to hold on to packets in hopes of reordering them
   *  @param dgd            the DatagramData instance
   *  @param pipeOut        the output pipe
   *  @param numPktsStore   cache size (max number of packets in cache)
   *  @param frameFillValue value to use when filling in missing data
   */
  @ProvisionalUseOnly("API may change")
  public RTPPacketsCache (DatagramData dgd, DataFile pipeOut, int numPktsStore, byte frameFillValue) {
    this.dgd = dgd;
    this.pipeOut = pipeOut;
    this.cacheSize = numPktsStore;
    this.missingFillValue = frameFillValue;
    storeBuf = new byte[numPktsStore][];
    viablePackets = new boolean[numPktsStore];
    packetInStore = new long[numPktsStore];
    maxAheadBeforeAssumeSomeMissing = (int)(numPktsStore * LARGEST_RANGE_BEFORE_FILL_AS_FRACTION_CACHE);
  }

  /** Decrement and wrap the cache index
   * @param index current index
   * @return index that has been decremented (and wrapped if need be)
   */
  @ProvisionalUseOnly("API may change")
  public int decrementAndWrapIndex(int index) {
    return index == 0 ? cacheSize -1 : index -1;
  }

  /** Get the value we are using as the maximum ahead a packet can be over
   *  the last output before we assume some packets are missing
   *  @return the maxAheadBeforeAssumeSomeMissing maximum ahead a packet can be
   *                                              before we assume aome are missing
   */
  @ProvisionalUseOnly("API may change")
  public int getMaxAheadBeforeAssumeSomeMissing () {
    return maxAheadBeforeAssumeSomeMissing;
  }

  /** Set the maximum packets ahead of the last output before we assume some
   *  packets are missing
   *  @param maxAheadBeforeAssumeSomeMissing max number packets ahead before
   *                                         assume some are missing
   */
  @ProvisionalUseOnly("API may change")
  public void setMaxAheadBeforeAssumeSomeMissing (int maxAheadBeforeAssumeSomeMissing) {
    this.maxAheadBeforeAssumeSomeMissing = Math.min(cacheSize,maxAheadBeforeAssumeSomeMissing);
  }

  /** Set the output pipe
   *  @param pipeOut the output pipe
   */
  @InternalUseOnly
  public void setPipeOut (DataFile pipeOut) {
    this.pipeOut = pipeOut;
  }

  /** Get the output pipe
   * @return the output pipe
   */
  @InternalUseOnly
  public DataFile getPipeOut () {
    return pipeOut;
  }

  /** Set fill value used for missing packets
   *  @param missingFillValue values to use in fill
   */
  public void setMissingFillValue (byte missingFillValue) {
    this.missingFillValue = missingFillValue;
  }
}
