/**
   * Processes the session initiation {@link SessionIQ} that we were created with, passing its
   * content to the media handler and then sends either a "session-info/ringing" or a "terminate"
   * response.
   *
   * @param sessionInitIQ The {@link SessionIQ} that created the session that we are handling here.
   */
  protected synchronized void processSessionInitiate(SessionIQ sessionInitIQ) {
    // Do initiate the session.
    this.sessionInitIQ = sessionInitIQ;
    this.initiator = true;

    RtpDescriptionPacketExtension description = null;

    for (PacketExtension ext : sessionInitIQ.getExtensions()) {
      if (ext.getElementName().equals(RtpDescriptionPacketExtension.ELEMENT_NAME)) {
        description = (RtpDescriptionPacketExtension) ext;
        break;
      }
    }

    if (description == null) {
      logger.info("No description in incoming session initiate");

      // send an error response;
      String reasonText = "Error: no description";
      SessionIQ errResp =
          GTalkPacketFactory.createSessionTerminate(
              sessionInitIQ.getTo(),
              sessionInitIQ.getFrom(),
              sessionInitIQ.getID(),
              Reason.INCOMPATIBLE_PARAMETERS,
              reasonText);

      setState(CallPeerState.FAILED, reasonText);
      getProtocolProvider().getConnection().sendPacket(errResp);
      return;
    }

    try {
      getMediaHandler().processOffer(description);
    } catch (Exception ex) {
      logger.info("Failed to process an incoming session initiate", ex);

      // send an error response;
      String reasonText = "Error: " + ex.getMessage();
      SessionIQ errResp =
          GTalkPacketFactory.createSessionTerminate(
              sessionInitIQ.getTo(),
              sessionInitIQ.getFrom(),
              sessionInitIQ.getID(),
              Reason.INCOMPATIBLE_PARAMETERS,
              reasonText);

      setState(CallPeerState.FAILED, reasonText);
      getProtocolProvider().getConnection().sendPacket(errResp);
      return;
    }

    // If we do not get the info about the remote peer yet. Get it right
    // now.
    if (this.getDiscoveryInfo() == null) {
      String calleeURI = sessionInitIQ.getFrom();
      retrieveDiscoveryInfo(calleeURI);
    }
  }
  /**
   * Process candidates received.
   *
   * @param sessionInitIQ The {@link SessionIQ} that created the session we are handling here
   */
  public void processCandidates(SessionIQ sessionInitIQ) {
    Collection<PacketExtension> extensions = sessionInitIQ.getExtensions();
    List<GTalkCandidatePacketExtension> candidates = new ArrayList<GTalkCandidatePacketExtension>();

    for (PacketExtension ext : extensions) {
      if (ext.getElementName().equalsIgnoreCase(GTalkCandidatePacketExtension.ELEMENT_NAME)) {
        GTalkCandidatePacketExtension cand = (GTalkCandidatePacketExtension) ext;
        candidates.add(cand);
      }
    }

    try {
      getMediaHandler().processCandidates(candidates);
    } catch (OperationFailedException ofe) {
      logger.warn("Failed to process an incoming candidates", ofe);

      // send an error response
      String reasonText = "Error: " + ofe.getMessage();
      SessionIQ errResp =
          GTalkPacketFactory.createSessionTerminate(
              sessionInitIQ.getTo(),
              sessionInitIQ.getFrom(),
              sessionInitIQ.getID(),
              Reason.GENERAL_ERROR,
              reasonText);

      getMediaHandler().getTransportManager().close();
      setState(CallPeerState.FAILED, reasonText);
      getProtocolProvider().getConnection().sendPacket(errResp);
      return;
    }

    // HACK for FreeSwitch that send accept message before sending
    // candidates
    if (sessAcceptedWithNoCands != null) {
      if (isInitiator()) {
        try {
          answer();
        } catch (OperationFailedException e) {
          logger.info("Failed to answer call (FreeSwitch hack)");
        }
      } else {
        final SessionIQ sess = sessAcceptedWithNoCands;
        sessAcceptedWithNoCands = null;

        // run in another thread to not block smack receive thread and
        // possibly delay others candidates messages.
        new Thread() {
          @Override
          public void run() {
            processSessionAccept(sess);
          }
        }.start();
      }
      sessAcceptedWithNoCands = null;
    }
  }
  /**
   * Get an XML string representation.
   *
   * @return XML string representation
   */
  @Override
  public String toXML() {
    StringBuilder bldr = new StringBuilder();

    bldr.append("<").append(getElementName()).append(" ");

    if (getNamespace() != null) bldr.append("xmlns='").append(getNamespace()).append("'");

    // add the rest of the attributes if any
    for (Map.Entry<String, String> entry : attributes.entrySet()) {
      bldr.append(" ").append(entry.getKey()).append("='").append(entry.getValue()).append("'");
    }

    bldr.append(">");

    if (userCount != 0)
      bldr.append("<")
          .append(ELEMENT_USER_COUNT)
          .append(">")
          .append(userCount)
          .append("</")
          .append(ELEMENT_USER_COUNT)
          .append(">");

    if (active != -1)
      bldr.append("<")
          .append(ELEMENT_ACTIVE)
          .append(">")
          .append((active > 0))
          .append("</")
          .append(ELEMENT_ACTIVE)
          .append(">");

    if (locked != -1)
      bldr.append("<")
          .append(ELEMENT_LOCKED)
          .append(">")
          .append((active > 0))
          .append("</")
          .append(ELEMENT_LOCKED)
          .append(">");

    for (PacketExtension ext : getChildExtensions()) {
      bldr.append(ext.toXML());
    }

    bldr.append("</").append(getElementName()).append(">");
    return bldr.toString();
  }
  /**
   * Processes the session initiation {@link SessionIQ} that we were created with, passing its
   * content to the media handler.
   *
   * @param sessionInitIQ The {@link SessionIQ} that created the session that we are handling here.
   */
  public void processSessionAccept(SessionIQ sessionInitIQ) {
    this.sessionInitIQ = sessionInitIQ;

    CallPeerMediaHandlerGTalkImpl mediaHandler = getMediaHandler();
    Collection<PacketExtension> extensions = sessionInitIQ.getExtensions();
    RtpDescriptionPacketExtension answer = null;

    for (PacketExtension ext : extensions) {
      if (ext.getElementName().equalsIgnoreCase(RtpDescriptionPacketExtension.ELEMENT_NAME)) {
        answer = (RtpDescriptionPacketExtension) ext;
        break;
      }
    }

    try {
      mediaHandler.getTransportManager().wrapupConnectivityEstablishment();
      mediaHandler.processAnswer(answer);
    } catch (IllegalArgumentException e) {
      // HACK for FreeSwitch that send accept message before sending
      // candidates
      sessAcceptedWithNoCands = sessionInitIQ;
      return;
    } catch (Exception exc) {
      if (logger.isInfoEnabled()) logger.info("Failed to process a session-accept", exc);

      // send an error response
      String reasonText = "Error: " + exc.getMessage();
      SessionIQ errResp =
          GTalkPacketFactory.createSessionTerminate(
              sessionInitIQ.getTo(),
              sessionInitIQ.getFrom(),
              sessionInitIQ.getID(),
              Reason.GENERAL_ERROR,
              reasonText);

      getMediaHandler().getTransportManager().close();
      setState(CallPeerState.FAILED, reasonText);
      getProtocolProvider().getConnection().sendPacket(errResp);
      return;
    }

    // tell everyone we are connecting so that the audio notifications would
    // stop
    setState(CallPeerState.CONNECTED);

    mediaHandler.start();
  }