package brn.swans.app.rtp;

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

import jist.runtime.JistAPI;
import jist.swans.Constants;

import org.apache.log4j.Logger;

/**
 * The class RTPPayloadSender is the base class for all modules creating rtp
 * data packets. It provides functionality needed by every rtp data packet
 * sender like opening and closing the data file and choosing sequence number
 * and time stamp start values.
 */
public abstract class RtpPayloadSender {

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

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

  /**
   * No transmission.
   */
  public static final int STATE_STOPPED = 0;

  /**
   * Data is being sent.
   */
  public static final int STATE_PLAYING = 1;

  /**
   * Finished.
   */
  public static final int STATE_FINISHED = 2;

  public static final int STATE_PAUSED = 3;

  public static final int STATE_SEEKED = 4;

  public static final int CMD_PLAY = 0;

  public static final int CMD_STOP = 1;

  public static final int CMD_PLAY_UNTIL_TIME = 2;

  public static final int CMD_PLAY_UNTIL_BYTE = 3;

  public static final int CMD_PAUSE = 4;

  public static final int CMD_SEEK_TIME = 5;

  public static final int CMD_SEEK_BYTE = 6;

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

  protected class Input implements JistAPI.DoNotRewrite {
    /**
     * The input file stream for the data file.
     */
    public java.io.InputStream inputStream;

    /**
     * This method is called by initializeSenderModule and opens the source data
     * file as an inputFileStream stored in member variable this.inputFileStream.
     * Most data formats can use this method directly, but when using a library
     * for a certain data format which offers an own open routine this method must
     * be overwritten.
     */
    public void openSourceFile(String fileName) throws FileNotFoundException {
      if (null != fileName)
        inputStream = new BufferedInputStream(new FileInputStream(fileName));
    }

    /**
     * This method is called by the destructor and closes the data file.
     */
    public void closeSourceFile() throws IOException {
      if (null != inputStream)
        inputStream.close();
    }
    
    public int read(byte[] b) throws IOException {
      if (null == inputStream) {
        Constants.random.nextBytes(b);
        return (b.length);
      }
      return inputStream.read(b);
    }

  }
  protected Input input = new Input();;

  /**
   * The maximum size of an RTPPacket.
   */
  protected int mtu;

  /**
   * The ssrc identifier of this sender module.
   */
  protected int ssrc;

  /**
   * The payload type this sender creates.
   */
  protected byte payloadType;

  /**
   * The clock rate in ticks per second this sender uses.
   */
  protected int clockRate;

  /**
   * The first rtp time stamp used for created rtp data packets. The value is
   * chosen randomly (unsigned).
   */
  protected int timeStampBase;

  /**
   * The current rtp time stamp (unsigned).
   */
  protected int timeStamp;

  /**
   * The first sequence number used for created rtp data packets. The value is
   * chosen randomly (unsigned).
   */
  protected short sequenceNumberBase;

  /**
   * The current sequence number (unsigned).
   */
  protected short sequenceNumber;

  /**
   * The current state of data transmission.
   */
  protected int status;

  /**
   * Entitiy reference to the rtp profile.
   */
  protected RtpProfile rtpProfile;

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

  public RtpPayloadSender() {
    this.mtu = 0;
    this.ssrc = 0;
    this.payloadType = 0;
    this.clockRate = 0;
    this.timeStampBase = Constants.random.nextInt(Short.MAX_VALUE);
    this.timeStamp = this.timeStampBase;
    this.sequenceNumberBase = (short) Constants.random.nextInt();
    this.sequenceNumber = this.sequenceNumberBase;
  }

  // ////////////////////////////////////////////////
  // entity hookup
  //

  /**
   * Hook up with the rtp profile entity.
   * 
   * @param profile
   *          profile entity
   */
  public void setProfile(RtpProfile profile) {
    this.rtpProfile = profile;
  }

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

  public int getClockRate() {
    return clockRate;
  }

  public int getTimeStamp() {
    return timeStamp;
  }

  public int getTimeStampBase() {
    return timeStampBase;
  }

  public short getSequenceNumberBase() {
    return sequenceNumberBase;
  }


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

  // ////////////////////////////////////////////////
  // Self-messages
  //

  /**
   * This method gets called when one (or more) rtp data packets have to be
   * sent. Subclasses must overwrite this method to do something useful. This
   * implementation doesn't send packets it just returns false which means end
   * of file reached.
   */
  protected abstract boolean sendPacket();

  protected abstract void cancelSend();

  // ////////////////////////////////////////////////
  // Called by the rtp profile
  //

  /**
   * This method is called when a newly create sender module received its
   * initialization message from profile module. It returns an
   * RTP_INP_SENDER_MODULE_INITIALIZED message which contains information needed
   * by the rtcp module.
   */
  public void initializeSenderModule(int ssrc, String fileName, int mtu) {
    this.mtu = mtu;
    this.ssrc = ssrc;
    try {
      openSourceFile(fileName);
    } catch (FileNotFoundException e) {
      // TODO Auto-generated catch block
      throw new RuntimeException(e);
    }
  }

  public void senderModuleControl(int ssrc, int command, long arg1) {

    switch (command) {
    case CMD_PLAY:
      play();
      break;
    case CMD_PLAY_UNTIL_TIME:
      playUntilTime(arg1);
      break;
    case CMD_PLAY_UNTIL_BYTE:
      playUntilByte((int) arg1);
      break;
    case CMD_PAUSE:
      pause();
      break;
    case CMD_STOP:
      stop();
      break;
    case CMD_SEEK_TIME:
      seekTime(arg1);
      break;
    case CMD_SEEK_BYTE:
      seekByte((int) arg1);
      break;
    default:
      throw new RuntimeException("Unknown sender control message");
    }
  }

  // ////////////////////////////////////////////////
  // internals
  //

  /**
   * This method is called by initializeSenderModule and opens the source data
   * file as an inputFileStream stored in member variable this.inputFileStream.
   * Most data formats can use this method directly, but when using a library
   * for a certain data format which offers an own open routine this method must
   * be overwritten.
   */
  protected void openSourceFile(String fileName) throws FileNotFoundException {
    input.openSourceFile(fileName);
  }

  /**
   * This method is called by the destructor and closes the data file.
   */
  protected void leaveSession() {
    try {
      input.closeSourceFile();
    } catch (IOException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }

  /**
   * Starts data transmission. Every sender module must implement this method.
   */
  protected void play() {
    this.status = STATE_PLAYING;

    // "PLAYING"
    rtpProfile.senderModuleStatus(ssrc, RtpPayloadSender.STATE_PLAYING,
        timeStamp);

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

  /**
   * Starts transmission from the current file position and plays until given
   * time (relative to start of file) is reached. Implementation in sender
   * modules is optional.
   */
  protected void playUntilTime(long moment) {
    throw new RuntimeException("playUntilTime() not implemented");
  }

  /**
   * Starts transmission from the current file position and plays until given
   * byte position (excluding file header) is reached. Implementation in sender
   * modules is optional.
   */
  protected void playUntilByte(int position) {
    throw new RuntimeException("playUntilByte() not implemented");
  }

  /**
   * When data is being transmitted this methods suspends till a new PLAY
   * command. Implementation in sender modules is optional.
   */
  protected void pause() {
    cancelSend();
    this.status = STATE_STOPPED;

    rtpProfile.senderModuleStatus(ssrc, STATE_PAUSED, timeStamp);
  }

  /**
   * When the data transmission is paused the current position is changed to
   * this time (relative to start of file). Implementation in sender modules is
   * optional.
   */
  protected void seekTime(long moment) {
    throw new RuntimeException("seekTime() not implemented");
  }

  /**
   * When the data transmission is paused the current position is changed to
   * this byte position (excluding file header). Implementation in sender
   * modules is optional.
   */
  protected void seekByte(int position) {
    throw new RuntimeException("seekByte() not implemented");
  }

  /**
   * This method stop data transmission and resets the sender module so that a
   * following PLAY command would start the transmission at the beginning again.
   * Every sender module should implement this method.
   */
  protected void stop() {
    cancelSend();
    this.status = STATE_STOPPED;

    rtpProfile.senderModuleStatus(ssrc, STATE_STOPPED, timeStamp);
  }

  /**
   * This method gets called when the sender module reaches the end of file. For
   * the transmission it has the same effect like stop().
   */
  protected void endOfFile() {
    this.status = STATE_STOPPED;

    rtpProfile.senderModuleStatus(ssrc, STATE_FINISHED, timeStamp);
  }

}