class StageUtils {

  public static Map<String, Set<String>> getClusterHostInfo(Cluster cluster) throws AmbariException {
    //Fill hosts and ports lists
    Set<String>   hostsSet  = new LinkedHashSet<>();
    List<Integer> portsList = new ArrayList<>();
    List<String>  rackList  = new ArrayList<>();
    List<String>  ipV4List  = new ArrayList<>();

    Collection<Host> allHosts = cluster.getHosts();
    for (Host host : allHosts) {

      hostsSet.add(host.getHostName());

      Integer currentPingPort = host.getCurrentPingPort();
      portsList.add(currentPingPort == null ? DEFAULT_PING_PORT : currentPingPort);

      String rackInfo = host.getRackInfo();
      rackList.add(StringUtils.isEmpty(rackInfo) ? DEFAULT_RACK : rackInfo );

      String iPv4 = host.getIPv4();
      ipV4List.add(StringUtils.isEmpty(iPv4) ? DEFAULT_IPV4_ADDRESS : iPv4 );
    }

    // add hosts from topology manager
    Map<String, Collection<String>> pendingHostComponents = topologyManager.getPendingHostComponents();
    for (String hostname : pendingHostComponents.keySet()) {
      if (!hostsSet.contains(hostname)) {
        hostsSet.add(hostname);
        portsList.add(DEFAULT_PING_PORT);
        rackList.add(DEFAULT_RACK);
        ipV4List.add(DEFAULT_IPV4_ADDRESS);
      }
    }

    List<String> hostsList = new ArrayList<>(hostsSet);
    Map<String, String> additionalComponentToClusterInfoKeyMap = new HashMap<>();

    // Fill hosts for services
    Map<String, SortedSet<Integer>> hostRolesInfo = new HashMap<>();
    for (Map.Entry<String, Service> serviceEntry : cluster.getServices().entrySet()) {

      Service service = serviceEntry.getValue();

      for (Map.Entry<String, ServiceComponent> serviceComponentEntry : service.getServiceComponents().entrySet()) {

        ServiceComponent serviceComponent = serviceComponentEntry.getValue();
        String componentName = serviceComponent.getName();

        String roleName = componentToClusterInfoKeyMap.get(componentName);
        if(null == roleName) {
          roleName = additionalComponentToClusterInfoKeyMap.get(componentName);
        }
        if (null == roleName && !serviceComponent.isClientComponent()) {
          roleName = componentName.toLowerCase() + "_hosts";
          additionalComponentToClusterInfoKeyMap.put(componentName, roleName);
        }

        if (roleName == null) {
          continue;
        }

        for (String hostName : serviceComponent.getServiceComponentHosts().keySet()) {

          if (roleName != null) {
            SortedSet<Integer> hostsForComponentsHost = hostRolesInfo.get(roleName);

            if (hostsForComponentsHost == null) {
              hostsForComponentsHost = new TreeSet<>();
              hostRolesInfo.put(roleName, hostsForComponentsHost);
            }

            int hostIndex = hostsList.indexOf(hostName);
            //Add index of host to current host role
            hostsForComponentsHost.add(hostIndex);
          }
        }
      }
    }

    // add components from topology manager
    for (Map.Entry<String, Collection<String>> entry : pendingHostComponents.entrySet()) {
      String hostname = entry.getKey();
      Collection<String> hostComponents = entry.getValue();

      for (String hostComponent : hostComponents) {
        String roleName = getClusterHostInfoKey(hostComponent);
        if (null == roleName) {
          roleName = additionalComponentToClusterInfoKeyMap.get(hostComponent);
        }
        if (null == roleName) {
          // even though all mappings are being added, componentToClusterInfoKeyMap is
          // a higher priority lookup
          for (Service service : cluster.getServices().values()) {
            for (ServiceComponent sc : service.getServiceComponents().values()) {
              if (sc.getName().equals(hostComponent)) {
                roleName = hostComponent.toLowerCase() + "_hosts";
                additionalComponentToClusterInfoKeyMap.put(hostComponent, roleName);
              }
            }
          }
        }

        if (roleName != null) {
          SortedSet<Integer> hostsForComponentsHost = hostRolesInfo.get(roleName);

          if (hostsForComponentsHost == null) {
            hostsForComponentsHost = new TreeSet<>();
            hostRolesInfo.put(roleName, hostsForComponentsHost);
          }

          int hostIndex = hostsList.indexOf(hostname);
          if (hostIndex != -1) {
            if (!hostsForComponentsHost.contains(hostIndex)) {
              hostsForComponentsHost.add(hostIndex);
            }
          } else {
            //todo: I don't think that this can happen
            //todo: determine if it can and if so, handle properly
            //todo: if it 'cant' should probably enforce invariant
            throw new RuntimeException("Unable to get host index for host: " + hostname);
          }
        }
      }
    }

    Map<String, Set<String>> clusterHostInfo = new HashMap<>();

    for (Map.Entry<String, SortedSet<Integer>> entry : hostRolesInfo.entrySet()) {
      TreeSet<Integer> sortedSet = new TreeSet<>(entry.getValue());

      Set<String> replacedRangesSet = replaceRanges(sortedSet);

      clusterHostInfo.put(entry.getKey(), replacedRangesSet);
    }

    clusterHostInfo.put(HOSTS_LIST, hostsSet);
    clusterHostInfo.put(PORTS, replaceMappedRanges(portsList));
    clusterHostInfo.put(IPV4_ADDRESSES, replaceMappedRanges(ipV4List));
    clusterHostInfo.put(RACKS, replaceMappedRanges(rackList));

    // Fill server host
    /*
     * Note: We don't replace server host name, port, ssl usage by an index (like we do
     * with component hostnames), because if ambari-agent is not installed
     * at ambari-server host, then allHosts map will not contain
     * ambari-server hostname.
     */
    clusterHostInfo.put(AMBARI_SERVER_HOST, Sets.newHashSet(getHostName()));
    
    boolean serverUseSsl = configuration.getApiSSLAuthentication();
    int port = serverUseSsl ? configuration.getClientSSLApiPort() : configuration.getClientApiPort();
    clusterHostInfo.put(AMBARI_SERVER_PORT, Sets.newHashSet(Integer.toString(port)));
    clusterHostInfo.put(AMBARI_SERVER_USE_SSL, Sets.newHashSet(Boolean.toString(serverUseSsl)));

    return clusterHostInfo;
  }

  @Inject public  StageUtils(StageFactory stageFactory);

}