// 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;
};
