const npm = {
    u: require('util'),
    os: require('os'),
    utils: require('../utils/static')
};

const errorReasons = {
    0: 'Source %s returned a rejection at index %d.',
    1: 'Source %s threw an error at index %d.',
    2: 'Destination %s returned a rejection at index %d.',
    3: 'Destination %s threw an error at index %d.'
};

/**
 * @class errors.SequenceError
 * @augments external:Error
 * @description
 * This type represents all errors rejected by method {@link sequence}, except for {@link external:TypeError TypeError}
 * when the method receives invalid input parameters.
 *
 * @property {string} name
 * Standard {@link external:Error Error} property - error type name = `SequenceError`.
 *
 * @property {string} message
 * Standard {@link external:Error Error} property - the error message.
 *
 * @property {string} stack
 * Standard {@link external:Error Error} property - the stack trace.
 *
 * @property {} error
 * The error that was thrown or the rejection reason.
 *
 * @property {number} index
 * Index of the element in the sequence for which the error/rejection occurred.
 *
 * @property {number} duration
 * Duration (in milliseconds) of processing until the error/rejection occurred.
 *
 * @property {string} reason
 * Textual explanation of why the method failed.
 *
 * @property {} source
 * Resolved `data` parameter that was passed into the `source` function.
 *
 * It is only set when the error/rejection occurred inside the `source` function.
 *
 * @property {} dest
 * Resolved `data` parameter that was passed into the `dest` function.
 *
 * It is only set when the error/rejection occurred inside the `dest` function.
 *
 * @see {@link sequence}
 *
 */
class SequenceError extends Error {

    constructor(e, code, cbName, duration) {

        let message;
        if (e.error instanceof Error) {
            message = e.error.message;
        } else {
            message = e.error;
            if (typeof message !== 'string') {
                message = npm.u.inspect(message);
            }
        }

        super(message);
        this.name = this.constructor.name;

        this.index = e.index;
        this.duration = duration;
        this.error = e.error;

        if ('source' in e) {
            this.source = e.source;
        } else {
            this.dest = e.dest;
        }

        cbName = cbName ? ('\'' + cbName + '\'') : '<anonymous>';
        this.reason = npm.u.format(errorReasons[code], cbName, e.index);

        Error.captureStackTrace(this, this.constructor);
    }
}

/**
 * @method errors.SequenceError.toString
 * @description
 * Creates a well-formatted multi-line string that represents the error.
 *
 * It is called automatically when writing the object into the console.
 *
 * @param {number} [level=0]
 * Nested output level, to provide visual offset.
 *
 * @returns {string}
 */
SequenceError.prototype.toString = function (level) {

    level = level > 0 ? parseInt(level) : 0;

    const gap0 = npm.utils.messageGap(level),
        gap1 = npm.utils.messageGap(level + 1),
        lines = [
            'SequenceError {',
            gap1 + 'message: ' + JSON.stringify(this.message),
            gap1 + 'reason: ' + this.reason,
            gap1 + 'index: ' + this.index,
            gap1 + 'duration: ' + this.duration
        ];

    lines.push(gap1 + 'error: ' + npm.utils.formatError(this.error, level + 1));
    lines.push(gap0 + '}');
    return lines.join(npm.os.EOL);
};

npm.utils.addInspection(SequenceError, function () {
    return this.toString();
});

module.exports = {SequenceError};