if (!window.ice) {
    window.ice = new Object;
}
if (!window.ice.icepush) {
    (function(namespace) {
        window.ice.icepush = true;
function apply(fun, arguments) {
    return fun.apply(fun, arguments);
}
function withArguments() {
    var args = arguments;
    return function(fun) {
        apply(fun, args);
    };
}
function let(definition) {
    return function() {
        return apply(definition, arguments);
    };
}
function curry() {
    var args = arguments;
    return function() {
        var curriedArguments = [];
        var fun = args[0];
        for (var i = 1; i < args.length; i++) curriedArguments.push(args[i]);
        for (var j = 0; j < arguments.length; j++) curriedArguments.push(arguments[j]);
        return apply(fun, curriedArguments);
    };
}
function $witch(tests, defaultRun) {
    return function(val) {
        var args = arguments;
        var conditions = [];
        var runs = [];
        tests(function(condition, run) {
            conditions.push(condition);
            runs.push(run);
        });
        var size = conditions.length;
        for (var i = 0; i < size; i++) {
            if (apply(conditions[i], args)) {
                return apply(runs[i], args);
            }
        }
        if (defaultRun) apply(defaultRun, args);
    };
}
function identity(arg) {
    return arg;
}
function negate(b) {
    return !b;
}
function greater(a, b) {
    return a > b;
}
function less(a, b) {
    return a < b;
}
function not(a) {
    return !a;
}
function multiply(a, b) {
    return a * b;
}
function plus(a, b) {
    return a + b;
}
function max(a, b) {
    return a > b ? a : b;
}
function increment(value, step) {
    return value + (step ? step : 1);
}
function decrement(value, step) {
    return value - (step ? step : 1);
}
function any() {
    return true;
}
function none() {
    return false;
}
function noop() {
}
function isArray(a) {
    return a && !!a.push;
}
function isString(s) {
    return typeof s == 'string';
}
function isNumber(s) {
    return typeof s == 'number';
}
function isBoolean(s) {
    return typeof s == 'boolean';
}
function isIndexed(s) {
    return typeof s.length == 'number';
}
function isObject(o) {
    return o.instanceTag == o;
}
var uid = (function() {
    var id = 0;
    return function() {
        return id++;
    };
})();
function operationNotSupported() {
    throw 'operation not supported';
}
function operator(defaultOperation) {
    return function() {
        var args = arguments;
        var instance = arguments[0];
        if (instance.instanceTag && instance.instanceTag == instance) {
            var method = instance(arguments.callee);
            if (method) {
                return method.apply(method, args);
            } else {
                operationNotSupported();
            }
        } else {
            return defaultOperation ? defaultOperation.apply(defaultOperation, args) : operationNotSupported();
        }
    };
}
var asString = operator(String);
var asNumber = operator(Number);
var hash = operator(function(o) {
    var s;
    if (isString(o)) {
        s = o;
    } else if (isNumber(o)) {
        return Math.abs(Math.round(o));
    } else {
        s = o.toString();
    }
    var h = 0;
    for (var i = 0, l = s.length; i < l; i++) {
        var c = parseInt(s[i], 36);
        if (!isNaN(c)) {
            h = c + (h << 6) + (h << 16) - h;
        }
    }
    return Math.abs(h);
});
var equal = operator(function(a, b) {
    return a == b;
});
function object(definition) {
    var operators = [];
    var methods = [];
    var unknown = null;
    var id = uid();
    operators.push(hash);
    methods.push(function(self) {
        return id;
    });
    operators.push(equal);
    methods.push(function(self, other) {
        return self == other;
    });
    operators.push(asString);
    methods.push(function(self) {
        return 'Object:' + id.toString(16);
    });
    definition(function(operator, method) {
        var size = operators.length;
        for (var i = 0; i < size; i++) {
            if (operators[i] == operator) {
                methods[i] = method;
                return;
            }
        }
        operators.push(operator);
        methods.push(method);
    }, function(method) {
        unknown = method;
    });
    function self(operator) {
        var size = operators.length;
        for (var i = 0; i < size; i++) {
            if (operators[i] == operator) {
                return methods[i];
            }
        }
        return unknown;
    }
    return self.instanceTag = self;
}
function objectWithAncestors() {
    var definition = arguments[0];
    var args = arguments;
    var o = object(definition);
    function self(operator) {
        var method = o(operator);
        if (method) {
            return method;
        } else {
            var size = args.length;
            for (var i = 1; i < size; i++) {
                var ancestor = args[i];
                var overriddenMethod = ancestor(operator);
                if (overriddenMethod) {
                    return overriddenMethod;
                }
            }
            return null;
        }
    }
    return self.instanceTag = self;
}
var indexOf = operator($witch(function(condition) {
    condition(isString, function(items, item) {
        return items.indexOf(item);
    });
    condition(isArray, function(items, item) {
        for (var i = 0, size = items.length; i < size; i++) {
            if (items[i] == item) {
                return i;
            }
        }
        return -1;
    });
    condition(any, operationNotSupported);
}));
var concatenate = operator(function(items, other) {
    return items.concat(other);
});
var append = operator(function(items, item) {
    if (isArray(items)) {
        items.push(item);
        return items;
    } else {
        operationNotSupported();
    }
});
var insert = operator($witch(function(condition) {
    condition(isArray, function(items, item) {
        items.unshift(item);
        return items;
    });
    condition(any, operationNotSupported);
}));
var each = operator(function(items, iterator) {
    var size = items.length;
    for (var i = 0; i < size; i++) iterator(items[i], i);
});
var inject = operator(function(items, initialValue, injector) {
    var tally = initialValue;
    var size = items.length;
    for (var i = 0; i < size; i++) {
        tally = injector(tally, items[i]);
    }
    return tally;
});
var select = operator($witch(function(condition) {
    condition(isArray, function(items, selector) {
        return inject(items, [], function(tally, item) {
            return selector(item) ? append(tally, item) : tally;
        });
    });
    condition(isString, function(items, selector) {
        return inject(items, '', function(tally, item) {
            return selector(item) ? concatenate(tally, item) : tally;
        });
    });
    condition(isIndexed, function(items, selector) {
        return Stream(function(cellConstructor) {
            function selectingStream(start, end) {
                if (start > end) return null;
                var item = items[start];
                return selector(item) ?
                       function() {
                           return cellConstructor(item, selectingStream(start + 1, end));
                       } : selectingStream(start + 1, end);
            }
            return selectingStream(0, items.length - 1);
        });
    });
}));
var detect = operator(function(items, iterator, notDetectedThunk) {
    var size = items.length;
    for (var i = 0; i < size; i++) {
        var element = items[i];
        if (iterator(element, i)) {
            return element;
        }
    }
    return notDetectedThunk ? notDetectedThunk(items) : null;
});
var contains = operator($witch(function(condition) {
    condition(isString, function(items, item) {
        return items.indexOf(item) > -1;
    });
    condition(isArray, function(items, item) {
        var size = items.length;
        for (var i = 0; i < size; i++) {
            if (items[i] == item) {
                return true;
            }
        }
        return false;
    });
    condition(any, operationNotSupported);
}));
var size = operator(function(items) {
    return items.length;
});
var empty = operator(function(items) {
    items.length = 0;
});
var isEmpty = operator(function(items) {
    return items.length == 0;
});
var notEmpty = function(items) {
    return !isEmpty(items);
};
var collect = operator($witch(function(condition) {
    condition(isString, function(items, collector) {
        return inject(items, '', function(tally, item) {
            return concatenate(tally, collector(item));
        });
    });
    condition(isArray, function(items, collector) {
        return inject(items, [], function(tally, item) {
            return append(tally, collector(item));
        });
    });
    condition(isIndexed, function(items, collector) {
        return Stream(function(cellConstructor) {
            function collectingStream(start, end) {
                if (start > end) return null;
                return function() {
                    return cellConstructor(collector(items[start], start), collectingStream(start + 1, end));
                };
            }
            return collectingStream(0, items.length - 1);
        });
    });
}));
var sort = operator(function(items, sorter) {
    return copy(items).sort(function(a, b) {
        return sorter(a, b) ? -1 : 1;
    });
});
var reverse = operator(function(items) {
    return copy(items).reverse();
});
var copy = operator(function(items) {
    return inject(items, [], curry(append));
});
var join = operator(function(items, separator) {
    return items.join(separator);
});
var inspect = operator();
var reject = function(items, rejector) {
    return select(items, function(i) {
        return !rejector(i);
    });
};
var intersect = operator(function(items, other) {
    return select(items, curry(contains, other));
});
var complement = operator(function(items, other) {
    return reject(items, curry(contains, other));
});
var broadcast = function(items, args) {
    args = args || [];
    each(items, function(i) {
        apply(i, args);
    });
};
var broadcaster = function(items) {
    return function() {
        var args = arguments;
        each(items, function(i) {
            apply(i, args);
        });
    };
};
var asArray = function(items) {
    return inject(items, [], append);
};
var asSet = function(items) {
    return inject(items, [], function(set, item) {
        if (not(contains(set, item))) {
            append(set, item);
        }
        return set;
    });
};
var key = operator();
var value = operator();
function Cell(k, v) {
    return object(function(method) {
        method(key, function(self) {
            return k;
        });
        method(value, function(self) {
            return v;
        });
        method(asString, function(self) {
            return 'Cell[' + asString(k) + ': ' + asString(v) + ']';
        });
    });
}
function Stream(streamDefinition) {
    var stream = streamDefinition(Cell);
    return object(function(method) {
        method(each, function(self, iterator) {
            var cursor = stream;
            while (cursor != null) {
                var cell = cursor();
                iterator(key(cell));
                cursor = value(cell);
            }
        });
        method(inject, function(self, initialValue, injector) {
            var tally = initialValue;
            var cursor = stream;
            while (cursor != null) {
                var cell = cursor();
                tally = injector(tally, key(cell));
                cursor = value(cell);
            }
            return tally;
        });
        method(join, function(self, separator) {
            var tally;
            var cursor = stream;
            while (cursor != null) {
                var cell = cursor();
                var itemAsString = asString(key(cell));
                tally = tally ? tally + separator + itemAsString : itemAsString;
                cursor = value(cell);
            }
            return tally;
        });
        method(collect, function(self, collector) {
            return Stream(function(cellConstructor) {
                function collectingStream(stream) {
                    if (!stream) return null;
                    var cell = stream();
                    return function() {
                        return cellConstructor(collector(key(cell)), collectingStream(value(cell)));
                    };
                }
                return collectingStream(stream);
            });
        });
        method(contains, function(self, item) {
            var cursor = stream;
            while (cursor != null) {
                var cell = cursor();
                if (item == key(cell)) return true;
                cursor = value(cell);
            }
            return false;
        });
        method(size, function(self) {
            var cursor = stream;
            var i = 0;
            while (cursor != null) {
                i++;
                cursor = value(cursor());
            }
            return i;
        });
        method(select, function(self, selector) {
            return Stream(function(cellConstructor) {
                function select(stream) {
                    if (!stream) return null;
                    var cell = stream();
                    var k = key(cell);
                    var v = value(cell);
                    return selector(k) ? function() {
                        return cellConstructor(k, select(v));
                    } : select(v);
                }
                return select(stream);
            });
        });
        method(detect, function(self, detector, notDetectedThunk) {
            var cursor = stream;
            var result;
            while (cursor != null) {
                var cell = cursor();
                var k = key(cell);
                if (detector(k)) {
                    result = k;
                    break;
                }
                cursor = value(cell);
            }
            if (result) {
                return result;
            } else {
                return notDetectedThunk ? notDetectedThunk(self) : null;
            }
        });
        method(isEmpty, function(self) {
            return stream == null;
        });
        method(copy, function(self) {
            return Stream(streamDefinition);
        });
        method(asString, function(self) {
            return 'Stream[' + join(self, ', ') + ']';
        });
    });
}
var indexOf = function(s, substring) {
    var index = s.indexOf(substring);
    if (index >= 0) {
        return index;
    } else {
        throw '"' + s + '" does not contain "' + substring + '"';
    }
};
var lastIndexOf = function(s, substring) {
    var index = s.lastIndexOf(substring);
    if (index >= 0) {
        return index;
    } else {
        throw 'string "' + s + '" does not contain "' + substring + '"';
    }
};
var startsWith = function(s, pattern) {
    return s.indexOf(pattern) == 0;
};
var endsWith = function(s, pattern) {
    return s.lastIndexOf(pattern) == s.length - pattern.length;
};
var containsSubstring = function(s, substring) {
    return s.indexOf(substring) >= 0;
};
var blank = function(s) {
    return /^\s*$/.test(s);
};
var split = function(s, separator) {
    return s.length == 0 ? [] : s.split(separator);
};
var replace = function(s, regex, replace) {
    return s.replace(regex, replace);
};
var toLowerCase = function(s) {
    return s.toLowerCase();
};
var toUpperCase = function(s) {
    return s.toUpperCase();
};
var substring = function(s, from, to) {
    return s.substring(from, to);
};
var trim = function(s) {
    s = s.replace(/^\s+/, '');
    for (var i = s.length - 1; i >= 0; i--) {
        if (/\S/.test(s.charAt(i))) {
            s = s.substring(0, i + 1);
            break;
        }
    }
    return s;
};
var asNumber = Number;
var asBoolean = function(s) {
    return 'true' == s || 'any' == s;
};
var asRegexp = function(s) {
    return new RegExp(s);
};
function registerListener(eventType, obj, listener) {
    if (obj.addEventListener) {
        obj.addEventListener(eventType, listener, false);
        return function() {
            obj.removeEventListener(eventType, listener, false);
        }
    } else {
        var type = 'on' + eventType;
        obj.attachEvent(type, listener);
        return function() {
            obj.detachEvent(type, listener);
        }
    }
}
var onLoad = curry(registerListener, 'load');
var onUnload = curry(registerListener, 'unload');
var onBeforeUnload = curry(registerListener, 'beforeunload');
var onResize = curry(registerListener, 'resize');
var onKeyPress = curry(registerListener, 'keypress');
var onKeyUp = curry(registerListener, 'keyup');
window.width = function() {
    return window.innerWidth ? window.innerWidth : (document.documentElement && document.documentElement.clientWidth) ? document.documentElement.clientWidth : document.body.clientWidth;
};
window.height = function() {
    return window.innerHeight ? window.innerHeight : (document.documentElement && document.documentElement.clientHeight) ? document.documentElement.clientHeight : document.body.clientHeight;
};
var debug = operator();
var info = operator();
var warn = operator();
var error = operator();
var childLogger = operator();
var log = operator();
var threshold = operator();
var enable = operator();
var disable = operator();
var toggle = operator();
function Logger(category, handler) {
    return object(function(method) {
        each([debug, info, warn, error], function(priorityOperator) {
            method(priorityOperator, function(self, message, exception) {
                log(handler, priorityOperator, category, message, exception);
            });
        });
        method(childLogger, function(self, categoryName, newHandler) {
            return Logger(append(copy(category), categoryName), newHandler || handler);
        });
        method(asString, function(self) {
            return 'Logger[' + join(category, '.') + ']';
        });
    });
}
function formatOutput(category, message) {
    var timestamp = (new Date()).toUTCString();
    return join(['[', join(category, '.'), '] [', timestamp, '] ', message], '');
}
function LocalStorageLogHandler(handler) {
    var enabled = false;
    if (window.addEventListener) {
        window.addEventListener('storage', function (e) {
            if (e.key == 'ice.localStorageLogHandler.enabled') {
                enabled = e.newValue == 'yes';
            }
        }, false);
    }
    function storeLogMessage(level, message, exception) {
        var previousMessages = localStorage['ice.localStorageLogHandler.store'] || '';
        var fullMessage = '[' + level + '] [' + ice.windowID + '] ' + message;
        if (exception) {
            fullMessage = fullMessage + '\n' + exception.message;
        }
        var messages = previousMessages + '%%' + fullMessage;
        var maxStorageUsed = localStorage['ice.localStorageLogHandler.maxSize'] || 500;
        var overflow = messages.length - maxStorageUsed * 1024;
        if (overflow > 0) {
            messages = messages.substr(overflow);
        }
        localStorage['ice.localStorageLogHandler.currentEntry'] = fullMessage;
        localStorage['ice.localStorageLogHandler.store'] = messages;
    }
    return object(function(method) {
        method(threshold, function(self, priority) {
            threshold(handler, priority);
        });
        method(log, function(self, operation, category, message, exception) {
            if (window.localStorage && (window.localStorage['ice.localStorageLogHandler.enabled'] || enabled)) {
                var formattedMessage = formatOutput(category, message);
                var priorityName;
                switch (operation) {
                    case debug:
                        priorityName = 'debug';
                        break;
                    case info:
                        priorityName = 'info ';
                        break;
                    case warn:
                        priorityName = 'warn ';
                        break;
                    case error:
                        priorityName = 'error';
                        break;
                    default:
                        priorityName = 'debug';
                }
                storeLogMessage(priorityName, formattedMessage, exception);
            }
            log(handler, operation, category, message, exception);
        });
    });
}
function ConsoleLogHandler(priority) {
    var ieConsole = !window.console.debug;
    var debugPrimitive = ieConsole ?
        function(self, category, message, exception) {
            exception ? console.log(formatOutput(category, message), '\n', exception) : console.log(formatOutput(category, message));
        } :
        function(self, category, message, exception) {
            exception ? console.debug(formatOutput(category, message), exception) : console.debug(formatOutput(category, message));
        };
    var infoPrimitive = ieConsole ?
        function(self, category, message, exception) {
            exception ? console.info(formatOutput(category, message), '\n', exception) : console.info(formatOutput(category, message));
        } :
        function(self, category, message, exception) {
            exception ? console.info(formatOutput(category, message), exception) : console.info(formatOutput(category, message));
        };
    var warnPrimitive = ieConsole ?
        function(self, category, message, exception) {
            exception ? console.warn(formatOutput(category, message), '\n', exception) : console.warn(formatOutput(category, message));
        } :
        function(self, category, message, exception) {
            exception ? console.warn(formatOutput(category, message), exception) : console.warn(formatOutput(category, message));
        };
    var errorPrimitive = ieConsole ?
        function(self, category, message, exception) {
            exception ? console.error(formatOutput(category, message), '\n', exception) : console.error(formatOutput(category, message));
        } :
        function(self, category, message, exception) {
            exception ? console.error(formatOutput(category, message), exception) : console.error(formatOutput(category, message));
        };
    var handlers = [
        Cell(debug, object(function(method) {
            method(debug, debugPrimitive);
            method(info, infoPrimitive);
            method(warn, warnPrimitive);
            method(error, errorPrimitive);
        })),
        Cell(info, object(function(method) {
            method(debug, noop);
            method(info, infoPrimitive);
            method(warn, warnPrimitive);
            method(error, errorPrimitive);
        })),
        Cell(warn, object(function(method) {
            method(debug, noop);
            method(info, noop);
            method(warn, warnPrimitive);
            method(error, errorPrimitive);
        })),
        Cell(error, object(function(method) {
            method(debug, noop);
            method(info, noop);
            method(warn, noop);
            method(error, errorPrimitive);
        }))
    ];
    var handler;
    function selectHandler(p) {
        handler = value(detect(handlers, function(cell) {
            return key(cell) == p;
        }));
    }
    selectHandler(priority || debug);
    return object(function (method) {
        method(threshold, function(self, priority) {
            selectHandler(priority);
        });
        method(log, function(self, operation, category, message, exception) {
            operation(handler, category, message, exception);
        });
    });
}
var FirebugLogHandler = ConsoleLogHandler;
function WindowLogHandler(thresholdPriority, name) {
    var lineOptions = [25, 50, 100, 200, 400];
    var numberOfLines = lineOptions[3];
    var categoryMatcher = /.*/;
    var closeOnExit = true;
    var logContainer;
    var logEntry = noop;
    function trimLines() {
        var nodes = logContainer.childNodes;
        var trim = size(nodes) - numberOfLines;
        if (trim > 0) {
            each(copy(nodes), function(node, index) {
                if (index < trim) logContainer.removeChild(node);
            });
        }
    }
    function trimAllLines() {
        each(copy(logContainer.childNodes), function(node) {
            logContainer.removeChild(node);
        });
    }
    function toggle() {
        var disabled = logEntry == noop;
        logEntry = disabled ? displayEntry : noop;
        return !disabled;
    }
    function displayEntry(priorityName, colorName, category, message, exception) {
        setTimeout(function(){
            try {
                var categoryName = join(category, '.');
                if (categoryMatcher.test(categoryName)) {
                    var elementDocument = logContainer.ownerDocument;
                    var timestamp = new Date();
                    var completeMessage = join(['[', categoryName, '] : ', message, (exception ? join(['\n', exception.name, ' <', exception.message, '>'], '') : '')], '');
                    each(split(completeMessage, '\n'), function(line) {
                        if (/(\w+)/.test(line)) {
                            var eventNode = elementDocument.createElement('div');
                            eventNode.style.padding = '3px';
                            eventNode.style.color = colorName;
                            eventNode.setAttribute("title", timestamp + ' | ' + priorityName);
                            logContainer.appendChild(eventNode).appendChild(elementDocument.createTextNode(line));
                        }
                    });
                    logContainer.scrollTop = logContainer.scrollHeight;
                }
                trimLines();
            } catch (ex) {
                logEntry = noop;
            }
        }, 1);
    }
    function showWindow() {
        var logWindow = window.open('', '_blank', 'scrollbars=1,width=800,height=680');
        try {
            var windowDocument = logWindow.document;
            var documentBody = windowDocument.body;
            each(copy(documentBody.childNodes), function(e) {
                windowDocument.body.removeChild(e);
            });
            documentBody.appendChild(windowDocument.createTextNode(' Close on exit '));
            var closeOnExitCheckbox = windowDocument.createElement('input');
            closeOnExitCheckbox.style.margin = '2px';
            closeOnExitCheckbox.setAttribute('type', 'checkbox');
            closeOnExitCheckbox.defaultChecked = true;
            closeOnExitCheckbox.checked = true;
            closeOnExitCheckbox.onclick = function() {
                closeOnExit = closeOnExitCheckbox.checked;
            };
            documentBody.appendChild(closeOnExitCheckbox);
            documentBody.appendChild(windowDocument.createTextNode(' Lines '));
            var lineCountDropDown = windowDocument.createElement('select');
            lineCountDropDown.style.margin = '2px';
            each(lineOptions, function(count, index) {
                var option = lineCountDropDown.appendChild(windowDocument.createElement('option'));
                if (numberOfLines == count) lineCountDropDown.selectedIndex = index;
                option.appendChild(windowDocument.createTextNode(asString(count)));
            });
            documentBody.appendChild(lineCountDropDown);
            documentBody.appendChild(windowDocument.createTextNode(' Category '));
            var categoryInputText = windowDocument.createElement('input');
            categoryInputText.style.margin = '2px';
            categoryInputText.setAttribute('type', 'text');
            categoryInputText.setAttribute('value', categoryMatcher.source);
            categoryInputText.onchange = function() {
                categoryMatcher = new RegExp(categoryInputText.value);
            };
            documentBody.appendChild(categoryInputText);
            documentBody.appendChild(windowDocument.createTextNode(' Level '));
            var levelDropDown = windowDocument.createElement('select');
            levelDropDown.style.margin = '2px';
            var levels = [Cell('debug', debug), Cell('info', info), Cell('warn', warn), Cell('error', error)];
            each(levels, function(priority, index) {
                var option = levelDropDown.appendChild(windowDocument.createElement('option'));
                if (thresholdPriority == value(priority)) levelDropDown.selectedIndex = index;
                option.appendChild(windowDocument.createTextNode(key(priority)));
            });
            levelDropDown.onchange = function(event) {
                thresholdPriority = value(levels[levelDropDown.selectedIndex]);
            };
            documentBody.appendChild(levelDropDown);
            var startStopButton = windowDocument.createElement('input');
            startStopButton.style.margin = '2px';
            startStopButton.setAttribute('type', 'button');
            startStopButton.setAttribute('value', 'Stop');
            startStopButton.onclick = function() {
                startStopButton.setAttribute('value', toggle() ? 'Stop' : 'Start');
            };
            documentBody.appendChild(startStopButton);
            var clearButton = windowDocument.createElement('input');
            clearButton.style.margin = '2px';
            clearButton.setAttribute('type', 'button');
            clearButton.setAttribute('value', 'Clear');
            documentBody.appendChild(clearButton);
            logContainer = documentBody.appendChild(windowDocument.createElement('pre'));
            logContainer.id = 'log-window';
            var logContainerStyle = logContainer.style;
            logContainerStyle.width = '100%';
            logContainerStyle.minHeight = '0';
            logContainerStyle.maxHeight = '550px';
            logContainerStyle.borderWidth = '1px';
            logContainerStyle.borderStyle = 'solid';
            logContainerStyle.borderColor = '#999';
            logContainerStyle.backgroundColor = '#ddd';
            logContainerStyle.overflow = 'scroll';
            lineCountDropDown.onchange = function(event) {
                numberOfLines = lineOptions[lineCountDropDown.selectedIndex];
                trimLines();
            };
            clearButton.onclick = trimAllLines;
            onUnload(window, function() {
                if (closeOnExit) {
                    logEntry = noop;
                    logWindow.close();
                }
            });
        } catch (e) {
            logWindow.close();
        }
    }
    onKeyUp(document, function(evt) {
        var event = $event(evt, document.documentElement);
        if (keyCode(event) == 84 && isCtrlPressed(event) && isShiftPressed(event)) {
            showWindow();
            logEntry = displayEntry;
        }
    });
    return object(function(method) {
        method(threshold, function(self, priority) {
            thresholdPriority = priority;
        });
        method(log, function(self, operation, category, message, exception) {
            operation(self, category, message, exception);
        });
        method(debug, function(self, category, message, exception) {
            logEntry('debug', '#333', category, message, exception);
        });
        method(info, function(self, category, message, exception) {
            logEntry('info', 'green', category, message, exception);
        });
        method(warn, function(self, category, message, exception) {
            logEntry('warn', 'orange', category, message, exception);
        });
        method(error, function(self, category, message, exception) {
            logEntry('error', 'red', category, message, exception);
        });
    });
}
function lookupCookieValue(name) {
    var tupleString = detect(split(asString(document.cookie), '; '), function(tuple) {
        return startsWith(tuple, name);
    }, function() {
        throw 'Cannot find value for cookie: ' + name;
    });
    return decodeURIComponent(contains(tupleString, '=') ? split(tupleString, '=')[1] : '');
}
function lookupCookie(name, failThunk) {
    try {
        return Cookie(name, lookupCookieValue(name));
    } catch (e) {
        if (failThunk) {
            return failThunk();
        } else {
            throw e;
        }
    }
}
function existsCookie(name) {
    var exists = true;
    lookupCookie(name, function() {
        exists = false;
    });
    return exists;
}
var update = operator();
var remove = operator();
function Cookie(name, val, path) {
    val = val || '';
    path = path || '/';
    document.cookie = name + '=' + val + '; path=' + path;
    return object(function(method) {
        method(value, function(self) {
            return lookupCookieValue(name);
        });
        method(update, function(self, val) {
            document.cookie = name + '=' + encodeURIComponent(val) + '; path=' + path;
            return self;
        });
        method(remove, function(self) {
            var date = new Date();
            date.setTime(date.getTime() - 24 * 60 * 60 * 1000);
            document.cookie = name + '=; expires=' + date.toGMTString() + '; path=' + path;
        });
        method(asString, function(self) {
            return 'Cookie[' + name + ', ' + value(self) + ', ' + path + ']';
        });
    });
}
var run = operator();
var runOnce = operator();
var stop = operator();
function Delay(f, milliseconds) {
    return object(function(method) {
        var id = null;
        var canceled = false;
        method(run, function(self, times) {
            if (id || canceled) return;
            var call = times ? function() {
                try {
                    f();
                } finally {
                    if (--times < 1) stop(self);
                }
            } : f;
            id = setInterval(call, milliseconds);
            return self;
        });
        method(runOnce, function(self) {
            return run(self, 1);
        });
        method(stop, function(self) {
            if (id) {
                clearInterval(id);
                id = null;
            } else {
                canceled = true;
            }
        });
    });
}
function identifier(element) {
    return element ? element.id : null;
}
function tag(element) {
    return toLowerCase(element.nodeName);
}
function property(element, name) {
    return element[name];
}
function parents(element) {
    return Stream(function(cellConstructor) {
        function parentStream(e) {
            if (e == null || e == document) return null;
            return function() {
                return cellConstructor(e, parentStream(e.parentNode));
            };
        }
        return parentStream(element.parentNode);
    });
}
function enclosingForm(element) {
    return element.form || detect(parents(element), function(e) {
        return tag(e) == 'form';
    }, function() {
        throw 'cannot find enclosing form';
    });
}
function enclosingBridge(element) {
    return property(detect(parents(element), function(e) {
        return property(e, 'bridge') != null;
    }, function() {
        throw 'cannot find enclosing bridge';
    }), 'bridge');
}
function serializeElementOn(element, query) {
    var tagName = tag(element);
    switch (tagName) {
        case 'a':
            var name = element.name || element.id;
            if (name) addNameValue(query, name, name);
            break;
        case 'input':
            switch (element.type) {
                case 'image':
                case 'submit':
                case 'button':
                    addNameValue(query, element.name, element.value);
                    break;
            }
            break;
        case 'button':
            if (element.type == 'submit') addNameValue(query, element.name, element.value);
            break;
        default:
    }
}
function $elementWithID(id) {
    return document.getElementById(id);
}
var cancel = operator();
var cancelBubbling = operator();
var cancelDefaultAction = operator();
var isKeyEvent = operator();
var isMouseEvent = operator();
var capturedBy = operator();
var triggeredBy = operator();
var serializeEventOn = operator();
var serializePositionOn = operator();
var type = operator();
var yes = any;
var no = none;
function isIEEvent(event) {
    return event.srcElement;
}
function Event(event, capturingElement) {
    return object(function (method) {
        method(cancel, function (self) {
            cancelBubbling(self);
            cancelDefaultAction(self);
        });
        method(isKeyEvent, no);
        method(isMouseEvent, no);
        method(type, function (self) {
            return event.type;
        });
        method(triggeredBy, function (self) {
            return capturingElement;
        });
        method(capturedBy, function (self) {
            return capturingElement;
        });
        method(serializeEventOn, function (self, query) {
            serializeElementOn(capturingElement, query);
            addNameValue(query, 'ice.event.target', identifier(triggeredBy(self)));
            addNameValue(query, 'ice.event.captured', identifier(capturedBy(self)));
            addNameValue(query, 'ice.event.type', 'on' + type(self));
        });
        method(serializeOn, curry(serializeEventOn));
    });
}
function IEEvent(event, capturingElement) {
    return objectWithAncestors(function (method) {
        method(triggeredBy, function (self) {
            return event.srcElement ? event.srcElement : null;
        });
        method(cancelBubbling, function (self) {
            event.cancelBubble = true;
            if (event.stopPropagation) {
                event.stopPropagation();
            }
        });
        method(cancelDefaultAction, function (self) {
            event.returnValue = false;
            if (event.preventDefault) {
                event.preventDefault();
            }
        });
        method(asString, function (self) {
            return 'IEEvent[' + type(self) + ']';
        });
    }, Event(event, capturingElement));
}
function NetscapeEvent(event, capturingElement) {
    return objectWithAncestors(function (method) {
        method(triggeredBy, function (self) {
            return event.target ? event.target : null;
        });
        method(cancelBubbling, function (self) {
            try {
                event.stopPropagation();
            } catch (e) {
            }
        });
        method(cancelDefaultAction, function (self) {
            try {
                event.preventDefault();
            } catch (e) {
            }
        });
        method(asString, function (self) {
            return 'NetscapeEvent[' + type(self) + ']';
        });
    }, Event(event, capturingElement));
}
var isAltPressed = operator();
var isCtrlPressed = operator();
var isShiftPressed = operator();
var isMetaPressed = operator();
var serializeKeyOrMouseEventOn = operator();
function KeyOrMouseEvent(event) {
    return object(function (method) {
        method(isAltPressed, function (self) {
            return event.altKey;
        });
        method(isCtrlPressed, function (self) {
            return event.ctrlKey;
        });
        method(isShiftPressed, function (self) {
            return event.shiftKey;
        });
        method(isMetaPressed, function (self) {
            return event.metaKey;
        });
        method(serializeKeyOrMouseEventOn, function (self, query) {
            addNameValue(query, 'ice.event.alt', isAltPressed(self));
            addNameValue(query, 'ice.event.ctrl', isCtrlPressed(self));
            addNameValue(query, 'ice.event.shift', isShiftPressed(self));
            addNameValue(query, 'ice.event.meta', isMetaPressed(self));
        });
    });
}
var isLeftButton = operator();
var isRightButton = operator();
var positionX = operator();
var positionY = operator();
var serializeMouseEventOn = operator();
function MouseEvent(event) {
    return objectWithAncestors(function (method) {
        method(isMouseEvent, yes);
        method(serializeMouseEventOn, function (self, query) {
            serializeKeyOrMouseEventOn(self, query);
            addNameValue(query, 'ice.event.x', positionX(self));
            addNameValue(query, 'ice.event.y', positionY(self));
            addNameValue(query, 'ice.event.left', isLeftButton(self));
            addNameValue(query, 'ice.event.right', isRightButton(self));
        });
    }, KeyOrMouseEvent(event));
}
function MouseEventTrait(method) {
    method(serializeOn, function (self, query) {
        serializeEventOn(self, query);
        serializeMouseEventOn(self, query);
    });
}
function IEMouseEvent(event, capturingElement) {
    return objectWithAncestors(function (method) {
        MouseEventTrait(method);
        method(positionX, function (self) {
            return event.clientX + (document.documentElement.scrollLeft || document.body.scrollLeft);
        });
        method(positionY, function (self) {
            return event.clientY + (document.documentElement.scrollTop || document.body.scrollTop);
        });
        method(isLeftButton, function (self) {
            return event.button == 1;
        });
        method(isRightButton, function (self) {
            return event.button == 2;
        });
        method(asString, function (self) {
            return 'IEMouseEvent[' + type(self) + ']';
        });
    }, MouseEvent(event), IEEvent(event, capturingElement));
}
function NetscapeMouseEvent(event, capturingElement) {
    return objectWithAncestors(function (method) {
        MouseEventTrait(method);
        method(positionX, function (self) {
            return event.pageX;
        });
        method(positionY, function (self) {
            return event.pageY;
        });
        method(isLeftButton, function (self) {
            return event.which == 1;
        });
        method(isRightButton, function (self) {
            return event.which == 2;
        });
        method(asString, function (self) {
            return 'NetscapeMouseEvent[' + type(self) + ']';
        });
    }, MouseEvent(event), NetscapeEvent(event, capturingElement));
}
var keyCharacter = operator();
var keyCode = operator();
var serializeKeyEventOn = operator();
function KeyEvent(event) {
    return objectWithAncestors(function (method) {
        method(isKeyEvent, yes);
        method(keyCharacter, function (self) {
            return String.fromCharCode(keyCode(self));
        });
        method(serializeKeyEventOn, function (self, query) {
            serializeKeyOrMouseEventOn(self, query);
            addNameValue(query, 'ice.event.keycode', keyCode(self));
        });
    }, KeyOrMouseEvent(event));
}
function KeyEventTrait(method) {
    method(serializeOn, function (self, query) {
        serializeEventOn(self, query);
        serializeKeyEventOn(self, query);
    });
}
function IEKeyEvent(event, capturingElement) {
    return objectWithAncestors(function (method) {
        KeyEventTrait(method);
        method(keyCode, function (self) {
            return event.keyCode;
        });
        method(asString, function (self) {
            return 'IEKeyEvent[' + type(self) + ']';
        });
    }, KeyEvent(event), IEEvent(event, capturingElement));
}
function NetscapeKeyEvent(event, capturingElement) {
    return objectWithAncestors(function (method) {
        KeyEventTrait(method);
        method(keyCode, function (self) {
            return event.which == 0 ? event.keyCode : event.which;
        });
        method(asString, function (self) {
            return 'NetscapeKeyEvent[' + type(self) + ']';
        });
    }, KeyEvent(event), NetscapeEvent(event, capturingElement));
}
function isEnterKey(event) {
    return keyCode(event) == 13;
}
function isEscKey(event) {
    return keyCode(event) == 27;
}
function UnknownEvent(capturingElement) {
    return objectWithAncestors(function (method) {
        method(cancelBubbling, noop);
        method(cancelDefaultAction, noop);
        method(type, function (self) {
            return 'unknown';
        });
        method(asString, function (self) {
            return 'UnkownEvent[]';
        });
    }, Event(null, capturingElement));
}
var MouseListenerNames = [ 'onclick', 'ondblclick', 'onmousedown', 'onmousemove', 'onmouseout', 'onmouseover', 'onmouseup' ];
var KeyListenerNames = [ 'onkeydown', 'onkeypress', 'onkeyup', 'onhelp' ];
function $event(e, element) {
    var capturedEvent = e || window.event;
    if (capturedEvent && capturedEvent.type) {
        var eventType = 'on' + capturedEvent.type;
        if (contains(KeyListenerNames, eventType)) {
            return isIEEvent(capturedEvent) ? IEKeyEvent(capturedEvent, element) : NetscapeKeyEvent(capturedEvent, element);
        } else if (contains(MouseListenerNames, eventType)) {
            return isIEEvent(capturedEvent) ? IEMouseEvent(capturedEvent, element) : NetscapeMouseEvent(capturedEvent, element);
        } else {
            return isIEEvent(capturedEvent) ? IEEvent(capturedEvent, element) : NetscapeEvent(capturedEvent, element);
        }
    } else {
        return UnknownEvent(element);
    }
}
var asURIEncodedString = operator();
var serializeOn = operator();
var Parameter = function(name, value) {
    return objectWithAncestors(function(method) {
        method(asURIEncodedString, function(self) {
            return encodeURIComponent(name) + '=' + encodeURIComponent(value);
        });
        method(serializeOn, function(self, query) {
            addParameter(query, self);
        });
    }, Cell(name, value));
};
var addParameter = operator();
var addNameValue = operator();
var queryParameters = operator();
var addQuery = operator();
var appendToURI = operator();
var Query = function() {
    var parameters = [];
    return object(function(method) {
        method(queryParameters, function(self) {
            return parameters;
        });
        method(addParameter, function(self, parameter) {
            append(parameters, parameter);
            return self;
        });
        method(addNameValue, function(self, name, value) {
            append(parameters, Parameter(name, value));
            return self;
        });
        method(addQuery, function(self, appended) {
            serializeOn(appended, self);
            return self;
        });
        method(serializeOn, function(self, query) {
            each(parameters, curry(addParameter, query));
        });
        method(asURIEncodedString, function(self) {
            return join(collect(parameters, asURIEncodedString), '&');
        });
        method(appendToURI, function(self, uri) {
            if (not(isEmpty(parameters))) {
                return uri + (contains(uri, '?') ? '&' : '?') + asURIEncodedString(self);
            } else {
                return uri;
            }
        });
        method(asString, function(self) {
            return inject(parameters, '', function(tally, p) {
                return tally + '|' + key(p) + '=' + value(p) + '|\n';
            });
        });
    });
};
var getSynchronously = operator();
var getAsynchronously = operator();
var postSynchronously = operator();
var postAsynchronously = operator();
var Client = function(autoclose) {
    var newNativeRequest;
    if (window.XMLHttpRequest) {
        newNativeRequest = function() {
            return new XMLHttpRequest();
        };
    } else if (window.ActiveXObject) {
        newNativeRequest = function() {
            return new window.ActiveXObject('Microsoft.XMLHTTP');
        };
    } else {
        throw 'cannot create XMLHttpRequest';
    }
    function withNewQuery(setup) {
        var query = Query();
        setup(query);
        return query;
    }
    var autoClose = autoclose ? close : noop;
    return object(function(method) {
        method(getAsynchronously, function(self, uri, setupQuery, setupRequest, onResponse) {
            var nativeRequestResponse = newNativeRequest();
            var request = RequestProxy(nativeRequestResponse);
            var response = ResponseProxy(nativeRequestResponse);
            nativeRequestResponse.open('GET', appendToURI(withNewQuery(setupQuery), uri), true);
            setupRequest(request);
            nativeRequestResponse.onreadystatechange = function() {
                if (nativeRequestResponse.readyState == 4) {
                    onResponse(response, request);
                    autoClose(request);
                }
            };
            nativeRequestResponse.send('');
            return request;
        });
        method(getSynchronously, function(self, uri, setupQuery, setupRequest, onResponse) {
            var nativeRequestResponse = newNativeRequest();
            var request = RequestProxy(nativeRequestResponse);
            var response = ResponseProxy(nativeRequestResponse);
            nativeRequestResponse.open('GET', appendToURI(withNewQuery(setupQuery), uri), false);
            setupRequest(request);
            nativeRequestResponse.send('');
            onResponse(response, request);
            autoClose(request);
        });
        method(postAsynchronously, function(self, uri, setupQuery, setupRequest, onResponse) {
            var nativeRequestResponse = newNativeRequest();
            var request = RequestProxy(nativeRequestResponse);
            var response = ResponseProxy(nativeRequestResponse);
            nativeRequestResponse.open('POST', uri, true);
            setupRequest(request);
            nativeRequestResponse.onreadystatechange = function() {
                if (nativeRequestResponse.readyState == 4) {
                    onResponse(response, request);
                    autoClose(request);
                }
            };
            nativeRequestResponse.send(asURIEncodedString(withNewQuery(setupQuery)));
            return request;
        });
        method(postSynchronously, function(self, uri, setupQuery, setupRequest, onResponse) {
            var nativeRequestResponse = newNativeRequest();
            var request = RequestProxy(nativeRequestResponse);
            var response = ResponseProxy(nativeRequestResponse);
            nativeRequestResponse.open('POST', uri, false);
            setupRequest(request);
            nativeRequestResponse.send(asURIEncodedString(withNewQuery(setupQuery)));
            onResponse(response, request);
            autoClose(request);
        });
    });
};
var close = operator();
var abort = operator();
var setHeader = operator();
var onResponse = operator();
var RequestProxy = function(nativeRequestResponse) {
    return object(function(method) {
        method(setHeader, function(self, name, value) {
            nativeRequestResponse.setRequestHeader(name, value);
        });
        method(close, function(self) {
            nativeRequestResponse.onreadystatechange = noop;
        });
        method(abort, function(self) {
            nativeRequestResponse.onreadystatechange = noop;
            nativeRequestResponse.abort();
            method(abort, noop);
        });
    });
};
var statusCode = operator();
var statusText = operator();
var getHeader = operator();
var getAllHeaders = operator();
var hasHeader = operator();
var contentAsText = operator();
var contentAsDOM = operator();
var ResponseProxy = function(nativeRequestResponse) {
    return object(function(method) {
        method(statusCode, function() {
            try {
                return nativeRequestResponse.status;
            } catch (e) {
                return 0;
            }
        });
        method(statusText, function(self) {
            try {
                return nativeRequestResponse.statusText;
            } catch (e) {
                return '';
            }
        });
        method(hasHeader, function(self, name) {
            try {
                var header = nativeRequestResponse.getResponseHeader(name);
                return header && header != '';
            } catch (e) {
                return false;
            }
        });
        method(getHeader, function(self, name) {
            try {
                return nativeRequestResponse.getResponseHeader(name);
            } catch (e) {
                return null;
            }
        });
        method(getAllHeaders, function(self, name) {
            try {
                return collect(reject(split(nativeRequestResponse.getAllResponseHeaders(), '\n'), isEmpty), function(pair) {
                    var nameValue = split(pair, ': ')
                    return Cell(nameValue[0], nameValue[1]);
                });
            } catch (e) {
                return [];
            }
        });
        method(contentAsText, function(self) {
            try {
                return nativeRequestResponse.responseText;
            } catch (e) {
                return '';
            }
        });
        method(contentAsDOM, function(self) {
            try {
                return nativeRequestResponse.responseXML;
            } catch (e) {
                var txt = '<error>' + e + '</error>';
                var doc;
                if (window.DOMParser) {
                    var parser = new DOMParser();
                    doc = parser.parseFromString(txt,"text/xml");
                } else {
                    doc = new ActiveXObject("Microsoft.XMLDOM");
                    doc.async = false;
                    doc.loadXML(txt);
                }
                return doc;
            }
        });
        method(asString, function(self) {
            return inject(getAllHeaders(self), 'HTTP Response\n', function(result, header) {
                return result + key(header) + ': ' + value(header) + '\n';
            }) + contentAsText(self);
        });
    });
};
function OK(response) {
    return statusCode(response) == 200;
}
function NotFound(response) {
    return statusCode(response) == 404;
}
function ServerInternalError(response) {
    var code = statusCode(response);
    return code >= 500 && code < 600;
}
function FormPost(request) {
    setHeader(request, 'Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
}
var attributeAsString = operator();
var attributeAsBoolean = operator();
var attributeAsNumber = operator();
var valueAsStrings = operator();
var valueAsBooleans = operator();
var valueAsNumbers = operator();
var attributeNames = operator();
var childConfiguration = operator();
function XMLDynamicConfiguration(lookupElement) {
    function asBoolean(s) {
        return 'true' == toLowerCase(s);
    }
    function lookupAttribute(name) {
        var a = lookupElement().getAttribute(name);
        if (a) {
            return a;
        } else {
            throw 'unknown attribute: ' + name;
        }
    }
    function lookupValues(name) {
        return collect(asArray(lookupElement().getElementsByTagName(name)), function(e) {
            var valueNode = e.firstChild;
            return valueNode ? valueNode.nodeValue : '';
        });
    }
    return object(function(method) {
        method(attributeAsString, function(self, name, defaultValue) {
            try {
                return lookupAttribute(name);
            } catch (e) {
                if (isString(defaultValue)) {
                    return defaultValue;
                } else {
                    throw e;
                }
            }
        });
        method(attributeAsNumber, function(self, name, defaultValue) {
            try {
                return Number(lookupAttribute(name));
            } catch (e) {
                if (isNumber(defaultValue)) {
                    return defaultValue;
                } else {
                    throw e;
                }
            }
        });
        method(attributeAsBoolean, function(self, name, defaultValue) {
            try {
                return asBoolean(lookupAttribute(name));
            } catch (e) {
                if (isBoolean(defaultValue)) {
                    return defaultValue;
                } else {
                    throw e;
                }
            }
        });
        method(childConfiguration, function(self, name) {
            var elements = lookupElement().getElementsByTagName(name);
            if (isEmpty(elements)) {
                throw 'unknown configuration: ' + name;
            } else {
                return XMLDynamicConfiguration(function() {
                    return lookupElement().getElementsByTagName(name)[0];
                });
            }
        });
        method(valueAsStrings, function(self, name, defaultValues) {
            var values = lookupValues(name);
            return isEmpty(values) && defaultValues ? defaultValues : values;
        });
        method(valueAsNumbers, function(self, name, defaultValues) {
            var values = lookupValues(name);
            return isEmpty(values) && defaultValues ? defaultValues : collect(values, Number);
        });
        method(valueAsBooleans, function(self, name, defaultValues) {
            var values = lookupValues(name);
            return isEmpty(values) && defaultValues ? defaultValues : collect(values, asBoolean);
        });
    });
}
var register = operator();
var deserializeAndExecute = operator();
function CommandDispatcher() {
    var commands = [];
    return object(function(method) {
        method(register, function(self, messageName, command) {
            commands = reject(commands, function(cell) {
                return key(cell) == messageName;
            });
            append(commands, Cell(messageName, command));
        });
        method(deserializeAndExecute, function(self, message) {
            var messageName = message.nodeName;
            var found = detect(commands, function(cell) {
                return key(cell) == messageName;
            }, function() {
                throw 'Unknown message received: ' + messageName;
            });
            value(found)(message);
        });
    });
}
function NoopCommand() {
    debug(namespace.logger, 'received noop');
}
function ParsingError(message) {
    logger.error('Parsing error');
    var errorNode = message.firstChild;
    logger.error(errorNode.data);
    var sourceNode = errorNode.firstChild;
    logger.error(sourceNode.data);
}
function Macro(dispatcher) {
    return function(message) {
        each(message.childNodes, curry(deserializeAndExecute, dispatcher));
    };
}
        var ffMatch = navigator.userAgent.match(/Firefox\/(\w\.?\w)/);
        var firefoxGreaterThan3point6 = ffMatch ? (Number(ffMatch[1]) > 3.6) : true;
        var ie = window.attachEvent || /Trident.*rv\:11\./.test(navigator.userAgent) || /MSIE/.test(navigator.userAgent);
        function useLocalStorage() {
            var workingLocalStorage = false;
            if (window.localStorage) {
                var key = 'testLocalStorage';
                var value = String(Math.random());
                try {
                    window.localStorage[key] = value;
                    workingLocalStorage = window.localStorage[key] == value;
                } catch (ex) {
                    return false;
                } finally {
                    window.localStorage.removeItem(key);
                }
            }
            return workingLocalStorage && firefoxGreaterThan3point6 && !ie;
        }
var resumePushIDExpiry = operator();
var stopPushIDExpiry = operator();
var PushIDExpiryMonitor;
(function () {
    if (useLocalStorage()) {
        PushIDExpiryMonitor = function(parentLogger) {
            var logger = childLogger(parentLogger, 'pushid-expiry');
            var notificationResponsivness = {};
            var testChannel = "ice.push.liveliness";
            var testLivelinessBroadcaster = LocalStorageNotificationBroadcaster(testChannel, function (verifiedIds) {
                var ids = registeredWindowPushIds();
                var confirmedIds = intersect(verifiedIds, ids);
                if (notEmpty(confirmedIds)) {
                    notifyWindows(confirmLivelinessBroadcaster, ids);
                }
            });
            var confirmationChannel = "ice.push.confirm";
            var confirmLivelinessBroadcaster = LocalStorageNotificationBroadcaster(confirmationChannel, function (confirmedIDs) {
                each(confirmedIDs, function (id) {
                    delete notificationResponsivness[id];
                });
            });
            function requestConfirmLiveliness() {
                var ids = registeredPushIds();
                var discardUnresponsiveIds = [];
                for (var id in notificationResponsivness) {
                    if (notificationResponsivness.hasOwnProperty(id)) {
                        if (not(contains(ids, id))) {
                            append(discardUnresponsiveIds, id);
                        }
                    }
                }
                each(discardUnresponsiveIds, function (id) {
                    delete notificationResponsivness[id];
                });
                each(ids, function (id) {
                    var count = notificationResponsivness[id];
                    if (count) {
                        notificationResponsivness[id] = count + 1;
                    } else {
                        notificationResponsivness[id] = 1;
                    }
                });
                if (notEmpty(ids)) {
                    notifyWindows(testLivelinessBroadcaster, ids);
                }
                return notificationResponsivness;
            }
            function removeUnusedPushIDs() {
                var unresponsivePushIds = requestConfirmLiveliness();
                var ids = [];
                for (var p in unresponsivePushIds) {
                    if (unresponsivePushIds.hasOwnProperty(p) && unresponsivePushIds[p] > 5) {
                        append(ids, p);
                    }
                }
                if (notEmpty(ids)) {
                    info(logger, 'expirying unused pushIDs: ' + ids);
                    delistPushIDsWithBrowser(ids);
                }
            }
            var pid = object(function (method) {
                method(stop, noop);
            });
            return object(function (method) {
                method(resumePushIDExpiry, function (self) {
                    info(logger, 'resume monitoring for unused pushIDs');
                    pid = Delay(removeUnusedPushIDs, 10000);
                    run(pid);
                });
                method(stopPushIDExpiry, function (self) {
                    info(logger, 'stopped monitoring for unused pushIDs');
                    stop(pid);
                });
            });
        };
    } else {
        PushIDExpiryMonitor = function () {
            return object(function (method) {
                method(resumePushIDExpiry, noop);
                method(stopPushIDExpiry, noop);
            });
        };
    }
})();
var setValue = operator();
var getValue = operator();
var existsSlot;
var removeSlot;
var Slot;
(function () {
    if (useLocalStorage()) {
        Slot = function LocalStorageSlot(name, val) {
            window.localStorage.setItem(name, window.localStorage.getItem(name) || '');
            return object(function(method) {
                method(getValue, function(self) {
                    var val = window.localStorage.getItem(name);
                    return val ? val : '';
                });
                method(setValue, function(self, val) {
                    window.localStorage.setItem(name, val || '');
                });
            });
        };
        existsSlot = function(name) {
            return window.localStorage.getItem(name) != null;
        };
        removeSlot = function(name) {
            window.localStorage.removeItem(name);
        };
    } else {
        Slot = function CookieSlot(name, val) {
            var c = existsCookie(name) ? lookupCookie(name) : Cookie(name, val);
            return object(function(method) {
                method(getValue, function(self) {
                    try {
                        return value(c);
                    } catch (e) {
                        c = Cookie(name, '');
                        return '';
                    }
                });
                method(setValue, function(self, val) {
                    try {
                        update(c, val);
                    } catch (e) {
                        c = Cookie(name, val);
                    }
                });
            });
        };
        existsSlot = existsCookie;
        removeSlot = function(name) {
            if (existsCookie(name)) {
                remove(lookupCookie(name));
            }
        };
    }
}());
var send = operator();
var onSend = operator();
var onReceive = operator();
var onServerError = operator();
var whenDown = operator();
var whenTrouble = operator();
var whenStopped = operator();
var whenReEstablished = operator();
var startConnection = operator();
var resumeConnection = operator();
var pauseConnection = operator();
var controlRequest = operator();
var reconfigure = operator();
var changeHeartbeatInterval = operator();
var shutdown = operator();
var AsyncConnection;
(function() {
    var PushIDs = 'ice.pushids';
    var SequenceNumber = 'ice.push.sequence';
    var ConnectionRunning = 'ice.connection.running';
    var ConnectionLease = 'ice.connection.lease';
    var ConnectionContextPath = 'ice.connection.contextpath';
    var AcquiredMarker = ':acquired';
    var NetworkDelay = 5000;
    function timedRetryAbort(retryAction, abortAction, timeouts) {
        var index = 0;
        var errorActions = inject(timeouts, [abortAction], function(actions, interval) {
            return insert(actions, curry(runOnce, Delay(retryAction, interval)));
        });
        return function() {
            if (index < errorActions.length) {
                apply(errorActions[index], arguments);
                index++;
            }
        };
    }
    AsyncConnection = function(logger, windowID, configuration) {
        var logger = childLogger(logger, 'async-connection');
        var channel = Client(false);
        var onSendListeners = [];
        var onReceiveListeners = [];
        var onServerErrorListeners = [];
        var connectionDownListeners = [];
        var connectionTroubleListeners = [];
        var connectionStoppedListeners = [];
        var connectionReEstablished = [];
        var sequenceNo = Slot(SequenceNumber);
        var listener = object(function(method) {
            method(close, noop);
            method(abort, noop);
        });
        onBeforeUnload(window, function() {
            connectionDownListeners = [];
        });
        var sendXWindowHeader = noop;
        var receiveXWindowHeader = function (response) {
            var xWindow = getHeader(response, "X-Set-Window-Cookie");
            if (xWindow) {
                sendXWindowHeader = function(request) {
                    setHeader(request, "X-Window-Cookie", xWindow);
                };
            }
        };
        var lastSentPushIds = registeredPushIds();
        function contextPath() {
            return namespace.push.configuration.contextPath;
        }
        function askForConfiguration(query) {
            try {
                lookupCookieValue(BrowserIDName);
                addNameValue(query, 'ice.sendConfiguration', '');
                askForConfiguration = noop;
            } catch (ex) {
            }
        }
        function requestForBlockingResponse() {
            try {
                debug(logger, "closing previous connection...");
                close(listener);
                setValue(contextPathSlot, contextPath());
                lastSentPushIds = registeredPushIds();
                if (isEmpty(lastSentPushIds)) {
                    stopTimeoutBombs();
                    broadcast(connectionStoppedListeners, ['connection stopped, no pushIDs registered']);
                } else {
                    debug(logger, 'connect...');
                    listener = postAsynchronously(channel, namespace.push.configuration.blockingConnectionURI, function(q) {
                        each(lastSentPushIds, curry(addNameValue, q, 'ice.pushid'));
                        addNameValue(q, BrowserIDName, lookupCookieValue(BrowserIDName));
                        askForConfiguration(q);
                    }, function(request) {
                        FormPost(request);
                        sendXWindowHeader(request);
                        setHeader(request, 'ice.push.window', namespace.windowID);
                        setHeader(request, SequenceNumber, getValue(sequenceNo));
                        broadcast(onSendListeners, [request]);
                    }, $witch(function (condition) {
                        condition(OK, function(response) {
                            var sequenceNoValue = Number(getHeader(response, SequenceNumber));
                            if (sequenceNoValue) {
                                setValue(sequenceNo, sequenceNoValue);
                            }
                            var reconnect = getHeader(response, 'X-Connection') != 'close';
                            var nonEmptyResponse = notEmpty(contentAsText(response));
                            if (reconnect) {
                                if (nonEmptyResponse) {
                                    broadcast(onReceiveListeners, [response]);
                                    resetEmptyResponseRetries();
                                } else {
                                    warn(logger, 'empty response received');
                                    decrementEmptyResponseRetries();
                                }
                                if (anyEmptyResponseRetriesLeft()) {
                                    resetTimeoutBomb();
                                    connect();
                                } else {
                                    info(logger, 'blocking connection stopped, too many empty responses received...');
                                }
                            } else {
                                info(logger, 'blocking connection stopped at server\'s request...');
                                var reason = getHeader(response, 'X-Connection-reason');
                                if (reason) {
                                    info(logger, reason);
                                }
                                stopTimeoutBombs();
                                broadcast(connectionStoppedListeners, ['connection stopped by server']);
                            }
                            receiveXWindowHeader(response);
                        });
                        condition(ServerInternalError, retryOnServerError);
                    }));
                }
            } catch (e) {
                error(logger, 'failed to re-initiate blocking connection', e);
            }
        }
        var connect = requestForBlockingResponse;
        var heartbeatTimeout;
        var networkErrorRetryTimeouts;
        function setupNetworkErrorRetries() {
            heartbeatTimeout = attributeAsNumber(configuration, 'heartbeatTimeout', 15000);
            networkErrorRetryTimeouts = collect(split(attributeAsString(configuration, 'networkErrorRetryTimeouts', '1 1 1 2 2 3'), ' '), Number);
        }
        setupNetworkErrorRetries();
        var serverErrorRetryTimeouts;
        var retryOnServerError;
        function setupServerErrorRetries() {
            serverErrorRetryTimeouts = collect(split(attributeAsString(configuration, 'serverErrorRetryTimeouts', '1000 2000 4000'), ' '), Number);
            retryOnServerError = timedRetryAbort(connect, broadcaster(onServerErrorListeners), serverErrorRetryTimeouts);
        }
        setupServerErrorRetries();
        var emptyResponseRetries;
        function resetEmptyResponseRetries() {
            emptyResponseRetries = attributeAsNumber(configuration, 'emptyResponseRetries', 3);
        }
        function decrementEmptyResponseRetries() {
            --emptyResponseRetries;
        }
        function anyEmptyResponseRetriesLeft() {
            return emptyResponseRetries > 0;
        }
        resetEmptyResponseRetries();
        var initialRetryIndex = function () {
            return 0;
        };
        var pendingRetryIndex = initialRetryIndex;
        var stopTimeoutBombs = noop;
        function chainTimeoutBombs(timeoutAction, abortAction, intervals, remainingBombsIndex) {
            var index = remainingBombsIndex();
            stopTimeoutBombs();
            function sparkTimeoutBomb() {
                var run = true;
                var timeoutBomb = runOnce(Delay(function() {
                    if (run) {
                        var retryCount = intervals.length;
                        if (index < retryCount) {
                            timeoutAction(++index, retryCount);
                            stopTimeoutBombs = sparkTimeoutBomb();
                        } else {
                            abortAction();
                        }
                    }
                }, intervals[index]));
                return function() {
                    run = false;
                    stop(timeoutBomb);
                }
            }
            stopTimeoutBombs = sparkTimeoutBomb();
            return function() {
                return index;
            }
        }
        function recalculateRetryIntervals() {
            return asArray(collect(networkErrorRetryTimeouts, function (factor) {
                return factor * heartbeatTimeout + NetworkDelay;
            }));
        }
        function networkErrorRetry(i, retries) {
            warn(logger, 'failed to connect ' + i + ' time' + (i > 1 ? 's' : '') + (i < retries ? ', retrying ...' : ''));
            broadcast(connectionTroubleListeners);
            connect();
        }
        function networkFailure() {
            broadcast(connectionDownListeners);
        }
        function resetTimeoutBomb() {
            pendingRetryIndex = chainTimeoutBombs(networkErrorRetry, networkFailure, recalculateRetryIntervals(), initialRetryIndex);
        }
        function adjustTimeoutBombIntervals() {
            pendingRetryIndex = chainTimeoutBombs(networkErrorRetry, networkFailure, recalculateRetryIntervals(), pendingRetryIndex);
        }
        function initializeConnection() {
            info(logger, 'initialize connection within window ' + namespace.windowID);
            resetTimeoutBomb();
            setValue(sequenceNo, Number(getValue(sequenceNo)) + 1);
            connect();
        }
        var pollingPeriod = 1000;
        var leaseSlot = Slot(ConnectionLease, asString((new Date).getTime()));
        var connectionSlot = Slot(ConnectionRunning);
        var contextPathSlot;
        function updateLease() {
            setValue(leaseSlot, (new Date).getTime() + pollingPeriod * 3);
        }
        function isLeaseExpired() {
            return asNumber(getValue(leaseSlot)) < (new Date).getTime();
        }
        function shouldEstablishBlockingConnection() {
            return !existsSlot(ConnectionRunning) || isEmpty(getValue(connectionSlot));
        }
        function offerCandidature() {
            setValue(connectionSlot, windowID);
        }
        function isWinningCandidate() {
            return startsWith(getValue(connectionSlot), windowID);
        }
        function markAsOwned() {
            setValue(connectionSlot, windowID + AcquiredMarker);
        }
        function isOwner() {
            return getValue(connectionSlot) == (windowID + AcquiredMarker);
        }
        function hasOwner() {
            return endsWith(getValue(connectionSlot), AcquiredMarker);
        }
        function owner() {
            var owner = getValue(connectionSlot);
            var i = indexOf(owner, AcquiredMarker);
            return i > -1 ? substring(owner, 0, i) : owner;
        }
        function nonMatchingContextPath() {
            return getValue(contextPathSlot) != contextPath();
        }
        var lastOwningWindow = '';
        var paused = false;
        var blockingConnectionMonitor = object(function(method) {
            method(stop, noop);
        });
        function createBlockingConnectionMonitor() {
            contextPathSlot = Slot(ConnectionContextPath, contextPath())
            if (nonMatchingContextPath()) {
                offerCandidature();
                info(logger, 'Blocking connection cannot be shared among multiple web-contexts.\nInitiating blocking connection for "' + contextPath() + '"  web-context...');
            }
            blockingConnectionMonitor = run(Delay(function() {
                if (shouldEstablishBlockingConnection()) {
                    offerCandidature();
                    info(logger, 'blocking connection not initialized...candidate for its creation');
                } else {
                    if (isWinningCandidate()) {
                        if (!hasOwner()) {
                            markAsOwned();
                            if (notEmpty(registeredPushIds())) {
                                initializeConnection();
                            }
                        }
                        updateLease();
                    }
                    if (isLeaseExpired()) {
                        setTimeout(offerCandidature, 1.5 * Math.random() * pollingPeriod);
                        info(logger, 'blocking connection lease expired...candidate for its creation');
                    }
                }
                if (isOwner()) {
                    var ids = registeredPushIds();
                    if ((size(ids) != size(lastSentPushIds)) || notEmpty(complement(ids, lastSentPushIds))) {
                        abort(listener);
                        connect();
                    }
                } else {
                    stopTimeoutBombs();
                    abort(listener);
                }
                var currentlyOwningWindow = getValue(connectionSlot);
                if (hasOwner()) {
                    if (lastOwningWindow != currentlyOwningWindow) {
                        lastOwningWindow = currentlyOwningWindow;
                        broadcast(connectionReEstablished, [ owner() ]);
                    }
                } else {
                    lastOwningWindow = '';
                }
            }, pollingPeriod));
        }
        info(logger, 'connection monitoring started within window ' + namespace.windowID);
        return object(function(method) {
            method(onSend, function(self, callback) {
                append(onSendListeners, callback);
            });
            method(onReceive, function(self, callback) {
                append(onReceiveListeners, callback);
            });
            method(onServerError, function(self, callback) {
                append(onServerErrorListeners, callback);
            });
            method(whenDown, function(self, callback) {
                append(connectionDownListeners, callback);
            });
            method(whenTrouble, function(self, callback) {
                append(connectionTroubleListeners, callback);
            });
            method(whenStopped, function(self, callback) {
                append(connectionStoppedListeners, callback);
            });
            method(whenReEstablished, function(self, callback) {
                append(connectionReEstablished, callback);
            });
            method(startConnection, function(self) {
                createBlockingConnectionMonitor();
                paused = false;
            });
            method(resumeConnection, function(self) {
                if (paused) {
                    connect = requestForBlockingResponse;
                    initializeConnection();
                    createBlockingConnectionMonitor();
                    paused = false;
                }
            });
            method(pauseConnection, function(self) {
                if (not(paused)) {
                    abort(listener);
                    stop(blockingConnectionMonitor);
                    stopTimeoutBombs();
                    connect = noop;
                    paused = true;
                    broadcast(connectionStoppedListeners, ['connection stopped']);
                }
            });
            method(controlRequest, function(self, parameterCallback, headerCallback, responseCallback) {
                if (paused) {
                    postAsynchronously(channel, namespace.push.configuration.blockingConnectionURI, function(q) {
                        each(lastSentPushIds, curry(addNameValue, q, 'ice.pushid'));
                        parameterCallback(curry(addNameValue, q));
                    }, function(request) {
                        FormPost(request);
                        setHeader(request, 'ice.push.window', namespace.windowID);
                        headerCallback(curry(setHeader, request));
                    }, $witch(function (condition) {
                        condition(OK, function(response) {
                            responseCallback(curry(getHeader, response), contentAsText(response), contentAsDOM(response));
                        });
                        condition(ServerInternalError, function() {
                            throw statusText(response);
                        });
                    }));
                } else {
                    throw 'Cannot make a request while the blocking connection is running.';
                }
            });
            method(changeHeartbeatInterval, function(self, interval) {
                setupNetworkErrorRetries();
                heartbeatTimeout = interval;
                info(logger, 'heartbeat timeout changed to ' + interval + ' ms');
                adjustTimeoutBombIntervals();
            });
            method(reconfigure, function(self) {
                setupNetworkErrorRetries();
                adjustTimeoutBombIntervals();
                setupServerErrorRetries();
            });
            method(shutdown, function(self) {
                try {
                    method(shutdown, noop);
                    connect = noop;
                    resetTimeoutBomb = noop;
                } catch (e) {
                    error(logger, 'error during shutdown', e);
                } finally {
                    broadcast(connectionStoppedListeners, ['connection stopped']);
                    onReceiveListeners = connectionDownListeners = onServerErrorListeners = connectionStoppedListeners = [];
                    abort(listener);
                    stopTimeoutBombs();
                    stop(blockingConnectionMonitor);
                    if (isOwner()) {
                        removeSlot(ConnectionRunning);
                    }
                }
            });
        });
    };
})();
var notifyWindows = operator();
var disposeBroadcast = operator();
function LocalStorageNotificationBroadcaster(name, callback) {
    var Separator = ':::';
    if (!window.localStorage.getItem(name)) {
        window.localStorage.setItem(name, '');
    }
    function storageListener(e) {
        var newValue = e.newValue;
        if (e.key == name && newValue) {
            var ids = split(newValue, Separator)[0];
            callback(split(ids, ' '));
        }
    }
    if (window.addEventListener) {
        window.addEventListener('storage', storageListener, false);
    } else {
        document.attachEvent('onstorage', storageListener);
    }
    return object(function(method) {
        method(notifyWindows, function(self, newValue) {
            window.localStorage.setItem(name, join(newValue, ' ') + Separator + Math.random());
            var agent = navigator.userAgent;
            if (!/MSIE/.test(agent) && !/Trident/.test(agent)) {
                callback(newValue);
            }
        });
        method(disposeBroadcast, noop);
    });
}
function CookieBasedNotificationBroadcaster(name, callback) {
    var notifiedPushIDs = lookupCookie(name, function() {
        return Cookie(name, '');
    });
    var notificationMonitor = run(Delay(function() {
        try {
            var pushIDsValue = value(notifiedPushIDs);
            if (pushIDsValue) {
                var ids = split(pushIDsValue, ' ');
                if (notEmpty(ids)) {
                    var notifiedIDs = callback(ids);
                    update(notifiedPushIDs, join(complement(ids, notifiedIDs), ' '));
                }
            }
        } catch (e) {
            warn(namespace.logger, 'failed to listen for updates', e);
        }
    }, 300));
    return object(function(method) {
        method(notifyWindows, function(self, receivedPushIDs) {
            var ids = split(value(notifiedPushIDs), ' ');
            update(notifiedPushIDs, join(asSet(concatenate(ids, receivedPushIDs)), ' '));
        });
        method(disposeBroadcast, function(self) {
            stop(notificationMonitor);
        });
    });
}
        var notificationListeners = [];
        namespace.onNotification = function(callback) {
            append(notificationListeners, callback);
        };
        var receiveListeners = [];
        namespace.onBlockingConnectionReceive = function(callback) {
            append(receiveListeners, callback);
        };
        var serverErrorListeners = [];
        namespace.onBlockingConnectionServerError = function(callback) {
            append(serverErrorListeners, callback);
        };
        var blockingConnectionUnstableListeners = [];
        namespace.onBlockingConnectionUnstable = function(callback) {
            append(blockingConnectionUnstableListeners, callback);
        };
        var blockingConnectionLostListeners = [];
        namespace.onBlockingConnectionLost = function(callback) {
            append(blockingConnectionLostListeners, callback);
        };
        var blockingConnectionReEstablishedListeners = [];
        namespace.onBlockingConnectionReEstablished = function(callback) {
            append(blockingConnectionReEstablishedListeners, callback);
        };
        var PushIDs = 'ice.pushids';
        var BrowserIDName = 'ice.push.browser';
        var NotifiedPushIDs = 'ice.notified.pushids';
        var handler = LocalStorageLogHandler(window.console ? ConsoleLogHandler(debug) : WindowLogHandler(debug, window.location.href));
        namespace.windowID = namespace.windowID || substring(Math.random().toString(16), 2, 7);
        namespace.logger = Logger([ 'icepush' ], handler);
        namespace.info = info;
        var pushIdentifiers = [];
        function registeredWindowPushIds() {
            return pushIdentifiers;
        }
        var pushIDsSlot = Slot(PushIDs);
        function registeredPushIds() {
            try {
                return split(getValue(pushIDsSlot), ' ');
            } catch (e) {
                return [];
            }
        }
        function enlistPushIDsWithBrowser(ids) {
            var registeredIDs = split(getValue(pushIDsSlot), ' ');
            try {
                lookupCookieValue(BrowserIDName)
            } catch (ex) {
                try {
                    var id = ids[0].split(':')[0];
                    Cookie(BrowserIDName, id);
                } catch (ex) {
                    error(namespace.logger, 'Failed to extract browser ID from push ID.');
                }
            }
            setValue(pushIDsSlot, join(asSet(concatenate(registeredIDs, ids)), ' '));
        }
        function delistPushIDsWithBrowser(ids) {
            if (existsSlot(PushIDs)) {
                var registeredIDs = split(getValue(pushIDsSlot), ' ');
                setValue(pushIDsSlot, join(complement(registeredIDs, ids), ' '));
            }
        }
        function enlistPushIDsWithWindow(ids) {
            enlistPushIDsWithBrowser(ids);
            pushIdentifiers = concatenate(pushIdentifiers, ids);
        }
        function delistPushIDsWithWindow(ids) {
            delistPushIDsWithBrowser(ids);
            pushIdentifiers = complement(pushIdentifiers, ids);
        }
        onBeforeUnload(window, function() {
            delistPushIDsWithBrowser(pushIdentifiers);
            pushIdentifiers = [];
        });
        function throwServerError(response) {
            throw 'Server internal error: ' + contentAsText(response);
        }
        var currentNotifications = [];
        var apiChannel = Client(true);
        namespace.uriextension = '';
        namespace.push = {
            register: function(pushIds, callback) {
                if ((typeof callback) == 'function') {
                    enlistPushIDsWithWindow(pushIds);
                    namespace.onNotification(function(ids) {
                        currentNotifications = asArray(intersect(ids, pushIds));
                        if (notEmpty(currentNotifications)) {
                            try {
                                callback(currentNotifications);
                            } catch (e) {
                                error(namespace.logger, 'error thrown by push notification callback', e);
                            }
                        }
                    });
                } else {
                    throw 'the callback is not a function';
                }
            },
            deregister: delistPushIDsWithWindow,
            getCurrentNotifications: function() {
                return currentNotifications;
            },
            createPushId: function() {
                var id;
                postSynchronously(apiChannel, namespace.push.configuration.createPushIdURI || 'create-push-id.icepush', function (q) {
                    try {
                        addNameValue(q, BrowserIDName, lookupCookieValue(BrowserIDName));
                    } catch (ex) {
                    }
                }, FormPost, $witch(function (condition) {
                    condition(OK, function(response) {
                        id = contentAsText(response);
                    });
                    condition(ServerInternalError, throwServerError);
                }));
                return id;
            },
            notify: function(group) {
                postAsynchronously(apiChannel, namespace.push.configuration.notifyURI || 'notify.icepush', function(q) {
                    addNameValue(q, BrowserIDName, lookupCookieValue(BrowserIDName));
                    addNameValue(q, 'group', group);
                }, FormPost, $witch(function(condition) {
                    condition(ServerInternalError, throwServerError);
                }));
            },
            addGroupMember: function(group, id) {
                postAsynchronously(apiChannel, namespace.push.configuration.addGroupMemberURI || 'add-group-member.icepush', function(q) {
                    addNameValue(q, BrowserIDName, lookupCookieValue(BrowserIDName));
                    addNameValue(q, 'group', group);
                    addNameValue(q, 'id', id);
                }, FormPost, $witch(function(condition) {
                    condition(ServerInternalError, throwServerError);
                }));
            },
            removeGroupMember: function(group, id) {
                postAsynchronously(apiChannel, namespace.push.configuration.removeGroupMemberURI || 'remove-group-member.icepush', function(q) {
                    addNameValue(q, BrowserIDName, lookupCookieValue(BrowserIDName));
                    addNameValue(q, 'group', group);
                    addNameValue(q, 'id', id);
                }, FormPost, $witch(function(condition) {
                    condition(ServerInternalError, throwServerError);
                }));
            },
            get: function(uri, parameters, responseCallback) {
                getAsynchronously(apiChannel, uri, function(query) {
                    parameters(curry(addNameValue, query));
                    addNameValue(q, BrowserIDName, lookupCookieValue(BrowserIDName));
                }, noop, $witch(function(condition) {
                    condition(OK, function(response) {
                        responseCallback(statusCode(response), contentAsText(response), contentAsDOM(response));
                    });
                    condition(ServerInternalError, throwServerError);
                }));
            },
            post: function(uri, parameters, responseCallback) {
                postAsynchronously(apiChannel, uri, function(query) {
                    parameters(curry(addNameValue, query));
                    addNameValue(query, BrowserIDName, lookupCookieValue(BrowserIDName));
                }, FormPost, $witch(function(condition) {
                    condition(OK, function(response) {
                        responseCallback(statusCode(response), contentAsText(response), contentAsDOM(response));
                    });
                    condition(ServerInternalError, throwServerError);
                }));
            },
            searchAndEvaluateScripts: function(element) {
                each(element.getElementsByTagName('script'), function(script) {
                    var newScript = document.createElement('script');
                    newScript.setAttribute('type', 'text/javascript');
                    if (script.src) {
                        newScript.src = script.src;
                    } else {
                        newScript.text = script.text;
                    }
                    element.appendChild(newScript);
                });
            },
            configuration: {
                contextPath: '.',
                blockingConnectionURI: 'listen.icepush'
            }
        };
        function Bridge() {
            var windowID = namespace.windowID;
            var logger = childLogger(namespace.logger, windowID);
            var publicConfiguration = namespace.push.configuration;
            var configurationElement = document.documentElement;
            var configuration = XMLDynamicConfiguration(function() {
                return configurationElement;
            });
            var commandDispatcher = CommandDispatcher();
            var heartbeatTimestamp;
            var pushIdExpiryMonitor = PushIDExpiryMonitor(logger);
            var asyncConnection = AsyncConnection(logger, windowID, configuration);
            register(commandDispatcher, 'noop', NoopCommand);
            register(commandDispatcher, 'parsererror', ParsingError);
            register(commandDispatcher, 'macro', Macro(commandDispatcher));
            register(commandDispatcher, 'configuration', function(message) {
                configurationElement = message;
                publicConfiguration.contextPath = attributeAsString(configuration, 'contextPath', publicConfiguration.contextPath);
                publicConfiguration.blockingConnectionURI = attributeAsString(configuration, 'blockingConnectionURI', publicConfiguration.blockingConnectionURI || 'listen.icepush');
                reconfigure(asyncConnection);
            });
            register(commandDispatcher, 'browser', function(message) {
                Cookie(BrowserIDName, message.getAttribute('id'));
            });
            register(commandDispatcher, 'back-off', function(message) {
                debug(logger, 'received back-off');
                var delay = asNumber(message.getAttribute('delay'));
                try {
                    pauseConnection(asyncConnection);
                } finally {
                    runOnce(Delay(function() {
                        resumeConnection(asyncConnection);
                    }, delay));
                }
            });
            function purgeUnusedPushIDs(ids) {
                var registeredIDs = split(getValue(pushIDsSlot), ' ');
                return intersect(ids, registeredIDs);
            }
            function selectWindowNotifications(ids) {
                try {
                    var windowPushIDs = asArray(intersect(ids, pushIdentifiers));
                    if (notEmpty(windowPushIDs)) {
                        broadcast(notificationListeners, [ windowPushIDs ]);
                        debug(logger, 'picked up notifications for this window: ' + windowPushIDs);
                        return windowPushIDs;
                    } else {
                        return [];
                    }
                } catch (e) {
                    warn(logger, 'failed to listen for updates', e);
                    return [];
                }
            }
            var notificationBroadcaster = useLocalStorage() ?
                LocalStorageNotificationBroadcaster(NotifiedPushIDs, selectWindowNotifications) : CookieBasedNotificationBroadcaster(NotifiedPushIDs, selectWindowNotifications);
            register(commandDispatcher, 'notified-pushids', function(message) {
                var text = message.firstChild;
                if (text && !blank(text.data)) {
                    var receivedPushIDs = split(text.data, ' ');
                    debug(logger, 'received notifications: ' + receivedPushIDs);
                    notifyWindows(notificationBroadcaster, purgeUnusedPushIDs(asSet(receivedPushIDs)));
                } else {
                    warn(logger, "No notification was received.");
                }
            });
            function dispose() {
                try {
                    info(logger, 'shutting down bridge...');
                    dispose = noop;
                    disposeBroadcast(notificationBroadcaster);
                } finally {
                    shutdown(asyncConnection);
                }
            }
            onBeforeUnload(window, function() {
                pauseConnection(asyncConnection);
            });
            onUnload(window, dispose);
            onSend(asyncConnection, function(request) {
                if (heartbeatTimestamp) {
                    setHeader(request, 'ice.push.heartbeatTimestamp', heartbeatTimestamp);
                }
            });
            onReceive(asyncConnection, function(response) {
                var mimeType = getHeader(response, 'Content-Type');
                if (mimeType && startsWith(mimeType, 'text/xml')) {
                    var message = contentAsDOM(response).documentElement;
                    deserializeAndExecute(commandDispatcher, message);
                    broadcast(receiveListeners, [ message ]);
                } else {
                    warn(logger, 'unknown content in response - ' + mimeType + ', expected text/xml');
                    dispose();
                }
                if (hasHeader(response, 'ice.push.heartbeatTimestamp')) {
                    heartbeatTimestamp = Number(getHeader(response, 'ice.push.heartbeatTimestamp'));
                }
            });
            onServerError(asyncConnection, function(response) {
                try {
                    warn(logger, 'server side error');
                    broadcast(serverErrorListeners, [ statusCode(response), contentAsText(response), contentAsDOM(response) ]);
                } finally {
                    dispose();
                }
            });
            whenStopped(asyncConnection, function(reason) {
                debug(logger, reason + ' in window [' + windowID + ']');
                stopPushIDExpiry(pushIdExpiryMonitor);
            });
            whenReEstablished(asyncConnection, function(windowID) {
                broadcast(blockingConnectionReEstablishedListeners);
                (windowID == namespace.windowID ? resumePushIDExpiry : stopPushIDExpiry)(pushIdExpiryMonitor);
            });
            whenDown(asyncConnection, function(reconnectAttempts) {
                try {
                    warn(logger, 'connection to server was lost');
                    broadcast(blockingConnectionLostListeners, [ reconnectAttempts ]);
                } finally {
                    dispose();
                }
            });
            whenTrouble(asyncConnection, function() {
                warn(logger, 'connection in trouble');
                broadcast(blockingConnectionUnstableListeners);
            });
            namespace.push.connection = {
                resumeConnection: function() {
                    resumeConnection(asyncConnection);
                },
                pauseConnection: function() {
                    pauseConnection(asyncConnection);
                },
                changeHeartbeatInterval: function(interval) {
                    changeHeartbeatInterval(asyncConnection, interval);
                },
                onSend: function(callback) {
                    onSend(asyncConnection, function(request) {
                        callback(function(name, value) {
                            setHeader(request, name, value);
                        });
                    });
                },
                onReceive: function(callback) {
                    onReceive(asyncConnection, function(response) {
                        callback(function(name) {
                            return getHeader(response, name);
                        }, contentAsText(response), contentAsDOM(response));
                    });
                },
                controlRequest: function(addParameter, addHeader, responseCallback) {
                    controlRequest(asyncConnection, addParameter, addHeader, responseCallback);
                }
            };
            var deviceURI;
            namespace.push.parkInactivePushIds = function(url) {
                deviceURI = url;
                namespace.push.connection.pauseConnection();
                namespace.push.connection.resumeConnection();
            };
            namespace.push.connection.onSend(function(header) {
                if (deviceURI) {
                    header('ice.parkids', 'true');
                    header('ice.notifyBack', deviceURI)
                }
            });
            info(logger, 'bridge loaded!');
            onLoad(window, curry(startConnection, asyncConnection));
        }
        Bridge();
        onKeyPress(document, function(ev) {
            var e = $event(ev);
            if (isEscKey(e)) cancelDefaultAction(e);
        });
    })(window.ice);
}
