(function (factory) {
    if (typeof module === "object" && typeof module.exports === "object") {
        var v = factory(require, exports);
        if (v !== undefined) module.exports = v;
    }
    else if (typeof define === "function" && define.amd) {
        define(["require", "exports", "diff"], factory);
    }
})(function (require, exports) {
    "use strict";
    Object.defineProperty(exports, "__esModule", { value: true });
    var diff_1 = require("diff");
    var ErrorFormatter = (function () {
        function ErrorFormatter(executor) {
            this.executor = executor;
        }
        /**
         * Generates a full error message from a plain Error object, avoiding duplicate error messages that might be
         * caused by different opinions on what a stack trace should look like.
         *
         * @param error An object describing the error.
         * @returns A string message describing the error.
         */
        ErrorFormatter.prototype.format = function (error, options) {
            options = options || {};
            var message;
            if (typeof error !== 'string' && (error.message || error.stack)) {
                message = (error.name || 'Error') + ': ' + (error.message || 'Unknown error');
                var stack = error.stack;
                if (stack) {
                    // V8 puts the original error at the top of the stack too; avoid redundant output that may
                    // cause confusion about how many times an assertion was actually called
                    if (stack.indexOf(message) === 0) {
                        stack = stack.slice(message.length);
                    }
                    else if (stack.indexOf(error.message) === 0) {
                        stack = stack.slice(String(error.message).length);
                    }
                    else if (stack.indexOf('Error\n') === 0) {
                        stack = stack.slice('Error'.length);
                    }
                    stack = this._normalizeStackTrace(stack);
                }
                var anyError = error;
                // Assertion errors may have showDiff, actual, and expected properties
                if (anyError.showDiff && typeof anyError.actual === 'object' && typeof anyError.expected === 'object') {
                    var diff = this._createDiff(anyError.actual, anyError.expected);
                    if (diff) {
                        message += '\n\n' + diff + '\n';
                    }
                }
                if (stack && /\S/.test(stack)) {
                    message += stack;
                }
                else if (anyError.fileName) {
                    message += '\n  at ' + anyError.fileName;
                    if (anyError.lineNumber != null) {
                        message += ':' + anyError.lineNumber;
                        if (anyError.columnNumber != null) {
                            message += ':' + anyError.columnNumber;
                        }
                    }
                    message += '\nNo stack';
                }
                else {
                    message += '\nNo stack or location';
                }
            }
            else {
                message = String(error);
            }
            var space = options.space;
            if (space != null) {
                var lines = message.split('\n');
                message = [lines[0]].concat(lines.slice(1).map(function (line) {
                    return space + line;
                })).join('\n');
            }
            return message;
        };
        ErrorFormatter.prototype._getSource = function (tracepath) {
            if (tracepath === '<anonymous>') {
                return 'anonymous';
            }
            return tracepath;
        };
        /**
         * Creates a unified diff to explain the difference between two objects.
         *
         * @param actual The actual result.
         * @param expected The expected result.
         * @returns A unified diff formatted string representing the difference between the two objects.
         */
        ErrorFormatter.prototype._createDiff = function (actual, expected) {
            // TODO: Remove the casts when the diffJson typings are updated (the current typings are missing the options
            // argument).
            var diff = diff_1.diffJson(actual, expected, { undefinedReplacement: null });
            if (diff.length === 1 && !diff[0].added && !diff[0].removed) {
                return '';
            }
            return diff.reduce(function (d, _a) {
                var value = _a.value, added = _a.added, removed = _a.removed;
                var lastChar = value[value.length - 1] === '\n' ? '\n' : '';
                var lines = value.split('\n');
                if (lastChar === '\n') {
                    lines.pop();
                }
                var prefix = '';
                if (d.length > 0 && d[d.length - 1] !== '\n') {
                    prefix = '\n';
                }
                var char = added ? 'E' : removed ? 'A' : ' ';
                return d + ("" + prefix + char + " ") + lines.join("\n" + char + " ") + lastChar;
            }, '');
        };
        /**
         * Return a trace line in a standardized format.
         */
        ErrorFormatter.prototype._formatLine = function (data) {
            if (!data.func) {
                return '  at <' + this._getSource(data.source) + '>';
            }
            return '  at ' + data.func + '  <' + this._getSource(data.source) + '>';
        };
        /**
         * Parse a stack trace, apply any source mappings, and normalize its format.
         */
        ErrorFormatter.prototype._normalizeStackTrace = function (stack) {
            var lines = stack.replace(/\s+$/, '').split('\n');
            var firstLine = '';
            // strip leading blank lines
            while (/^\s*$/.test(lines[0])) {
                lines = lines.slice(1);
            }
            var stackLines = /^\s*at /.test(lines[0]) ? this._processChromeTrace(lines) : this._processSafariTrace(lines);
            if (this.executor.config.filterErrorStack) {
                stackLines = stackLines.filter(function (line) {
                    return !(/\binternal\/process\//.test(line) ||
                        /\bnode_modules\/(?!digdug|leadfoot)/.test(line) ||
                        /\/__intern\//.test(line) ||
                        /\bModule\.(?:runMain|load)/.test(line) ||
                        /\bModule\._\w+/.test(line) ||
                        /\bbootstrap_node\.js/.test(line) ||
                        /<module.js[:>]/.test(line) ||
                        /<anonymous>$/.test(line) ||
                        /<native>$/.test(line));
                });
            }
            return '\n' + firstLine + stackLines.join('\n');
        };
        /**
         * Process Chrome, Opera, and IE traces.
         *
         * Ex)
         *   at Object._updateExpressionOptions (AxiomEditor.js:511)
         *   at Object.<anonymous> (AxiomEditor.js:291)
         *   at Function.m.emit (dojo.js.uncompressed.js:8875)
         */
        ErrorFormatter.prototype._processChromeTrace = function (lines) {
            var _this = this;
            return lines.map(function (line) {
                var match;
                if ((match = /^\s*at (.+?) \(([^)]+)\)$/.exec(line))) {
                    return _this._formatLine({ func: match[1], source: match[2] });
                }
                else if ((match = /^\s*at (.*)/.exec(line))) {
                    return _this._formatLine({ source: match[1] });
                }
                else {
                    return line;
                }
            });
        };
        /**
         * Process Safari and Firefox traces.
         *
         * Ex)
         *   _updateExpressionOptions@http://localhost:8080/AxiomEditor.js:511:49
         *   http://localhost:8080/AxiomEditor.js:291:34
         *   dispatchEvent@[native code]
         *   emit@http://ajax.googleapis.com/ajax/libs/dojo/1.12.2/dojo/dojo.js:118:282
         */
        ErrorFormatter.prototype._processSafariTrace = function (lines) {
            var _this = this;
            return lines.map(function (line) {
                var match;
                if ((match = /^([^@]+)@(.*)/.exec(line))) {
                    return _this._formatLine({ func: match[1], source: match[2] });
                }
                else if ((match = /^(\w+:\/\/.*)/.exec(line))) {
                    return _this._formatLine({ source: match[1] });
                }
                else {
                    return line;
                }
            });
        };
        return ErrorFormatter;
    }());
    exports.default = ErrorFormatter;
});
//# sourceMappingURL=ErrorFormatter.js.map