protocol implementation is messy but works