private TreeNode2<T> delete(TreeNode2<T> root, T value) {
        // first find the node that has the value
        TreeNode2<T> node = find(root, value);

        // not found
        if (node == null) { return null; }

        // search for a leaf to delete; swap values along the way
        while (true) {
            if (node.left != null) {        
                // look for predecessor in left subtree
                TreeNode2<T> predecessor = node.left;
                while (predecessor.right != null) {
                    predecessor = predecessor.right;
                }
                node.value = predecessor.value;
                node = predecessor;
                
            } else if (node.right != null){
                // look for successor in right subtree
                TreeNode2<T> successor = node.right;
                while (successor.left != null) {
                    successor = successor.left;
                }
                node.value = successor.value;
                node = successor;
            } else {
                // it's a leaf
                break;
            }
        }
        
        // delete the leaf
        TreeNode2<T> parent = node.parent;
        if (parent != null) {
            if (parent.left == node) {
                parent.left = null;
            } else {
                parent.right = null;
            }
            // root node does not change (value may change)
            return root;
        } else {
            // the tree is empty
            return null;
        }
    }