package brn.swans.app.rtp;

import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Vector;

import jist.swans.misc.Message;
import jist.swans.misc.Util;
import jist.swans.net.NetAddress;

import org.apache.log4j.Logger;

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

/**
 * The class RTPProfile is a module which handles RTPPayloadSender and
 * RTPPayloadReceiver modules. It creates them dynamically on demand. This class
 * offers all functionality for the above tasks, subclasses just need to set
 * variables like profile name, rtcp percentage and preferred port in their
 * initialize() method. The dynamically created sender and receiver modules must
 * have have following class names: * Rtp<profileName>Payload<payloadType>Sender *
 * Rtp<profileName>Payload<payloadType>Receiver
 */
public abstract class RtpProfile {

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


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

  /**
   * If this is set true the RTPProfile automatically sets the output file name
   * for payload receiver modules so the user is not bothered to set them
   * manually during simulation runtime.
   */
  protected static boolean autoOutputFileNames = true;


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

  /**
   * The name of this profile. Needed for dynamic creating of sender and
   * receiver modules.
   */
  protected String profileName;

  /**
   * The maximum number of incoming data streams this profile module can handle.
   * It is set to the gate size of "toPayloadReceiver", "fromPayloadReceiver".
   */
  // protected int maxReceivers;
  /**
   * Stores information to which gate rtp data packets from a ssrc must be
   * forwarded.
   */
  // protected List ssrcGates;
  /**
   * The percentage of the available bandwidth to be used for rtcp.
   */
  protected int rtcpPercentage;

  /**
   * The rtp port this profile uses if no port is given.
   */
  protected int preferredPort;

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

  /**
   * Maps ssrcs to payload receivers.
   */
  private Map mapSsrcToReceiver;

  /**
   * Entity reference to the rtp module.
   */
  protected RtpEndsystemModule rtpModule;

  /**
   * Entity reference to the sender entity.
   */
  protected RtpPayloadSender rtpPayloadSender;

  /**
   * Entity reference to the receiver entity.
   */
  // protected RtpPayloadReceiver rtpPayloadReceiverEntity;
  // ////////////////////////////////////////////////
  // initialization
  //
  public RtpProfile() {

    this.profileName = "Profile";
    this.rtcpPercentage = 5;
    this.preferredPort = 0;

    // how many gates to payload receivers do we have
    // this.maxReceivers = 0;
    // this.ssrcGates = new ArrayList();

    this.mapSsrcToReceiver = new HashMap();
  }

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

  /**
   * Hook up with the rtp entity.
   *
   * @param module
   *          udp entity
   */
  public void setRtpModule(RtpEndsystemModule module) {
    this.rtpModule = module;
  }

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

  public static boolean isAutoOutputFileNames() {
    return autoOutputFileNames;
  }

  public static void setAutoOutputFileNames(boolean autoOutputFileNames) {
    RtpProfile.autoOutputFileNames = autoOutputFileNames;
  }

  public int getRtcpPercentage() {
    return rtcpPercentage;
  }

  public int getClockRate() {
    return rtpPayloadSender.getClockRate();
  }

  public short getSequenceNumberBase() {
    return rtpPayloadSender.getSequenceNumberBase();
  }

  public int getTimeStampBase() {
    return rtpPayloadSender.getTimeStampBase();
  }

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

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


  // ////////////////////////////////////////////////
  // Called by the rtp module
  //

  /**
   * Initialization message received from rtp module.
   */
  public void initializeProfile(int mtu) {
    this.mtu = mtu;
  }

  /**
   * This method is called when the application issued the creation of an rtp
   * payload sender module to transmit data. It creates a new sender module and
   * connects it. The profile module informs the rtp module of having finished
   * this task. Then it initializes the newly create sender module with a
   * inititalizeSenderModule message.
   */
  /**
   * The sender module returns a senderModuleInitialized message after being
   * initialized. The profile module forwards this message to the rtp module
   * which delivers it to its destination, the rtcp module.
   */
  public void createSenderModule(int ssrc, int payloadType, String fileName) {
    String moduleName = "Rtp" + profileName + "Payload" + payloadType
        + "Sender";

    Vector prefixes = PackageManager.getProfilePrefixList();

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

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

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

    rtpPayloadSender.setProfile(this);
    rtpPayloadSender.initializeSenderModule(ssrc, fileName, mtu);
  }

  /**
   * The profile module forwards sender control messages to the sender module.
   */
  public void senderModuleControl(int ssrc, int command, long arg) {
    rtpPayloadSender.senderModuleControl(ssrc, command, arg);
  }

  /**
   * Handles incoming data packets: If there isn't a receiver module for this
   * sender it creates one. The data packet is forwarded to the receiver module
   * after calling processIncomingPacket.
   */
  public void dataIn(Message msg, NetAddress src, int srcPort) {

    if (log.isDebugEnabled())
      log.debug(this + " received packet " + msg);

    processIncomingPacket(msg);

    RtpMessage packet = (RtpMessage) msg;

    RtpPayloadReceiver recv = (RtpPayloadReceiver) mapSsrcToReceiver
        .get(new Integer(packet.getSsrc()));

    if (null == recv) {
      String moduleName = "Rtp" + profileName + "Payload"
          + packet.getPayloadType() + "Receiver";

      Vector prefixes = PackageManager.getProfilePrefixList();

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

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

      if (null == recv) {
        throw new RuntimeException("Could not find profile " + profileName);
      }
      recv.setProfile(this);
      mapSsrcToReceiver.put(new Integer(packet.getSsrc()), recv);

      if (autoOutputFileNames) {
        // TODO generate unique names for multicast
        recv.setOutputFileName("rtp-recv-ssrc-" + packet.getSsrc() + ".out");
      }
    }

    recv.dataIn(msg, src, srcPort);
  }

  // ////////////////////////////////////////////////
  // Called by the rtp payload sender
  //


  /**
   * After having received a sender module control message the sender module
   * returns a sender status message to inform the application what it's doing
   * at the moment.
   */
  public void senderModuleStatus(int ssrc, int status, int timeStamp) {
    rtpModule.senderModuleStatus(ssrc, status, timeStamp);
  }

  /**
   * Handles outgoing data packets: Calls processOutgoingPacket and forwards the
   * packet to the rtp module.
   */
  public void dataOut(Message msg) {

    if (log.isDebugEnabled())
      log.debug(this + " sending packet " + msg);

    rtpModule.dataOut(msg);
  }

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

  /**
   * Every time a rtp packet is received it it pre-processed by this method to
   * remove profile specific extension which are not handled by the payload
   * receiver module. In this implementation the packet isn't changed.
   * Important: This method works with RTPInnerPacket. So the rtp packet must be
   * decapsulated, changed and encapsulated again.
   */
  protected void processIncomingPacket(Message msg) {
    // do nothing with the packet
  }

  /**
   * Simular to the procedure for incoming packets, this adds profile specific
   * extensions to outgoing rtp packets.
   */
  protected void processOutgoingPacket(Message msg) {
    // do nothing with the packet
  }

  public void leaveSession() {
    if (null != rtpPayloadSender)
      rtpPayloadSender.leaveSession();

    Collection recvs = mapSsrcToReceiver.values();
    Iterator iter = recvs.iterator();
    while (iter.hasNext()) {
      RtpPayloadReceiver recv = (RtpPayloadReceiver) iter.next();
      recv.leaveSession();
    }
  }

}
