private void removeHelper(RedBlackNode<T> toRemove) {
        if (toRemove.isLeaf()) {
            if (toRemove.getColor() == Color.RED) { //removing a red leaf is never a problem
                delete(toRemove);
            } else { //it is a black leaf
                if (toRemove == root) { //there is only one node in the whole tree
                    root = null;
                } else { // we are removing a black leaf, this is the hard case
                    fixBlackLeaf(toRemove);
                    delete(toRemove);
                }
            }
        } else if (toRemove.getRight() != null && toRemove.getLeft() != null) { //the node is not a leaf so we swap it's data with it's predecessor and remove that one (always a leaf)
            RedBlackNode<T> predecessor = findPredecessor(toRemove);  
            assert(predecessor != null);
            toRemove.setData(predecessor.getData());
            removeHelper(predecessor);

        } else if (toRemove.getLeft() != null) { //right branch is empty, left is not
            assert (toRemove.getColor() == Color.BLACK);
            assert (toRemove.getLeft().getColor() == Color.RED);
            toRemove.getLeft().setColor(Color.BLACK);
            replaceNode(toRemove, toRemove.getLeft());
            delete(toRemove);
        } else if (toRemove.getRight() != null) { //left branch is empty, right is not
            assert (toRemove.getColor() == Color.BLACK);
            assert (toRemove.getRight().getColor() == Color.RED);
            toRemove.getRight().setColor(Color.BLACK);
            replaceNode(toRemove, toRemove.getRight());
            delete(toRemove);
        }
    }