package brn.swans.app.rtp.profile;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

import org.apache.log4j.Logger;

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;
import brn.swans.app.rtp.msg.RtpMpegMessage;

/**
 * An RTPAVProfilePayload32Sender is a module for sending data of payload type
 * 32 in the rtp audio/video profile, which is mpeg video. This implementation
 * doesn't send real mpeg data it just reads the gdf file created by Mpeg_Stat
 * and sends rtp data packets which contain an RTPMpegPacket. The corresponding
 * receiver module RTPAVProfilePayload32Receiver.
 */
public class RtpAvProfilePayload32Sender extends RtpPayloadSender {

  /**
   * Logger.
   */
  public static final Logger log = Logger.getLogger(RtpAvProfilePayload32Sender.class.getName());


  /**
   * The initial delay of the mpeg video.
   */
  protected long initialDelay;

  /**
   * The number of frames per second of the mpeg video.
   */
  protected double framesPerSecond;

  /**
   * The number of the current mpeg frame. Needed for calculating the rtp time
   * stamp in the rtp data packets.
   */
  protected double frameNumber;

  /**
   * Reader
   */
  protected class Input extends RtpPayloadSender.Input {
    BufferedReader reader = null;;
    
    public Input(RtpPayloadSender.Input input) {
      this.inputStream = input.inputStream;
      reader = new BufferedReader(new InputStreamReader(super.inputStream));
    }

    public String readLine() throws IOException {
      return reader.readLine();
    }

    public boolean ready() throws IOException {
      return reader.ready();
    }
  }
  
  protected Input input;

  protected SendSchedulerImpl scheduler;

  /**
   * Default constructor
   */
  public RtpAvProfilePayload32Sender() {
    super();
    
    scheduler = new SendSchedulerImpl();

    payloadType = 32;
    clockRate = 90000;
  }

  /*
   * (non-Javadoc)
   * 
   * @see brn.swans.app.rtp.RtpPayloadSender#sendPacket()
   */
  protected boolean sendPacket() {

    // read next frame line
    int bits;
    String unit;
    String description;

    try {
      String line = input.readLine();
      if (null == line)
        throw new RuntimeException("Input is null");
      
      StringTokenizer tokens = new StringTokenizer(line);
      bits = Integer.parseInt(tokens.nextToken());
      unit = tokens.nextToken();
      description = tokens.nextToken();
      if (tokens.hasMoreTokens())
        description += tokens.nextToken("\n");

      int pictureType = 0;

      switch (description.charAt(0)) {
      case 'I':
        pictureType = 1;
        break;
      case 'P':
        pictureType = 2;
        break;
      case 'B':
        pictureType = 3;
        break;
      case 'D':
        pictureType = 4;
        break;
      default:
        throw new RuntimeException("Unknown frame type given: " + description.charAt(0));
      }
      
      if (log.isDebugEnabled())
        log.debug(this + " read "+frameNumber+". frame, "+description+" "+bits+" "+unit);

      int bytesRemaining = bits / 8;

      while (bytesRemaining > 0) {
        RtpMessage rtpPacket = new RtpMessage();
        RtpMpegMessage mpegPacket = new RtpMpegMessage();

        // the only mpeg information we know is the picture type
        mpegPacket.setPictureType(pictureType);

        // the maximum number of real data bytes
        int maxDataSize = mtu - rtpPacket.getSize() - mpegPacket.getSize();

        if (bytesRemaining > maxDataSize) {

          // we do not know where slices in the
          // mpeg picture begin
          // so we simulate by assuming a slice
          // has a length of 64 bytes
          int slicedDataSize = (maxDataSize / 64) * 64;
          byte[] data = new byte[slicedDataSize];
          Constants.random.nextBytes(data);

          mpegPacket.setPayload(new MessageBytes(data));
          rtpPacket.setPayload(mpegPacket);

          bytesRemaining = bytesRemaining - slicedDataSize;
        } else {
          byte[] data = new byte[bytesRemaining];
          Constants.random.nextBytes(data);

          mpegPacket.setPayload(new MessageBytes(data));
          rtpPacket.setPayload(mpegPacket);

          // set marker because this is
          // the last packet of the frame
          rtpPacket.setMarker(true);
          bytesRemaining = 0;
        }

        rtpPacket.setPayloadType(payloadType);
        rtpPacket.setSequenceNumber(sequenceNumber);
        sequenceNumber++;

        rtpPacket.setTimeStamp((int) (timeStampBase + (initialDelay + (1 / framesPerSecond)
            * (double) frameNumber) * clockRate));
        rtpPacket.setSsrc(ssrc);

        if (log.isDebugEnabled())
          log.debug(this + " sending packet " + rtpPacket + ", remaining " + bytesRemaining);

        this.rtpProfile.dataOut(rtpPacket);
      }
      frameNumber++;

      boolean dataAvailable = input.ready();
      
      if (dataAvailable) {
        JistAPI.sleep((long) (1.0 / framesPerSecond * (double) Constants.SECOND));
        scheduler.schedulePacket();
      }
      
      return dataAvailable;
    } catch (IOException e) {
      // TODO Auto-generated catch block
      throw new RuntimeException(e);
    }
  }

  /*
   * (non-Javadoc)
   * @see brn.swans.app.rtp.RtpPayloadSender#cancelSend()
   */
  protected void cancelSend() {
    this.scheduler.cancelSend();
  }

  /**
   * Scheduler interface for the mpeg sender.
   * @author kurth
   *
   */
  protected static interface SendScheduler extends JistAPI.Proxiable {
    public void schedulePacket(int timerId);
  }

  /**
   * Scheduler implementation for the mpeg sender.
   * @author kurth
   *
   */
  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();
      }
    }

  }

  /*
   * (non-Javadoc)
   * @see brn.swans.app.rtp.RtpPayloadSender#initializeSenderModule(int, java.lang.String, int)
   */
  public void initializeSenderModule(int ssrc, String fileName, int mtu) {
    super.initializeSenderModule(ssrc, fileName, mtu);

    // first line: fps unit description
    try {
//      String unit;
//      String description;

      input = new Input(super.input);
      StringTokenizer tokens = new StringTokenizer(input.readLine());

      framesPerSecond = Float.parseFloat(tokens.nextToken());
//      unit = tokens.nextToken();
//      description = tokens.nextToken();

      frameNumber = 0;

      // second line: initial delay unit description
      tokens = new StringTokenizer(input.readLine());

      initialDelay = (long)(Float.parseFloat(tokens.nextToken()) * Constants.SECOND);
//      unit = tokens.nextToken();
//      description = tokens.nextToken();

      if (log.isDebugEnabled())
        log.debug(this + " opened " + fileName + ", fps " + framesPerSecond + 
            ", initDelay " + initialDelay);
      
    } catch (IOException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }

    // wait initial delay
//    JistAPI.sleep(initialDelay);
//    scheduler.schedulePacket();
  }

  /*
   * (non-Javadoc)
   * @see java.lang.Object#toString()
   */
  public String toString() {
    return //"RtpProfile " + 
    Util.timeSeconds();
  }
}
