def divMod(BigInteger number, BigInteger divisor) {
    def qr = number.divideAndRemainder(divisor)
    [div:qr[0], remainder:qr[1]]
}

def toText(value) {
    value = value as BigInteger
    def units = ['zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten',
            'eleven', 'twelve', 'thirteen', 'fourteen', 'fifteen', 'sixteen', 'seventeen', 'eighteen', 'nineteen']
    def tens = ['', '', 'twenty', 'thirty', 'forty', 'fifty', 'sixty', 'seventy', 'eighty', 'ninety']
    def big = ['', 'thousand'] + ['m', 'b', 'tr', 'quadr', 'quint', 'sext', 'sept', 'oct', 'non', 'dec'].collect { "${it}illion"}

    if (value < 0) {
        "negative ${toText(-value)}"
    } else if (value < 20) {
        units[value]
    } else if (value < 100) {
        divMod(value, 10).with { "${tens[div]} ${units[remainder]}".replace(' zero', '') }
    } else if (value < 1000) {
        divMod(value, 100).with { "${toText(div)} hundred and ${toText(remainder)}".replace(' and zero', '') }
    } else {
        def chunks = []
        while (value != 0) {
            divMod(value, 1000).with {
                chunks << remainder
                value = div
            }
        }
        if (chunks.size() > big.size()) {
            throw new IllegalArgumentException("Number overflow")
        }
        def text = []
        (0..<chunks.size()).each { index ->
            if (chunks[index] > 0) {
                text << "${toText(chunks[index])}${index == 0? '': ' ' + big[index]}"
                if (index == 0 && chunks[index] < 100) {
                    text << "and"
                }
            }
        }
        text.reverse().join(', ').replace(', and,', ' and')
    }
}