package brn.swans.app.rtp;

import java.util.Vector;

import jist.runtime.JistAPI;
import jist.runtime.JistAPI.Continuation;
import jist.swans.Constants;
import jist.swans.misc.Message;
import jist.swans.misc.MessageAnno;
import jist.swans.misc.Util;
import jist.swans.net.NetAddress;
import jist.swans.trans.TransInterface;
import jist.swans.trans.TransInterface.TransUdpInterface;

import org.apache.log4j.Logger;

import brn.swans.app.rtcp.RtcpEndsystemModule;
import brn.swans.app.rtp.msg.RtpMessage;

/**
 * An RTPEndsystemModule is the center of the rtp layer of an endsystem. It
 * creates the profile module, sends and receives rtp data packets and forwards
 * messages. It also communicates with the application.
 */
public class RtpEndsystemModule {

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

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

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

  /**
   * The CNAME of this end system.
   */
   private String commonName;
  /**
   * The name of the profile used in this session.
   */
  // private String profileName;
  /**
   * The available bandwidth for this session.
   */
  // private int bandwidth;
  /**
   * The maximum size of a packet.
   */
  private int mtu;

  /**
   * The percentage of the bandwidth used for rtcp.
   */
  // private int rtcpPercentage;
  /**
   * The rtp server socket file descriptor.
   */
  // NetAddress socketAddrServer;
  private int socketPortIn;

  /**
   * The rtp client socket file descriptor.
   */
  private NetAddress socketAddrOut;

  private int socketPortOut;

  private RtpProfile rtpProfile;

  private RtcpEndsystemModule rtcpModule;

  private RtpApplication rtpApp;

  private TransInterface.TransUdpInterface udpEntity;

  /** flow id for viz purposes */
  private int flowId = 0;
  /** packet id for viz purposes */
  private int packetId = 0;

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

  public RtpEndsystemModule() {

    // hookup
    rtcpModule = new RtcpEndsystemModule();
    rtcpModule.setRtpModuleEntity(this);
  }

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

  /**
   * Hook up with the app.
   *
   * @param app
   *          app entity
   */
  public void setApp(RtpApplication app) {
    this.rtpApp = app;
  }

  /**
   * Hook up with the transport entity.
   *
   * @param udpEntity
   *          udp entity
   */
  public void setUdpEntity(TransUdpInterface udpEntity) {
    if (!JistAPI.isEntity(udpEntity))
      throw new IllegalArgumentException("expected entity");

    this.udpEntity = udpEntity;
    rtcpModule.setUdpEntity(udpEntity);
  }

  public void setFlowId(int flowId) {
    this.flowId = flowId;
  }


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

  /**
   * Determines the maximum transmission unit that can be uses for rtp. This
   * implementation assumes that we use an ethernet with 1500 bytes mtu. The
   * returned value is 1500 bytes minus header sizes for ip and udp.
   */
  private static int resolveMTU() {
    // this is not what it should be
    // do something like mtu path discovery
    // for the simulation we can use this example value
    // it's 1500 bytes (ethernet) minus ip
    // and udp headers
    return 1500 - 20 - 8;
  };

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

  public String toString() {
    return //"RtpEndsystemModule:" +
      commonName + " " + Util.timeSeconds();
  }


  // ////////////////////////////////////////////////
  // Called by the rtp application
  //

  /**
   * Creates the profile module and initializes it.
   */
  public void enterSession(String commonName, String profileName,
      int bandwidth, NetAddress destAddr, short destPort, short srcPort) {

    // this.profileName = profileName;
     this.commonName = commonName;
    // this.bandwidth = bandwidth;
    this.socketAddrOut = destAddr;
    this.socketPortOut = destPort - destPort % 2;
    this.socketPortIn = srcPort - srcPort % 2;

    if (log.isDebugEnabled())
      log.debug(this + " enter session");

    mtu = resolveMTU();

    Vector prefixes = PackageManager.getProfilePrefixList();

    // load the profile via reflection
    for (int i = 0; null == rtpProfile && i < prefixes.size(); i++) {
      String className = prefixes.get(i) + ".rtp.profile." + profileName;

      try {
        Class classProfile = Class.forName(className);
        rtpProfile = (RtpProfile) classProfile.newInstance();
      } catch (ClassNotFoundException e) {
      } catch (InstantiationException e) {
      } catch (IllegalAccessException e) {
      }
    }

    if (null == rtpProfile) {
      throw new RuntimeException("Could not find profile " + profileName);
    }

    if (log.isDebugEnabled())
      log.debug(this + " loaded profile "
          + profileName + " from class " + rtpProfile.getClass());

    // rtpProfile.setAutoOutputFileNames(true);
    // rtpProfile.setMaxReceivers(maxReceivers);
    rtpProfile.setRtpModule(this);
    rtpProfile.initializeProfile(mtu);

    if (log.isDebugEnabled())
      log.debug(this + " (" + JistAPI.getTime()
          + "): adding socket handler on " + "port " + this.socketPortIn);

    // Create socket listener
    udpEntity.addSocketHandler(this.socketPortIn, new SocketHandler());

    if (log.isDebugEnabled())
      log.debug(this + " initializing rtcp");

    // initialize the rtcp
    rtcpModule.initializeRTCP(commonName, mtu, bandwidth, rtpProfile
        .getRtcpPercentage(), socketAddrOut, socketPortOut + 1);

    if (log.isDebugEnabled())
      log.debug(this + " rtcp initialized");

    rtpApp.sessionEntered(rtcpModule.getSsrc());
  }


  /**
   * Destroys the profile module and orders the rtcp module to send an rtcp bye
   * packet.
   */
  public void leaveSession() {
    rtpProfile.leaveSession();
    //rtpProfile = null;
    rtcpModule.leaveSession();
  }

  public void createSenderModule(int ssrc, int payloadType, String fileName) {

    if (log.isDebugEnabled())
      log.debug(this + " (" + JistAPI.getTime()
          + "): creating sender module for " + " payloadtype " + payloadType);

    rtpProfile.createSenderModule(ssrc, payloadType, fileName);

    rtcpModule.senderModuleInitialized(rtpProfile.getClockRate(), rtpProfile
        .getTimeStampBase(), rtpProfile.getSequenceNumberBase());
  }

  public void senderModuleControl(int ssrc, int command, long arg) {
    rtpProfile.senderModuleControl(ssrc, command, arg);
  }

  // ////////////////////////////////////////////////
  // Called from profile
  //

  public void senderModuleStatus(int ssrc, int status, int timeStamp) {
    rtpApp.senderModuleStatus(ssrc, status, timeStamp);
  }

  /**
   * Sends a rtp data packet to the socket layer and a copy of it to the rtcp
   * module.
   */
  public void dataOut(Message msg) {

    if (log.isDebugEnabled())
      log.debug(this + " sending data [" + msg
          + "] to [" + socketAddrOut + "," + socketPortOut + "]");

    MessageAnno anno = new MessageAnno();
    anno.put(MessageAnno.ANNO_RTG_FLOWID, flowId);
    anno.put(MessageAnno.ANNO_RTG_PACKETID, packetId++);

    udpEntity.send(msg, anno, socketAddrOut, socketPortOut, socketPortIn,
        Constants.NET_PRIORITY_D_VOICE);

    // RTCP module must be informed about sent rtp data packet
    rtcpModule.dataOut(msg, socketPortIn);
  }

  // ////////////////////////////////////////////////
  // Called from rtcp
  //

  /**
   * Informs the application that this end system has left the rtp session.
   */
  public void sessionLeft() {
    if (log.isDebugEnabled())
      log.debug(this + " session left");

    rtpApp.sessionLeft();
  }

  // ////////////////////////////////////////////////
  // Called from socket handler (udp)
  //

  public class SocketHandler implements TransInterface.SocketHandler {

    /**
     * Called when data from the socket layer has been received.
     */
    public void receive(Message msg, MessageAnno anno, NetAddress src, int srcPort)
        throws Continuation {
      RtpMessage rtpMsg = RtpMessage.fromBytes(msg);
      rtpMsg.setArrivalTime(JistAPI.getTime());

      if (log.isDebugEnabled())
        log.debug(this + " received data [" + rtpMsg
            + "] from [" + src + "," + srcPort + "]");

      rtpProfile.dataIn(rtpMsg, src, srcPort);
      rtcpModule.dataIn(rtpMsg, src, srcPort);
    }

    public String toString() {
      return "RtpEndsystemModule " + commonName + " " + Util.timeSeconds();
    }

  }

}
