/**
   * @param node Node whose value is studied
   * @return
   */
  private static Multimap<SuggestionType, Object> getSuggestionForNode(TreeNode node) {

    // For storing the suggestion type and the payload
    Multimap<SuggestionType, Object> suggestionsMultimap = ArrayListMultimap.create();

    final String EMPTY = "";

    try {
      InitialContext ic = new InitialContext();
      ProjectSimilarityService projectSimilarityService =
          (ProjectSimilarityService) ic.lookup("java:module/ProjectSimilarityService");
      QualityObjectiveSimilarityService qoSimilarityService =
          (QualityObjectiveSimilarityService)
              ic.lookup("java:module/QualityObjectiveSimilarityService");
      // TODO: Finding similar metrics

      if (node instanceof QualityObjective) {
        QualityObjective qo = (QualityObjective) node;
        Float value = qo.getValue();

        // If the value of a quality objective cannot be computed,
        // attempt to obtain a suggestion
        if (value == null || value.isNaN()) {
          // Attempt to find a suitable project
          Project proj = qo.getProject();
          Project similarProject = null;
          if (proj != null) {
            // Get a list of similar projects
            List<Project> similarProjects = projectSimilarityService.getSimilarProjects(proj);
            if (similarProjects != null && similarProjects.size() > 0) {
              // For now obtain the first suitable project
              similarProject = similarProjects.get(0);
            }
          }

          // If a suitable similar project was found, search for similar Quality Objective(s) to be
          // suggested
          if (similarProject != null) {
            // Get similar QOs from a similar project
            List<QualityObjective> qos = qoSimilarityService.getSimilarQOs(qo, similarProject);
            // If results were found, construct a suggestion (for now obtain the first one).
            if (qos != null && qos.size() > 0) {
              suggestionsMultimap.put(SuggestionType.QO_REPLACE, qos.get(0));
              return suggestionsMultimap;
            }
          }

          // Otherwise suggest the QO to be removed
          suggestionsMultimap.put(SuggestionType.QO_REMOVE, null);
          return suggestionsMultimap;
        }
      }
      // if a metric has no value / the value is 0, return a suggestion to
      // remove the metric
      if (node instanceof Metric) {

        Metric metric = (Metric) node;
        Float value = metric.getValue();

        if (value == null || value.isNaN() || value.isInfinite()) {
          suggestionsMultimap.put(SuggestionType.METRIC_REMOVE, null);
          return suggestionsMultimap;
        }
      }
    } catch (NamingException e) {
      e.printStackTrace();
    }

    // If no suggestion is to be provided, return an empty string
    // (overwrite the possible existing suggestion).
    suggestionsMultimap.put(SuggestionType.NO_SUGGESTION, "");
    return suggestionsMultimap;
  }
  /**
   * Traverse the tree in postorder and update tree values
   *
   * @param node
   */
  private static void postorderWithParticularNode(TreeNode node, TreeNode projectTreeNode) {

    if (node == null) {
      return;
    }

    if (projectTreeNode == null) {
      return;
    }

    logger.debug("------------postorder: " + projectTreeNode.getName() + "---------------");

    logger.debug("Traversing project tree in postorder..." + projectTreeNode.toString());
    // Update the value
    try {
      InitialContext ic = new InitialContext();
      AdapterDataService adapterDataService = new AdapterDataService();
      TreeNodeService treeNodeService = (TreeNodeService) ic.lookup("java:module/TreeNodeService");

      if (projectTreeNode instanceof Metric) {
        Metric metric = (Metric) projectTreeNode;
        logger.debug("Recomputing the value of the Metric " + projectTreeNode);
        Float value = null;
        if (metric.getMetricSource() == MetricSource.Manual) {
          metric.updateQualityStatus();
        } else {
          value =
              adapterDataService.getMetricValue(
                  metric.getMetricSource(), metric.getMetricType(), metric.getProject());
          metric.setValue(value);
        }
        metric.setLastUpdated(getLatestTreeUpdateDate());
        metric.addHistoricValue();
        // End Metric node treatment
      } else if (projectTreeNode instanceof QualityIndicator) {
        logger.info("Recomputing the value of the Quality Indicator " + projectTreeNode);
        QualityIndicator qi = (QualityIndicator) projectTreeNode;
        if (qi.getUseFormula()) {
          String formulaToEval = Formula.parseFormula(qi.getViewFormula());
          if (formulaToEval != null && !formulaToEval.isEmpty()) {

            Float computedValue = Formula.evalFormula(formulaToEval);
            if (computedValue != null && !computedValue.isNaN()) {
              qi.setValue(computedValue);
              qi.setLastUpdated(getLatestTreeUpdateDate());
              treeNodeService.update(qi);
            }
          }
        } else {
          float achieved = 0;
          float denominator = 0;
          for (final TreeNode me : qi.getChildren()) {
            float weight = ((Metric) me).getWeight();
            if (me.getQualityStatus() == QualityStatus.Green) {
              achieved += weight;
            }
            denominator += weight;
          }
          if (denominator == 0) qi.getChildren().size();
          qi.setValue(achieved * 100 / denominator);
        }
        qi.setLastUpdated(getLatestTreeUpdateDate());
        qi.addHistoricValue();
        // End Q.Indicator node treatment
      } else if (projectTreeNode instanceof QualityObjective) {
        logger.info("Recomputing the value of the Quality Objective " + projectTreeNode);
        QualityObjective qo = (QualityObjective) projectTreeNode;
        if (qo.getUseFormula()) {
          String formulaToEval = Formula.parseFormula(qo.getViewFormula());
          if (formulaToEval != null && !formulaToEval.isEmpty()) {

            Float computedValue = Formula.evalFormula(formulaToEval);
            if (computedValue != null && !computedValue.isNaN()) {
              qo.setValue(computedValue);
              qo.setLastUpdated(getLatestTreeUpdateDate());
            }
          }
        } else {
          float denominator = 0;
          float achieved = 0;
          for (final TreeNode qi : qo.getChildren()) {
            float weight = ((QualityIndicator) qi).getWeight();
            if (qi.getQualityStatus() == QualityStatus.Green) {
              achieved += weight;
            }
            denominator += weight;
          }
          qo.setValue(achieved * 100 / denominator);
        }
        qo.setLastUpdated(getLatestTreeUpdateDate());
        qo.addHistoricValue();
        // End Quality Objective node treatment
      } else if (projectTreeNode instanceof Project) {
        logger.info("Recomputing the value of the Project " + projectTreeNode);
        Project prj = (Project) projectTreeNode;
        double qoValueSum = 0;
        double denominator = 0;
        for (Object o : projectTreeNode.getChildren()) {
          QualityObjective qo = (QualityObjective) o;
          if (qo.getWeight() == 0) {
            continue;
          }
          qoValueSum += qo.getValue() * (prj.isFormulaAverage() ? qo.getWeight() : 1);
          denominator += prj.isFormulaAverage() ? qo.getWeight() : 1;
        }

        // bad idea to divide something under 0
        if (denominator == 0) {
          denominator = 1;
        }

        Double computedValue = qoValueSum / denominator;

        if (computedValue != null && !computedValue.isNaN() && !computedValue.isInfinite()) {
          prj.setValue(computedValue);
        }

        prj.setLastUpdated(getLatestTreeUpdateDate());
        prj.addHistoricValue();
        logger.debug(" [" + qoValueSum + "] denominator [" + denominator + "] " + computedValue);
        // End Project node treatment
      }

      // Get a (possible) suggestion for the tree node
      Multimap<?, ?> suggestions = getSuggestionForNode(projectTreeNode);
      // TODO: take all the suggestions into account
      Object[] types = suggestions.keys().toArray();
      Object[] suggestionsValues = suggestions.values().toArray();
      if (types.length > 0) {
        // for now use the first item as suggestion
        SuggestionType stype = (SuggestionType) types[0];
        projectTreeNode.setSuggestionType(stype);
        if (suggestionsValues[0] != null && !suggestionsValues[0].equals("")) {
          projectTreeNode.setSuggestionValue((String) suggestionsValues[0]);
        }
      }

      treeNodeService.update(projectTreeNode);

    } catch (NamingException e) {
      e.printStackTrace();
    }

    // Iterate the node children
    TreeNode nodeChild = projectTreeNode.getParent();
    UQasarUtil.postorderWithParticularNode(projectTreeNode, nodeChild);

    return;
  }