Sorry for the large diff, the implementation is quite long, and I can't really find a good way to split it into commits.