/**
   * Method is responsible for processing incoming subscription request (i.e. in the receivers
   * session manager).
   *
   * <p>If the contact is already subscribed the an auto-reply with type='subscribded' is sent,
   * otherwise contact is added to the roster (if it's missing/there is no current subscription),
   * sets the subscription type to {@code PresenceType.in_subscribe} and subsequently broadcast
   * presence update to all connected resources.
   *
   * @param packet packet is which being processed.
   * @param session user session which keeps all the user session data and also gives an access to
   *     the user's repository data.
   * @param results this a collection with packets which have been generated as input packet
   *     processing results.
   * @param settings this map keeps plugin specific settings loaded from the Tigase server
   *     configuration.
   * @param pres_type specifies type of the presence.
   * @throws NoConnectionIdException
   * @throws NotAuthorizedException
   * @throws TigaseDBException
   */
  protected void processInSubscribe(
      Packet packet,
      XMPPResourceConnection session,
      Queue<Packet> results,
      Map<String, Object> settings,
      RosterAbstract.PresenceType pres_type)
      throws NotAuthorizedException, TigaseDBException, NoConnectionIdException {

    // If the buddy is already subscribed then auto-reply with subscribed
    // presence stanza.
    if (roster_util.isSubscribedFrom(session, packet.getStanzaFrom())) {
      sendPresence(
          StanzaType.subscribed,
          session.getJID().copyWithoutResource(),
          packet.getStanzaFrom(),
          results,
          null);
    } else {
      RosterAbstract.SubscriptionType curr_sub =
          roster_util.getBuddySubscription(session, packet.getStanzaFrom());

      if (curr_sub == null) {
        roster_util.addBuddy(session, packet.getStanzaFrom(), null, null, null);
      } // end of if (curr_sub == null)
      roster_util.updateBuddySubscription(session, pres_type, packet.getStanzaFrom());
      if (!autoAuthorize) {
        updatePresenceChange(packet, session, results);
      } else {
        roster_util.setBuddySubscription(
            session,
            RosterAbstract.SubscriptionType.both,
            packet.getStanzaFrom().copyWithoutResource());
      }
    } // end of else
    if (autoAuthorize) {
      roster_util.updateBuddyChange(
          session,
          results,
          roster_util.getBuddyItem(session, packet.getStanzaFrom().copyWithoutResource()));
      broadcastProbe(session, results, settings);
      sendPresence(StanzaType.subscribed, session.getJID(), packet.getStanzaFrom(), results, null);
    }
  }
  /**
   * Method is responsible for processing outgoing subscribed and unsubscribed presence (i.e. in the
   * sender session manager).
   *
   * <p>Presence packet is forwarded to the destination with the JID stripped from the resource, a
   * subscription state is being updated and, in case there was a change, a roster push is being
   * sent to all user resources. Also, in case of presence type out_subscribed server send current
   * presence to the user from each of the contact's available resources. For the presence type
   * out_unsubscribed an unavailable presence is sent.
   *
   * @param packet packet is which being processed.
   * @param session user session which keeps all the user session data and also gives an access to
   *     the user's repository data.
   * @param results this a collection with packets which have been generated as input packet
   *     processing results.
   * @param settings this map keeps plugin specific settings loaded from the Tigase server
   *     configuration.
   * @param pres_type specifies type of the presence.
   * @throws NoConnectionIdException
   * @throws NotAuthorizedException
   * @throws TigaseDBException
   */
  protected void processOutSubscribed(
      Packet packet,
      XMPPResourceConnection session,
      Queue<Packet> results,
      Map<String, Object> settings,
      RosterAbstract.PresenceType pres_type)
      throws NotAuthorizedException, TigaseDBException, NoConnectionIdException {

    // According to RFC-3921 I must forward all these kind presence
    // requests, it allows to re-synchronize
    // subscriptions in case of synchronization loss
    forwardPresence(results, packet, session.getJID().copyWithoutResource());

    Element initial_presence = session.getPresence();
    JID buddy = packet.getStanzaTo().copyWithoutResource();
    boolean subscr_changed = roster_util.updateBuddySubscription(session, pres_type, buddy);

    if (autoAuthorize && (pres_type == RosterAbstract.PresenceType.out_subscribed)) {
      roster_util.setBuddySubscription(
          session, RosterAbstract.SubscriptionType.both, buddy.copyWithoutResource());
    }
    if (subscr_changed) {
      roster_util.updateBuddyChange(session, results, roster_util.getBuddyItem(session, buddy));
      if (initial_presence != null) {
        if (pres_type == RosterAbstract.PresenceType.out_subscribed) {

          // The contact's server MUST then also send current presence to the user
          // from each of the contact's available resources.
          List<XMPPResourceConnection> activeSessions = session.getActiveSessions();

          for (XMPPResourceConnection userSessions : activeSessions) {
            Element presence = userSessions.getPresence();

            sendPresence(StanzaType.available, userSessions.getjid(), buddy, results, presence);
          }
          roster_util.setPresenceSent(session, buddy, true);
        } else {
          sendPresence(StanzaType.unavailable, session.getJID(), buddy, results, null);
        }
      } // end of if (subscr_changed)
    }
  }
  /**
   * Method is responsible for processing outgoing subscribe and unsubscribe presence (i.e. in the
   * sender session manager).
   *
   * <p>Presence packet is forwarded to the destination with the JID stripped from the resource.
   *
   * <p>In case of {@code PresenceType.out_subscribe} packet type contact is added to the roster (in
   * case it was missing), a subscription state is being updated and, in case there was a change, a
   * roster push is being sent to all user resources.
   *
   * <p>In case of {@code PresenceType.out_unsubscribe} method updates contact subscription (and
   * generates roster push if there was a change) and if the resulting contact subscription is NONE
   * then contact is removed from the roster.
   *
   * @param packet packet is which being processed.
   * @param session user session which keeps all the user session data and also gives an access to
   *     the user's repository data.
   * @param results this a collection with packets which have been generated as input packet
   *     processing results.
   * @param settings this map keeps plugin specific settings loaded from the Tigase server
   *     configuration.
   * @param pres_type specifies type of the presence.
   * @throws NoConnectionIdException
   * @throws NotAuthorizedException
   * @throws TigaseDBException
   */
  protected void processOutSubscribe(
      Packet packet,
      XMPPResourceConnection session,
      Queue<Packet> results,
      Map<String, Object> settings,
      RosterAbstract.PresenceType pres_type)
      throws NotAuthorizedException, TigaseDBException, NoConnectionIdException {

    // According to RFC-3921 I must forward all these kind presence
    // requests, it allows to resynchronize
    // subscriptions in case of synchronization loss
    boolean subscr_changed = false;

    forwardPresence(results, packet, session.getJID().copyWithoutResource());

    RosterAbstract.SubscriptionType current_subscription =
        roster_util.getBuddySubscription(session, packet.getStanzaTo());

    if (pres_type == RosterAbstract.PresenceType.out_subscribe) {
      if (current_subscription == null) {
        roster_util.addBuddy(session, packet.getStanzaTo(), null, null, null);
      } // end of if (current_subscription == null)
      subscr_changed =
          roster_util.updateBuddySubscription(session, pres_type, packet.getStanzaTo());
      if (autoAuthorize) {
        roster_util.setBuddySubscription(
            session,
            RosterAbstract.SubscriptionType.both,
            packet.getStanzaTo().copyWithoutResource());
      }
      if (subscr_changed) {
        roster_util.updateBuddyChange(
            session, results, roster_util.getBuddyItem(session, packet.getStanzaTo()));
      } // end of if (subscr_changed)
    } else {
      if (log.isLoggable(Level.FINEST)) {
        log.log(Level.FINEST, "out_subscribe: current_subscription = " + current_subscription);
      }
      if (current_subscription != null) {
        subscr_changed =
            roster_util.updateBuddySubscription(session, pres_type, packet.getStanzaTo());
        current_subscription = roster_util.getBuddySubscription(session, packet.getStanzaTo());
        if (subscr_changed) {
          roster_util.updateBuddyChange(
              session, results, roster_util.getBuddyItem(session, packet.getStanzaTo()));
        } // end of if (subscr_changed)
        if (SUB_NONE.contains(current_subscription)) {
          roster_util.removeBuddy(session, packet.getStanzaTo());
        } // end of if (current_subscription == null)
      }
    }
  }
  /**
   * {@inheritDoc} <br>
   * <br>
   * Performs processing of <em>presence</em> packets and calls different methods for particular
   * {@link PresenceType}
   */
  @SuppressWarnings({"unchecked", "fallthrough"})
  @Override
  public void process(
      final Packet packet,
      final XMPPResourceConnection session,
      final NonAuthUserRepository repo,
      final Queue<Packet> results,
      final Map<String, Object> settings)
      throws XMPPException {
    if (session == null) {
      if (log.isLoggable(Level.FINE)) {
        log.log(Level.FINE, "Session is null, ignoring packet: {0}", packet);
      }

      return;
    } // end of if (session == null)
    if (!session.isAuthorized()) {
      if (log.isLoggable(Level.FINE)) {
        log.log(Level.FINE, "Session is not authorized, ignoring packet: {0}", packet);
      }

      return;
    }

    // Synchronization to avoid conflict with login/logout events
    // processed in the SessionManager asynchronously
    synchronized (session) {
      try {
        RosterAbstract.PresenceType pres_type = roster_util.getPresenceType(session, packet);

        if (pres_type == null) {
          log.log(Level.INFO, "Invalid presence found: {0}", packet);

          return;
        } // end of if (type == null)
        if (log.isLoggable(Level.FINEST)) {
          log.log(
              Level.FINEST,
              "{0} | {1} presence found: {2}",
              new Object[] {session.getBareJID().toString(), pres_type, packet});
        }

        // All 'in' subscription presences must have a valid from address
        switch (pres_type) {
          case in_unsubscribe:
          case in_subscribe:
          case in_unsubscribed:
          case in_subscribed:
            if (packet.getStanzaFrom() == null) {
              if (log.isLoggable(Level.FINE)) {
                log.fine(
                    "'in' subscription presence without valid 'from' address, "
                        + "dropping packet: "
                        + packet);
              }

              return;
            }
            if (session.isUserId(packet.getStanzaFrom().getBareJID())) {
              if (log.isLoggable(Level.FINE)) {
                log.log(
                    Level.FINE,
                    "''in'' subscription to myself, not allowed, returning "
                        + "error for packet: "
                        + "{0}",
                    packet);
              }
              results.offer(
                  Authorization.NOT_ALLOWED.getResponseMessage(
                      packet, "You can not subscribe to yourself.", false));

              return;
            }

            // as per http://xmpp.org/rfcs/rfc6121.html#sub
            // Implementation Note: When a server processes or generates an outbound
            // presence stanza of type "subscribe", "subscribed", "unsubscribe",
            // or "unsubscribed", the server MUST stamp the outgoing presence
            // stanza with the bare JID <localpart@domainpart> of the sending entity,
            // not the full JID <localpart@domainpart/resourcepart>.
            //
            // we enforce this rule also for incomming presence subscirption packets
            packet.initVars(
                packet.getStanzaFrom().copyWithoutResource(),
                session.getJID().copyWithoutResource());

            break;

          case out_subscribe:
          case out_unsubscribe:
          case out_subscribed:
          case out_unsubscribed:

            // Check wheher the destination address is correct to prevent
            // broken/corrupted roster entries:
            if ((packet.getStanzaTo() == null) || packet.getStanzaTo().toString().isEmpty()) {
              results.offer(
                  Authorization.JID_MALFORMED.getResponseMessage(
                      packet, "The destination address is incorrect.", false));

              return;
            }

            // According to RFC 3921 draft bis-3, both source and destination
            // addresses must be BareJIDs, handled by initVars(...)
            packet.initVars(
                session.getJID().copyWithoutResource(), packet.getStanzaTo().copyWithoutResource());

            break;

          default:
            break;
        }
        switch (pres_type) {
          case out_subscribe:
          case out_unsubscribe:
            processOutSubscribe(packet, session, results, settings, pres_type);

            break;

          case out_subscribed:
          case out_unsubscribed:
            processOutSubscribed(packet, session, results, settings, pres_type);

            break;

          case in_subscribe:
            processInSubscribe(packet, session, results, settings, pres_type);

            break;

          case in_unsubscribe:
            processInUnsubscribe(packet, session, results, settings, pres_type);

            break;

          case in_subscribed:
            processInSubscribed(packet, session, results, settings, pres_type);

            break;

          case in_unsubscribed:
            processInUnsubscribed(packet, session, results, settings, pres_type);

            break;

          default:
            results.offer(
                Authorization.BAD_REQUEST.getResponseMessage(
                    packet, "Request type is incorrect", false));

            break;
        } // end of switch (type)
      } catch (NotAuthorizedException e) {
        log.log(
            Level.INFO,
            "Can not access user Roster, user session is not authorized yet: {0}",
            packet);
        log.log(Level.FINEST, "presence problem...", e);
      } catch (TigaseDBException e) {
        log.log(Level.WARNING, "Error accessing database for presence data: {0}", e);
      } // end of try-catch
    }
  }
  /**
   * Method description
   *
   * @param packet
   * @param session
   * @param repo
   * @param results
   * @param settings
   * @throws XMPPException
   */
  @Override
  public void process(
      final Packet packet,
      final XMPPResourceConnection session,
      final NonAuthUserRepository repo,
      final Queue<Packet> results,
      final Map<String, Object> settings)
      throws XMPPException {
    if (session == null) {
      return;
    } // end of if (session == null)
    if (!session.isAuthorized()) {
      results.offer(
          session
              .getAuthState()
              .getResponseMessage(packet, "Session is not yet authorized.", false));

      return;
    } // end of if (!session.isAuthorized())

    // TODO: test what happens if resource is bound multiple times for the same
    // user session. in particular if XMPPSession object removes the old
    // resource from the list.
    Element request = packet.getElement();
    StanzaType type = packet.getType();

    try {
      switch (type) {
        case set:
          String resource = request.getChildCDataStaticStr(Iq.IQ_BIND_RESOURCE_PATH);

          try {
            if ((resource == null) || resource.trim().isEmpty()) {
              resource = resourceDefPrefix + (++resGenerator);
              session.setResource(resource);
            } else {
              try {
                session.setResource(resource);
              } catch (TigaseStringprepException ex) {

                // User provided resource is invalid, generating different
                // server one
                log.log(
                    Level.INFO,
                    "Incrrect resource provided by the user: {0}, generating a "
                        + "different one by the server.",
                    resource);
                resource = resourceDefPrefix + (++resGenerator);
                session.setResource(resource);
              }
            } // end of if (resource == null) else
          } catch (TigaseStringprepException ex) {
            log.log(
                Level.WARNING,
                "stringprep problem with the server generated resource: {0}",
                resource);
          }
          packet.initVars(session.getJID(), packet.getStanzaTo());

          // session.putSessionData(RESOURCE_KEY, "true");
          results.offer(packet.okResult(new Element("jid", session.getJID().toString()), 1));

          break;

        default:
          results.offer(
              Authorization.BAD_REQUEST.getResponseMessage(
                  packet, "Bind type is incorrect", false));

          break;
      } // end of switch (type)
    } catch (NotAuthorizedException e) {
      results.offer(
          session
              .getAuthState()
              .getResponseMessage(packet, "Session is not yet authorized.", false));
    } // end of try-catch
  }