/**
   * Gives back the relevant node based on the given radius
   *
   * @param position starting point to calculate recommendation
   * @param radius defines how many levels above start position should be incorporated
   */
  public INode collectNode(INode position, int radius) {

    INode relevantNode = null;

    // Going one level up if radius parameter stills allows it
    if (radius > 0) {
      if (!position.isRoot()) {
        INode parent = position.getParent();
        relevantNode = collectNode(parent, radius - 1);
      } else {
        relevantNode = position;
      }
    } else {
      relevantNode = position;
    }

    return relevantNode;
  }
  /**
   * Collect all the content that should be recommended based on given user and radius
   *
   * @param position starting point to calculate recommendation
   * @param radiusU defines how many levels of users should be incorporated
   * @param radiusC defines how many levels of content should be incorporated
   */
  public Map<INode, IAttribute> recommend(INode position, int radiusU, int radiusC) {

    Map<INode, IAttribute> recommendation = new HashMap<INode, IAttribute>();

    // Find relevant User
    INode relUser = collectNode(position, radiusU);
    System.out.println("starting recommendation from user node: " + relUser.toString());

    // collect leaf content nodes from relUser content items
    Set<INode> userAttributes = relUser.getAttributeKeys();
    for (INode content : userAttributes) {

      Map<INode, IAttribute> contentLeafNodes = collectLeaves(content, null);
      for (INode leafNode : contentLeafNodes.keySet()) {
        recommendation.put(leafNode, contentLeafNodes.get(leafNode));
      }
    }

    return recommendation;
  }
  /**
   * Gives back the leaf nodes related to a given node
   *
   * @param position this node is the starting point to find leaves
   * @param leaves collection of all leaves
   */
  public Map<INode, IAttribute> collectLeaves(INode position, Map<INode, IAttribute> leaves) {

    // Create leaves set if not exists
    if (leaves == null) {
      leaves = new HashMap<INode, IAttribute>();
    }

    // If position is leaf
    if (position.isLeaf()) {
      System.out.println(position.getAttributesString());
      leaves.put(position, position.getAttributeValue(position));
      return leaves;
    }

    // If position is no leaf
    Iterator<INode> children = position.getChildren();

    while (children.hasNext()) {

      INode child = children.next();
      Map<INode, IAttribute> tempLeaves = (collectLeaves(child, leaves));
      for (INode tempLeaf : tempLeaves.keySet()) {
        if (!leaves.keySet().contains(tempLeaf)) {
          leaves.put(tempLeaf, tempLeaf.getAttributeValue(tempLeaf));
        }
      }
    }

    return leaves;
  }
  /**
   * Runs a test that compares the tree calculation with the real values of a given user This
   * recommendation type allows to calculate an RMSE value that indicates the quality of the
   * recommendations produced by the clusterer
   */
  public Map<Integer, IAttribute> runTestRecommendation(INode testNode) {

    // Find position of the similar node in the tree
    INode position = leavesMapU.get((int) testNode.getDatasetId());

    if (position == null) {
      return null;
    } else {

      // Collect ratings of all content given by the input node
      Map<Integer, IAttribute> contentRatings = collectRatings(position, testNode, null);
      return contentRatings;
    }
  }
  /**
   * Collect ratings of all content given by the input node
   *
   * @param position this is the starting point for collecting
   * @param inputNodeID this node defines the content that needs to be collected
   */
  public Map<Integer, IAttribute> collectRatings(
      INode position, INode testNode, Map<Integer, IAttribute> contentRatings) {

    // Create Map for content of test user with empty values if not existing
    if (contentRatings == null) {

      contentRatings = new HashMap<Integer, IAttribute>(); // DatasetID, AttributeData
      Set<INode> testContentKeys = testNode.getAttributeKeys();
      for (INode testContentKey : testContentKeys) {
        contentRatings.put((int) testContentKey.getDatasetId(), null);
      }
    }

    // Look for content nodes on the list and add it to collected ratings map
    Set<INode> posContentKeys = position.getAttributeKeys();
    Map<Integer, IAttribute> posContentMap = new HashMap<Integer, IAttribute>();
    for (INode posContentKey : posContentKeys) {

      // Find Dataset ID of Content Node -> FIXME: very inefficient
      int contentDatasetID = 0;
      for (Iterator<Entry<Integer, INode>> iter = leavesMapC.entrySet().iterator();
          iter.hasNext(); ) {
        Map.Entry<Integer, INode> e = (Map.Entry<Integer, INode>) iter.next();
        if (posContentKey.getId() == e.getValue().getId()) {
          contentDatasetID = e.getKey();
        }
      }

      posContentMap.put(contentDatasetID, position.getAttributeValue(posContentKey));
    }

    for (Entry<Integer, IAttribute> contentRating : contentRatings.entrySet()) {

      int datasetID = contentRating.getKey();
      IAttribute rating = contentRating.getValue();

      // Check if value still needs to be found
      if (rating == null) {

        try {
          IAttribute contentAttributes = posContentMap.get(datasetID);
          contentRatings.put(datasetID, posContentMap.get(datasetID));
        } catch (Exception e) {
          System.out.println(e);
        }
      }
    }

    // Check if all movies were found
    if (contentRatings.containsValue(null) != true) {
      return contentRatings;
    } else {

      // Go one level up if possible
      if (position.getParent() != null) {
        contentRatings = collectRatings(position.getParent(), testNode, contentRatings);
        if (contentRatings != null) {
          return contentRatings;
        }
      } else {
        return contentRatings;
      }
    }

    return null;
  }