package brn.swans.app.rtp.profile;

import java.io.IOException;

import jist.runtime.JistAPI;
import jist.swans.Constants;
import jist.swans.misc.MessageBytes;
import jist.swans.misc.Util;
import brn.swans.app.rtp.RtpPayloadSender;
import brn.swans.app.rtp.msg.RtpMessage;

/**
 * The class RTPAVProfileSampleBasedAudioSender is a base class for modules
 * sending sample based audio as defined in the RTP audio/video profile. For
 * reading audio files it used libaudiofile. Subclasses must provide a method
 * initialize() to set parameters like sampling rate, sample width and number of
 * channels.
 * 
 * TODO implement seek
 */
public abstract class RtpAvProfileSampleBasedAudioSender extends RtpPayloadSender {


  protected static interface SendScheduler extends JistAPI.Proxiable {
    public void schedulePacket(int a_timerId);
  }
  
  public final class SendSchedulerImpl implements SendScheduler {

    /**
     * Timer id, used for canceling events.
     */
    protected int timerId;
    
    protected SendScheduler self;
    
    public SendSchedulerImpl() {
      this.timerId = 0;
      self = (SendScheduler) JistAPI.proxy(this, SendScheduler.class);
    }
    
    protected void cancelSend() {
      timerId++;
    }
    
    public void schedulePacket() {
      self.schedulePacket(timerId);
    }
    
    /**
     * Schedule packet for transmission.
     * 
     * @param timerId
     *          The current id of the timer.
     */
    public void schedulePacket(int timerId) {
      if (timerId != this.timerId)
        return;

      if (!sendPacket()) {
        endOfFile();
      }
    }

  }

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

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

  /**
   * File handle for the audio file.
   */
  // AFfilehandle _audioFile;
  /**
   * The time this sender module got initialized. Used to calculate time stamps.
   */
  protected long startTime;

  /**
   * The sampling rate of the audio. Must be set by subclasses in initialize().
   */
  protected int samplingRate;

  /**
   * The width of a sample of one channel in bits. Possibly values are 8, 16 and
   * 24. Must be set by subclasses in initialize().
   */
  protected int sampleWidth;

  /**
   * The number of different audio channels. Must be set by subclasses in
   * initialize().
   */
  protected int numberOfChannels;

  /**
   * Bytes per sample for random data.
   */
  protected int bytesPerSample;

  /**
   * AV profile: packetization interval for audio is 20 ms
   */
  protected long packetizationInterval;

  protected SendSchedulerImpl sendScheduler;

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

  public RtpAvProfileSampleBasedAudioSender() {
    super();
    this.startTime = JistAPI.getTime();
    this.packetizationInterval = 20 * Constants.MILLI_SECOND;

    this.sendScheduler = new SendSchedulerImpl();
  }

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

  // ////////////////////////////////////////////////
  // overwrites
  //
  
  public String toString() {
    return "RtpAvProfileSampleBasedAudioSender " +
      Util.timeSeconds();
  }

  protected void cancelSend() {
    this.sendScheduler.cancelSend();
  }

  protected void play() {

    if (status == RtpPayloadSender.STATE_STOPPED) {
      status = RtpPayloadSender.STATE_PLAYING;

      rtpProfile.senderModuleStatus(ssrc, RtpPayloadSender.STATE_PLAYING, 0);

      sendScheduler.schedulePacket();
    } else if (status == RtpPayloadSender.STATE_PLAYING) {
      throw new RuntimeException("sender module: already playing");
    }
  }

  protected void stop() {
    super.stop();
    // TODO
    // afSeekFrame(_audioFile, AF_DEFAULT_TRACK, 0);
  }

  protected void seekTime(long moment) {
    if (status != RtpPayloadSender.STATE_STOPPED)
      throw new RuntimeException("sender module: already playing");

    // TODO
    // int frameNumber = (int)(moment * (float)samplingRate);
    // afSeekFrame(_audioFile, AF_DEFAULT_TRACK, frameNumber);

    rtpProfile.senderModuleStatus(ssrc, RtpPayloadSender.STATE_SEEKED, 0);
  }

  protected void seekByte(int position) {
    if (status != RtpPayloadSender.STATE_STOPPED)
      throw new RuntimeException(
          "sender module: seeking not allowed while playing");

    // TODO
    // int frameNumber = (int)(((float)position) / afGetFrameSize(_audioFile,
    // AF_DEFAULT_TRACK, 0));
    // afSeekFrame(_audioFile, AF_DEFAULT_TRACK, frameNumber);

    rtpProfile.senderModuleStatus(ssrc, RtpPayloadSender.STATE_SEEKED, 0);
  }

  protected boolean sendPacket() {

    RtpMessage packet = new RtpMessage();
    int maxDataSize = mtu - packet.getHeaderLength();
    maxDataSize = maxDataSize - (maxDataSize % bytesPerSample);

    double packetIntv = packetizationInterval / (double) Constants.SECOND;
    int samplesPerPacketNeeded = (int) (packetIntv * samplingRate);
    int samplesPerPacketPossible = maxDataSize / bytesPerSample;

    int samplesInPacket = samplesPerPacketNeeded;

    // do samples for packetization interval fit into one packet?
    // if not put less samples in a packet
    if (samplesPerPacketPossible < samplesPerPacketNeeded) {
      packetizationInterval = (long) ((double) samplesPerPacketPossible
          / (double) samplingRate * (double) Constants.SECOND);
      samplesInPacket = samplesPerPacketPossible;
    }

    int dataSize = samplesInPacket * bytesPerSample;

    packet.setPayloadType(payloadType);
    packet.setSequenceNumber(sequenceNumber++);
    packet.setTimeStamp(timeStampBase
        + (int) ((JistAPI.getTime() - startTime) * (double) samplingRate 
            / (double) Constants.SECOND));
    packet.setSsrc(ssrc);

    byte[] sampleData = new byte[dataSize];
    int read = 0;
    try {
      read = input.read(sampleData);
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
    boolean dataAvailable = (read != -1);

    if (dataAvailable) {
      if (read != dataSize) {
        byte[] b = new byte[read];
        System.arraycopy(sampleData, 0, b, 0, b.length);
        sampleData = b;
      }
      
      packet.setPayload(new MessageBytes(sampleData));
  
      rtpProfile.dataOut(packet);

      JistAPI.sleep(packetizationInterval);
      sendScheduler.schedulePacket();
    }

    return dataAvailable;
  }

  // ////////////////////////////////////////////////
  // Internals
  //

}
