// Load modules

var Http = require('http'); var Hoek = require('hoek');

// Declare internals

var internals = {};

exports.wrap = function (error, statusCode, message) {

Hoek.assert(error instanceof Error, 'Cannot wrap non-Error object');
return (error.isBoom ? error : internals.initialize(error, statusCode || 500, message));

};

exports.create = function (statusCode, message, data) {

return internals.create(statusCode, message, data, exports.create);

};

internals.create = function (statusCode, message, data, ctor) {

var error = new Error(message ? message : undefined);       // Avoids settings null message
Error.captureStackTrace(error, ctor);                       // Filter the stack to our external API
error.data = data || null;
internals.initialize(error, statusCode);
return error;

};

internals.initialize = function (error, statusCode, message) {

var numberCode = parseInt(statusCode, 10);
Hoek.assert(!isNaN(numberCode) && numberCode >= 400, 'First argument must be a number (400+):', statusCode);

error.isBoom = true;
error.isServer = numberCode >= 500;

if (!error.hasOwnProperty('data')) {
    error.data = null;
}

error.output = {
    statusCode: numberCode,
    payload: {},
    headers: {}
};

error.reformat = internals.reformat;
error.reformat();

if (!message &&
    !error.message) {

    message = error.output.payload.error;
}

if (message) {
    error.message = (message + (error.message ? ': ' + error.message : ''));
}

return error;

};

internals.reformat = function () {

this.output.payload.statusCode = this.output.statusCode;
this.output.payload.error = Http.STATUS_CODES[this.output.statusCode] || 'Unknown';

if (this.output.statusCode === 500) {
    this.output.payload.message = 'An internal server error occurred';              // Hide actual error from user
}
else if (this.message) {
    this.output.payload.message = this.message;
}

};

// 4xx Client Errors

exports.badRequest = function (message, data) {

return internals.create(400, message, data, exports.badRequest);

};

exports.unauthorized = function (message, scheme, attributes) { // Or function (message, wwwAuthenticate[])

var err = internals.create(401, message, undefined, exports.unauthorized);

if (!scheme) {
    return err;
}

var wwwAuthenticate = '';
var i = 0;
var il = 0;

if (typeof scheme === 'string') {

    // function (message, scheme, attributes)

    wwwAuthenticate = scheme;

    if (attributes || message) {
        err.output.payload.attributes = {};
    }

    if (attributes) {
        var names = Object.keys(attributes);
        for (i = 0, il = names.length; i < il; ++i) {
            var name = names[i];
            if (i) {
                wwwAuthenticate += ',';
            }

            var value = attributes[name];
            if (value === null ||
                value === undefined) {              // Value can be zero

                value = '';
            }
            wwwAuthenticate += ' ' + name + '="' + Hoek.escapeHeaderAttribute(value.toString()) + '"';
            err.output.payload.attributes[name] = value;
        }
    }

    if (message) {
        if (attributes) {
            wwwAuthenticate += ',';
        }
        wwwAuthenticate += ' error="' + Hoek.escapeHeaderAttribute(message) + '"';
        err.output.payload.attributes.error = message;
    }
    else {
        err.isMissing = true;
    }
}
else {

    // function (message, wwwAuthenticate[])

    var wwwArray = scheme;
    for (i = 0, il = wwwArray.length; i < il; ++i) {
        if (i) {
            wwwAuthenticate += ', ';
        }

        wwwAuthenticate += wwwArray[i];
    }
}

err.output.headers['WWW-Authenticate'] = wwwAuthenticate;

return err;

};

exports.forbidden = function (message, data) {

return internals.create(403, message, data, exports.forbidden);

};

exports.notFound = function (message, data) {

return internals.create(404, message, data, exports.notFound);

};

exports.methodNotAllowed = function (message, data) {

return internals.create(405, message, data, exports.methodNotAllowed);

};

exports.notAcceptable = function (message, data) {

return internals.create(406, message, data, exports.notAcceptable);

};

exports.proxyAuthRequired = function (message, data) {

return internals.create(407, message, data, exports.proxyAuthRequired);

};

exports.clientTimeout = function (message, data) {

return internals.create(408, message, data, exports.clientTimeout);

};

exports.conflict = function (message, data) {

return internals.create(409, message, data, exports.conflict);

};

exports.resourceGone = function (message, data) {

return internals.create(410, message, data, exports.resourceGone);

};

exports.lengthRequired = function (message, data) {

return internals.create(411, message, data, exports.lengthRequired);

};

exports.preconditionFailed = function (message, data) {

return internals.create(412, message, data, exports.preconditionFailed);

};

exports.entityTooLarge = function (message, data) {

return internals.create(413, message, data, exports.entityTooLarge);

};

exports.uriTooLong = function (message, data) {

return internals.create(414, message, data, exports.uriTooLong);

};

exports.unsupportedMediaType = function (message, data) {

return internals.create(415, message, data, exports.unsupportedMediaType);

};

exports.rangeNotSatisfiable = function (message, data) {

return internals.create(416, message, data, exports.rangeNotSatisfiable);

};

exports.expectationFailed = function (message, data) {

return internals.create(417, message, data, exports.expectationFailed);

};

exports.badData = function (message, data) {

return internals.create(422, message, data, exports.badData);

};

exports.preconditionRequired = function (message, data) {

return internals.create(428, message, data, exports.preconditionRequired);

};

exports.tooManyRequests = function (message, data) {

return internals.create(429, message, data, exports.tooManyRequests);

};

// 5xx Server Errors

exports.internal = function (message, data, statusCode) {

return internals.serverError(message, data, statusCode, exports.internal);

};

internals.serverError = function (message, data, statusCode, ctor) {

var error;
if (data instanceof Error) {
    error = exports.wrap(data, statusCode, message);
} else {
    error = internals.create(statusCode || 500, message, undefined, ctor);
    error.data = data;
}

return error;

};

exports.notImplemented = function (message, data) {

return internals.serverError(message, data, 501, exports.notImplemented);

};

exports.badGateway = function (message, data) {

return internals.serverError(message, data, 502, exports.badGateway);

};

exports.serverTimeout = function (message, data) {

return internals.serverError(message, data, 503, exports.serverTimeout);

};

exports.gatewayTimeout = function (message, data) {

return internals.serverError(message, data, 504, exports.gatewayTimeout);

};

exports.badImplementation = function (message, data) {

var err = internals.serverError(message, data, 500, exports.badImplementation);
err.isDeveloperError = true;
return err;

};