/**
  * Returns the left or right child of a node depending the specified side
  *
  * @param node Parent Node
  * @param side Specified side
  * @return Child node based on size
  */
 protected TreeNode<K, V> getChildNode(TreeNode<K, V> node, Side side) {
   switch (side) {
     case LEFT:
       return node.getLeftNode();
     case RIGHT:
       return node.getRightNode();
   }
   return null;
 }
 /**
  * Joins two trees
  *
  * @param childNode Child node
  * @param parentNode Parent node
  * @param side Side of the parent node to join child node to
  */
 protected void joinTrees(TreeNode<K, V> childNode, TreeNode<K, V> parentNode, Side side) {
   if (parentNode == null) {
     root = childNode;
   } else {
     switch (side) {
       case RIGHT:
         parentNode.setRightNode(childNode);
         break;
       case LEFT:
         parentNode.setLeftNode(childNode);
         break;
     }
   }
 }
 @Override
 public V put(K key, V value) {
   Path<TreeNode<K, V>> path = new Path<TreeNode<K, V>>();
   TreeNode<K, V> node = this.findNode(key, path);
   if (node == null) {
     TreeNode<K, V> newLeaf = new TreeNode<K, V>(key, value);
     this.joinTrees(newLeaf, path.getLastParent(), path.getLastSide());
     super.size++;
     return null;
   } else {
     V result = node.getValue();
     node.setValue(value);
     return result;
   }
 }
  /**
   * Returns the node with a given key and fills the path variable with the corresponding
   * information
   *
   * @param key Key to search for
   * @param path Path variable to fill
   * @return The node with a given key
   */
  protected TreeNode<K, V> findNode(K key, Path<TreeNode<K, V>> path) {
    path.addRightStep(null);

    TreeNode<K, V> node = this.root;

    while (node != null) {
      int comparisonResult = node.getKey().compareTo(key);
      if (comparisonResult < 0) {
        if (path != null) path.addRightStep(node);
        node = node.getRightNode();
      } else if (comparisonResult > 0) {
        if (path != null) path.addLeftStep(node);
        node = node.getLeftNode();
      } else {
        return node;
      }
    }

    return null;
  }
  /**
   * Deletes a node with the specified key, if one is found, and stores the path traveled path in
   * path
   *
   * @param key Key to remove
   * @param path Traveled path
   * @return The deleted node's value, if no node was found returns null
   */
  protected V delete(K key, Path<TreeNode<K, V>> path) {
    TreeNode<K, V> node = this.findNode(key, path);
    if (node == null) {
      return null;
    }

    V result = node.getValue();
    if (node.getLeftNode() == null) {
      this.joinTrees(node.getRightNode(), path.getLastParent(), path.getLastSide());
    } else if (node.getRightNode() == null) {
      this.joinTrees(node.getLeftNode(), path.getLastParent(), path.getLastSide());
    } else {
      path.addRightStep(node);
      TreeNode<K, V> minNode = this.getLeafBySide(node.getRightNode(), Side.LEFT, path);
      node.setKey(minNode.getKey());
      node.setValue(minNode.getValue());
      this.joinTrees(minNode.getRightNode(), path.getLastParent(), path.getLastSide());
    }
    super.size--;
    return result;
  }
 @Override
 public V get(K key) {
   TreeNode<K, V> node = this.findNode(key);
   return node != null ? node.getValue() : null;
 }