class ConfigMergeHelper {

  @SuppressWarnings("unchecked")
  public Map<String, Map<String, ThreeWayValue>> getConflicts(String clusterName, StackId targetStack) throws AmbariException {
    Cluster cluster = m_clusters.get().getCluster(clusterName);
    StackId oldStack = cluster.getCurrentStackVersion();

    Map<String, Map<String, String>> oldMap = new HashMap<>();
    Map<String, Map<String, String>> newMap = new HashMap<>();

    // Collect service-level properties for old and new stack
    for (String serviceName : cluster.getServices().keySet()) {
      try {
        Set<PropertyInfo> newStackProperties = m_ambariMetaInfo.get().getServiceProperties(
          targetStack.getStackName(), targetStack.getStackVersion(), serviceName);
        addToMap(newMap, newStackProperties);
      } catch (StackAccessException e) {
        LOG.info("Skipping service {} which is currently installed but does not exist in the target stack {}", serviceName, targetStack);
        continue;
      }
      Set<PropertyInfo> oldStackProperties = m_ambariMetaInfo.get().getServiceProperties(
          oldStack.getStackName(), oldStack.getStackVersion(), serviceName);
      addToMap(oldMap, oldStackProperties);
  }

    // Collect stack-level properties defined for old and new stack
    Set<PropertyInfo> set = m_ambariMetaInfo.get().getStackProperties(
        oldStack.getStackName(), oldStack.getStackVersion());
    addToMap(oldMap, set);

    set = m_ambariMetaInfo.get().getStackProperties(
        targetStack.getStackName(), targetStack.getStackVersion());
    addToMap(newMap, set);

    // Final result after merging.
    Map<String, Map<String, ThreeWayValue>> result =
      new HashMap<>();

    // For every property of every config type, see if it is going to be merged:
    for (Entry<String, Map<String, String>> entry : oldMap.entrySet()) {
      if (!newMap.containsKey(entry.getKey())) {
        LOG.info("Stack {} does not have an equivalent config type {} in {}",
            oldStack.getStackId(), entry.getKey(), targetStack.getStackId());
        continue;
      }

      Map<String, String> oldPairs = entry.getValue();
      Map<String, String> newPairs = newMap.get(entry.getKey());
      Collection<String> customValueKeys = null;

      Config config = cluster.getDesiredConfigByType(entry.getKey());

      // The conflict occurs, if current property is not defined at stack
      // metainfo, but is present in current cluster configs,
      // and a new value is different from current value
      if (null != config) {
        Set<String> valueKeys = config.getProperties().keySet();

        customValueKeys = CollectionUtils.subtract(valueKeys, oldPairs.keySet());
      }

      // Keep properties with custom values (i.e., changed from default value in old stack)
      if (null != customValueKeys) {
        for (String prop : customValueKeys) {
          String newVal = newPairs.get(prop);
          String savedVal = config.getProperties().get(prop);
          if (null != newVal && null != savedVal && !newVal.equals(savedVal)) {
            ThreeWayValue twv = new ThreeWayValue();
            twv.oldStackValue = null;
            twv.newStackValue = normalizeValue(savedVal, newVal.trim());
            twv.savedValue = savedVal.trim();

            if (!result.containsKey(entry.getKey())) {
              result.put(entry.getKey(), new HashMap<>());
            }

            result.get(entry.getKey()).put(prop, twv);
          }
        }
      }

      // Also, conflict occurs if all four coditions are met:
      // a) property is defined both in old and in new stack
      // b) property value in new stack differs from current
      // the property value
      // c) current property value is not the same as in new stack
      // d) current property value is not the same as in old stack

      Collection<String> common = CollectionUtils.intersection(newPairs.keySet(),
          oldPairs.keySet());

      for (String prop : common) {
        String oldStackVal = oldPairs.get(prop);
        String newStackVal = newPairs.get(prop);
        String savedVal = "";
        if (null != config) {
          savedVal = config.getProperties().get(prop);
        }

        if (!(newStackVal == null || oldStackVal == null)
                && !newStackVal.equals(savedVal) &&
            (!oldStackVal.equals(newStackVal) || !oldStackVal.equals(savedVal))) {
          ThreeWayValue twv = new ThreeWayValue();
          twv.oldStackValue = normalizeValue(savedVal, oldStackVal.trim());
          twv.newStackValue = normalizeValue(savedVal, newStackVal.trim());
          twv.savedValue = (null == savedVal) ? null : savedVal.trim();

          if (!result.containsKey(entry.getKey())) {
            result.put(entry.getKey(), new HashMap<>());
          }

          result.get(entry.getKey()).put(prop, twv);
        }
      }
    }

    return result;
  }

}