/**
   * Method description
   *
   * @param ht_def_key
   * @param ht_dev_val
   * @param st_def_key
   * @param st_def_val
   * @param prop_key
   * @param prop_val_class
   * @param params
   * @param props
   * @param <T>
   */
  protected <T> void checkHighThroughputProperty(
      String ht_def_key,
      T ht_dev_val,
      String st_def_key,
      T st_def_val,
      String prop_key,
      Class<T> prop_val_class,
      Map<String, Object> params,
      Map<String, Object> props) {
    T tmp = st_def_val;
    String str_tmp = null;

    if (isHighThroughput()) {
      tmp = ht_dev_val;
      str_tmp = (String) params.get(ht_def_key);
    } else {
      tmp = st_def_val;
      str_tmp = (String) params.get(st_def_key);
    }
    if (prop_val_class.isAssignableFrom(Integer.class)) {
      tmp = prop_val_class.cast(DataTypes.parseNum(str_tmp, Integer.class, (Integer) tmp));
    }
    if (prop_val_class.isAssignableFrom(Long.class)) {
      tmp = prop_val_class.cast(DataTypes.parseNum(str_tmp, Long.class, (Long) tmp));
    }
    if (prop_val_class.isAssignableFrom(String.class)) {
      tmp = prop_val_class.cast(str_tmp);
    }
    props.put(prop_key, tmp);
  }
  private void reconnectService(final Map<String, Object> port_props, long delay) {
    if (log.isLoggable(Level.FINER)) {
      String cid = "" + port_props.get("local-hostname") + "@" + port_props.get("remote-hostname");

      log.log(
          Level.FINER,
          "Reconnecting service for: {0}, scheduling next try in {1}secs, cid: {2}, props: {3}",
          new Object[] {getName(), delay / 1000, cid, port_props});
    }
    addTimerTask(
        new tigase.util.TimerTask() {
          @Override
          public void run() {
            String host = (String) port_props.get(PORT_REMOTE_HOST_PROP_KEY);

            if (host == null) {
              host = (String) port_props.get("remote-hostname");
            }

            int port = (Integer) port_props.get(PORT_KEY);

            if (log.isLoggable(Level.FINE)) {
              log.log(
                  Level.FINE,
                  "Reconnecting service for component: {0}, to remote host: {1} on port: {2,number,#}",
                  new Object[] {getName(), host, port});
            }
            startService(port_props);
          }
        },
        delay);
  }
    @Override
    public String getRemoteHostname() {
      if (port_props.containsKey(PORT_REMOTE_HOST_PROP_KEY)) {
        return (String) port_props.get(PORT_REMOTE_HOST_PROP_KEY);
      }

      return (String) port_props.get("remote-hostname");
    }
    @Override
    public ConnectionType getConnectionType() {
      String type = null;

      if (port_props.get(PORT_TYPE_PROP_KEY) == null) {
        log.warning(
            getName() + ": connection type is null: " + port_props.get(PORT_KEY).toString());
      } else {
        type = port_props.get(PORT_TYPE_PROP_KEY).toString();
      }

      return ConnectionType.valueOf(type);
    }
  private void putDefPortParams(Map<String, Object> props, int port, SocketType sock) {
    log.log(Level.CONFIG, "Generating defaults for port: {0,number,#}", port);
    props.put(PROP_KEY + port + "/" + PORT_TYPE_PROP_KEY, ConnectionType.accept);
    props.put(PROP_KEY + port + "/" + PORT_SOCKET_PROP_KEY, sock);
    props.put(PROP_KEY + port + "/" + PORT_IFC_PROP_KEY, PORT_IFC_PROP_VAL);
    props.put(PROP_KEY + port + "/" + PORT_REMOTE_HOST_PROP_KEY, PORT_REMOTE_HOST_PROP_VAL);
    props.put(PROP_KEY + port + "/" + TLS_REQUIRED_PROP_KEY, TLS_REQUIRED_PROP_VAL);

    Map<String, Object> extra = getParamsForPort(port);

    if (extra != null) {
      for (Map.Entry<String, Object> entry : extra.entrySet()) {
        props.put(PROP_KEY + port + "/" + entry.getKey(), entry.getValue());
      } // end of for ()
    } // end of if (extra != null)
  }
  @Override
  public void setProperties(Map<String, Object> props) throws ConfigurationException {
    super.setProperties(props);
    if (props.get(MAX_INACTIVITY_TIME) != null) {
      maxInactivityTime = (Long) props.get(MAX_INACTIVITY_TIME) * SECOND;
    }
    if (props.get(WHITE_CHAR_ACK_PROP_KEY) != null) {
      white_char_ack = (Boolean) props.get(WHITE_CHAR_ACK_PROP_KEY);
    }
    if (props.get(XMPP_ACK_PROP_KEY) != null) {
      xmpp_ack = (Boolean) props.get(XMPP_ACK_PROP_KEY);
    }
    if (props.get(NET_BUFFER_PROP_KEY) != null) {
      net_buffer = (Integer) props.get(NET_BUFFER_PROP_KEY);
    }
    if (props.get(NET_BUFFER_LIMIT_PROP_KEY) != null) {
      net_buffer_limit = (Integer) props.get(NET_BUFFER_LIMIT_PROP_KEY);
    }
    if (props.get(TRAFFIC_THROTTLING_PROP_KEY) != null) {
      String[] tmp = ((String) props.get(TRAFFIC_THROTTLING_PROP_KEY)).split(",");

      for (String tmp_s : tmp) {
        String[] tmp_thr = tmp_s.split(":");

        if (tmp_thr[0].equalsIgnoreCase("xmpp")) {
          last_minute_packets_limit =
              DataTypes.parseNum(tmp_thr[1], Long.class, LAST_MINUTE_PACKETS_LIMIT_PROP_VAL);
          log.warning(getName() + " last_minute_packets_limit = " + last_minute_packets_limit);
          total_packets_limit =
              DataTypes.parseNum(tmp_thr[2], Long.class, TOTAL_PACKETS_LIMIT_PROP_VAL);
          log.warning(getName() + " total_packets_limit = " + total_packets_limit);
          if (tmp_thr[3].equalsIgnoreCase("disc")) {
            xmppLimitAction = LIMIT_ACTION.DISCONNECT;
          }
          if (tmp_thr[3].equalsIgnoreCase("drop")) {
            xmppLimitAction = LIMIT_ACTION.DROP_PACKETS;
          }
        }
        if (tmp_thr[0].equalsIgnoreCase("bin")) {
          last_minute_bin_limit =
              DataTypes.parseNum(tmp_thr[1], Long.class, LAST_MINUTE_BIN_LIMIT_PROP_VAL);
          log.warning(getName() + " last_minute_bin_limit = " + last_minute_bin_limit);
          total_bin_limit = DataTypes.parseNum(tmp_thr[2], Long.class, TOTAL_BIN_LIMIT_PROP_VAL);
          log.warning(getName() + " total_bin_limit = " + total_bin_limit);
        }
      }
    }

    if (props.get(ELEMENTS_NUMBER_LIMIT_PROP_KEY) != null) {
      elements_number_limit = (int) props.get(ELEMENTS_NUMBER_LIMIT_PROP_KEY);
    }

    if (props.get(WATCHDOG_DELAY) != null) {
      watchdogDelay = (long) props.get(WATCHDOG_DELAY);
    }
    if (props.get(WATCHDOG_TIMEOUT) != null) {
      watchdogTimeout = (long) props.get(WATCHDOG_TIMEOUT);
    }
    if (props.get(WATCHDOG_PING_TYPE_KEY) != null) {
      String value = String.valueOf(props.get(WATCHDOG_PING_TYPE_KEY));
      watchdogPingType = WATCHDOG_PING_TYPE.valueOf(value.toUpperCase());
    }

    if (props.size() == 1) {

      // If props.size() == 1, it means this is a single property update and
      // ConnectionManager does not support it yet.
      return;
    }
    if (isInitializationComplete()) {

      // Do we really need to do this again?
      // Looks like reconfiguration for the port is not working correctly anyway
      // so for now we do not want to do it.
      return;
    }
    releaseListeners();

    int[] ports = (int[]) props.get(PORTS_PROP_KEY);

    if (ports != null) {
      for (int i = 0; i < ports.length; i++) {
        Map<String, Object> port_props = new LinkedHashMap<String, Object>(20);

        for (Map.Entry<String, Object> entry : props.entrySet()) {
          if (entry.getKey().startsWith(PROP_KEY + ports[i])) {
            int idx = entry.getKey().lastIndexOf('/');
            String key = entry.getKey().substring(idx + 1);

            log.log(
                Level.CONFIG,
                "Adding port property key: {0}={1}",
                new Object[] {key, entry.getValue()});
            port_props.put(key, entry.getValue());
          } // end of if (entry.getKey().startsWith())
        } // end of for ()
        port_props.put(PORT_KEY, ports[i]);
        if (port_props.containsKey(PORT_TYPE_PROP_KEY)
            && !(port_props.get(PORT_TYPE_PROP_KEY) instanceof ConnectionType)) {
          Object val = port_props.get(PORT_TYPE_PROP_KEY);
          port_props.put(PORT_TYPE_PROP_KEY, ConnectionType.valueOf(val.toString()));
        }
        if (port_props.containsKey(PORT_SOCKET_PROP_KEY)
            && !(port_props.get(PORT_SOCKET_PROP_KEY) instanceof SocketType)) {
          Object val = port_props.get(PORT_SOCKET_PROP_KEY);
          port_props.put(PORT_SOCKET_PROP_KEY, SocketType.valueOf(val.toString()));
        }
        addWaitingTask(port_props);
        // reconnectService(port_props, startDelay);
      } // end of for (int i = 0; i < ports.length; i++)
    } // end of if (ports != null)
  }
  @Override
  public Map<String, Object> getDefaults(Map<String, Object> params) {
    log.log(Level.CONFIG, "{0} defaults: {1}", new Object[] {getName(), params.toString()});

    Map<String, Object> props = super.getDefaults(params);

    props.put(TLS_USE_PROP_KEY, TLS_USE_PROP_VAL);
    checkHighThroughputProperty(
        NET_BUFFER_HT_PROP_KEY,
        NET_BUFFER_HT_PROP_VAL,
        NET_BUFFER_ST_PROP_KEY,
        NET_BUFFER_ST_PROP_VAL,
        NET_BUFFER_PROP_KEY,
        Integer.class,
        params,
        props);
    checkHighThroughputProperty(
        HT_TRAFFIC_THROTTLING_PROP_KEY,
        getDefTrafficThrottling(),
        ST_TRAFFIC_THROTTLING_PROP_KEY,
        getDefTrafficThrottling(),
        TRAFFIC_THROTTLING_PROP_KEY,
        String.class,
        params,
        props);

    props.put(
        NET_BUFFER_LIMIT_PROP_KEY,
        isHighThroughput() ? NET_BUFFER_LIMIT_HT_PROP_VAL : NET_BUFFER_LIMIT_ST_PROP_VAL);

    if (params.get("--" + ELEMENTS_NUMBER_LIMIT_PROP_KEY) != null) {
      elements_number_limit =
          Integer.valueOf((String) params.get("--" + ELEMENTS_NUMBER_LIMIT_PROP_KEY));
    } else {
      elements_number_limit = ELEMENTS_NUMBER_LIMIT_PROP_VAL;
    }
    props.put(ELEMENTS_NUMBER_LIMIT_PROP_KEY, elements_number_limit);

    if (params.get("--" + WATCHDOG_DELAY) != null) {
      watchdogDelay = Integer.valueOf((String) params.get("--" + WATCHDOG_DELAY));
    }
    props.put(WATCHDOG_DELAY, watchdogDelay);

    if (params.get("--" + WATCHDOG_TIMEOUT) != null) {
      watchdogTimeout = Integer.valueOf((String) params.get("--" + WATCHDOG_TIMEOUT));
    }
    props.put(WATCHDOG_TIMEOUT, watchdogTimeout);

    WATCHDOG_PING_TYPE pingtype = WATCHDOG_PING_TYPE.WHITESPACE;
    if (params.get("--" + WATCHDOG_PING_TYPE_KEY) != null) {
      String type = (String) params.get("--" + WATCHDOG_PING_TYPE_KEY);
      pingtype = WATCHDOG_PING_TYPE.valueOf(type.toUpperCase());
    }
    props.put(WATCHDOG_PING_TYPE_KEY, pingtype);

    int[] ports = null;
    String ports_str = (String) params.get("--" + getName() + "-ports");

    if (ports_str != null) {
      String[] ports_stra = ports_str.split(",");

      ports = new int[ports_stra.length];

      int k = 0;

      for (String p : ports_stra) {
        try {
          ports[k++] = Integer.parseInt(p);
        } catch (NumberFormatException e) {
          log.warning("Incorrect ports default settings: " + p);
        }
      }
    }

    int ports_size = 0;

    if (ports != null) {
      log.config("Port settings preset: " + Arrays.toString(ports));
      for (int port : ports) {
        putDefPortParams(props, port, SocketType.plain);
      } // end of for (int i = 0; i < idx; i++)
      props.put(PORTS_PROP_KEY, ports);
    } else {
      int[] plains = getDefPlainPorts();

      if (plains != null) {
        ports_size += plains.length;
      } // end of if (plains != null)

      int[] ssls = getDefSSLPorts();

      if (ssls != null) {
        ports_size += ssls.length;
      } // end of if (ssls != null)
      if (ports_size > 0) {
        ports = new int[ports_size];
      } // end of if (ports_size > 0)
      if (ports != null) {
        int idx = 0;

        if (plains != null) {
          idx = plains.length;
          for (int i = 0; i < idx; i++) {
            ports[i] = plains[i];
            putDefPortParams(props, ports[i], SocketType.plain);
          } // end of for (int i = 0; i < idx; i++)
        } // end of if (plains != null)
        if (ssls != null) {
          for (int i = idx; i < idx + ssls.length; i++) {
            ports[i] = ssls[i - idx];
            putDefPortParams(props, ports[i], SocketType.ssl);
          } // end of for (int i = 0; i < idx + ssls.length; i++)
        } // end of if (ssls != null)
        props.put(PORTS_PROP_KEY, ports);
      } // end of if (ports != null)
    }

    String acks = (String) params.get(XMPP_STANZA_ACK);

    if (acks != null) {
      String[] acks_arr = acks.split(",");

      for (String ack_type : acks_arr) {
        if (STANZA_WHITE_CHAR_ACK.equals(ack_type)) {
          white_char_ack = true;
        }
        if (STANZA_XMPP_ACK.equals(ack_type)) {
          xmpp_ack = true;
        }
      }
    }
    props.put(WHITE_CHAR_ACK_PROP_KEY, white_char_ack);
    props.put(XMPP_ACK_PROP_KEY, xmpp_ack);
    props.put(MAX_INACTIVITY_TIME, getMaxInactiveTime() / SECOND);

    return props;
  }
 @Override
 public SocketType getSocketType() {
   return SocketType.valueOf(port_props.get(PORT_SOCKET_PROP_KEY).toString());
 }
 @Override
 public InetSocketAddress getRemoteAddress() {
   return (InetSocketAddress) port_props.get("remote-address");
 }
 @Override
 public int getPort() {
   return (Integer) port_props.get(PORT_KEY);
 }
 @Override
 public String[] getIfcs() {
   return (String[]) port_props.get(PORT_IFC_PROP_KEY);
 }
 @Override
 public String toString() {
   return port_props.toString();
 }
    @Override
    public void accept(SocketChannel sc) {
      String cid = "" + port_props.get("local-hostname") + "@" + port_props.get("remote-hostname");

      if (log.isLoggable(Level.FINEST)) {
        log.log(
            Level.FINEST,
            "Accept called for service: {0}, port_props: {1}",
            new Object[] {cid, port_props});
      }

      IO serv = getXMPPIOServiceInstance();
      serv.setBufferLimit(net_buffer_limit);

      ((XMPPDomBuilderHandler) serv.getSessionData().get(DOM_HANDLER))
          .setElementsLimit(elements_number_limit);

      serv.setIOServiceListener(ConnectionManager.this);
      serv.setSessionData(port_props);
      try {
        serv.accept(sc);
        if (getSocketType() == SocketType.ssl) {
          serv.startSSL(false, false, false);
        } // end of if (socket == SocketType.ssl)
        serviceStarted(serv);
        SocketThread.addSocketService(serv);
      } catch (Exception e) {
        if (getConnectionType() == ConnectionType.connect) {

          // Accept side for component service is not ready yet?
          // Let's wait for a few secs and try again.
          if (log.isLoggable(Level.FINEST)) {
            log.log(
                Level.FINEST,
                "Problem reconnecting the service: {0}, port_props: {1}, exception: {2}",
                new Object[] {serv, port_props, e});
          }
          updateConnectionDetails(port_props);

          boolean reconnect = false;
          Integer reconnects = (Integer) port_props.get(MAX_RECONNECTS_PROP_KEY);

          if (reconnects != null) {
            int recon = reconnects.intValue();

            if (recon != 0) {
              port_props.put(MAX_RECONNECTS_PROP_KEY, (--recon));
              reconnect = true;
            } // end of if (recon != 0)
          }
          if (reconnect) {
            reconnectService(port_props, connectionDelay);
          } else {
            reconnectionFailed(port_props);
          }
        } else {

          // Ignore
        }

        //      } catch (Exception e) {
        //        if (log.isLoggable(Level.FINEST)) {
        //          log.log(Level.FINEST, "Can not accept connection cid: " + cid, e);
        //        }
        //        log.log(Level.WARNING, "Can not accept connection.", e);
        //        serv.stop();
      } // end of try-catch
    }