/**
 * Create a context.
 * @function serverside.context._newContext
 * @param {object} [context] - Predefined context object.
 * @param {object} [properties] - Optional settings.
 * @returns {object} - Defined server context object.
 * @author Taka Okunishi
 * @protected
 * @ignore
 *
 */

var util = require('util'),
    format = util.format;

exports = module.exports = function (context, properties) {
    if (!context) {
        context = {};
    }
    var lazyProperties = exports._createLazyProperties;
    Object.defineProperties(context, lazyProperties(properties, context));

    Object.defineProperty(context, 'assertKeys', {
        configurable: true,
        enumerable: false,
        get: function () {
            return function (requiredKeys) {
                requiredKeys = [].concat(requiredKeys);
                var context = this;
                for (var i = 0; i < requiredKeys.length; i++) {
                    var key = requiredKeys[i];
                    if (!context[key]) {
                        var errMsg = format('context.%s missing.', key);
                        throw new Error(errMsg);
                    }
                }
            }
        }
    });

    return context;
};

exports._createLazyProperty = function (getter) {
    var _cache = null;
    return {
        enumerable: true,
        configurable: true,
        get: function () {
            if (_cache)return _cache;
            var result = getter.apply(this, arguments);
            _cache = result;
            return result;
        }
    }
};

exports._createLazyProperties = function (properties, context) {
    var result = {};
    Object.keys(properties).forEach(function (key) {
        result[key] = exports._createLazyProperty(function () {
            return exports._applyContext(properties[key], context);
        });
    });
    return result;
};

/**
 * Apply context to property definitions.
 * @param {object} definitions - Property definitions.
 * @param {object} context - A context object to apply.
 * @returns {*}
 * @protected
 * @ignore
 */
exports._applyContext = function (definitions, context) {
    if (Array.isArray(definitions)) {
        return definitions.map(function (definition) {
            return definition.call(definition, context);
        });
    }
    for (var key in definitions) {
        if (!definitions.hasOwnProperty(key)) continue;
        definitions[key] = definitions[key].call(definitions, context);
    }
    return definitions;
};
