define([
    '/bower_components/chainpad/chainpad.dist.js',
], function (ChainPad) {
    var Diff = ChainPad.Diff;

    var isSpace = function (S, i) {
        return /^\s$/.test(S.charAt(i));
    };

    var leadingBoundary = function (S, offset) {
        if (/\s/.test(S.charAt(offset))) { return offset; }
        while (offset > 0) {
            offset--;
            if (isSpace(S, offset)) { offset++; break; }
        }
        return offset;
    };

    var trailingBoundary = function (S, offset) {
        if (isSpace(S, offset)) { return offset; }
        while (offset < S.length && !/\s/.test(S.charAt(offset))) {
            offset++;
        }
        return offset;
    };

    var opsToWords = function (previous, current) {
        var output = [];
        Diff.diff(previous, current).forEach(function (op) {
            // ignore deleted sections...
            var offset = op.offset;
            var toInsert = op.toInsert;

            // given an operation,  check whether it is a word fragment,
            // if it is, expand it to its word boundaries
            var first = current.slice(leadingBoundary(current, offset), offset);
            var last = current.slice(offset + toInsert.length, trailingBoundary(current, offset + toInsert.length));

            var result = first + toInsert + last;
            // concat-in-place
            Array.prototype.push.apply(output, result.split(/\s+/));
        });
        return output.filter(Boolean);
    };

    var runningDiff = function (getter, f, time) {
        var last = getter();
        // first time through, send all the words :D
        f(opsToWords("", last));
        return setInterval(function () {
            var current = getter();

            // find inserted words...
            var words = opsToWords(last, current);
            last = current;
            f(words);
        }, time);
    };

    return runningDiff;
});