(function () {
    
    var logger = ST.logger.forClass('context/Custom'),
        contextInit,
        contextStop,
        checkGlobalLeaks;
    
    /**
     * Configures a custom context. Configured methods can return
     * promises if they perform asynchronous operations.
     * 
     * @param {Object} cfg Configuration object. Can contain the following properties:
     * @param {Function} [cfg.init] Context initialization function 
     * @param {Function} [cfg.stop] Context termination function
     */
    ST.configureContext = function (cfg) {
        contextInit = cfg.init;
        contextStop = cfg.stop;
        checkGlobalLeaks = cfg.checkGlobalLeaks;
    };
 
    
    /**
     * Customizable context executed in sandbox environment. In contrast with
     * the default WebDriver context, where ST + WebDriverIO is automatically
     * initialized, no driver libraries will be loaded by default.
     * 
     * The user is responsible for controlling the driver client
     * lifecycle, using any libraries such as WebdriverJS based on the
     * configuration provided via `driverConfig` instance property.
     * 
     * The appropriate set of test suites is always loaded by the ST Sandbox
     * initialization.
     * 
     * @class ST.context.Custom
     */
    ST.context.Custom = ST.define({
        extend: ST.context.Base,
 
        constructor: function (config) {
            var me = this;
            ST.context.Custom.superclass.constructor.call(me, config);
            ST.apply(me, config);
        },
 
        init: function () {
            logger.trace('.init');
            var me = this,
                result;
            
            if (typeof contextInit === 'function') {
                try {
                    result = contextInit.call(me);
                } catch (err) {
                    logger.error(err.stack || err);
                    return Promise.reject(err);
                }
            }
            
            if (result && result.then) {
                return result;
            } else {
                return Promise.resolve();
            }
        },
 
        stop: function (resolve, reject) {
            logger.trace('.stop');
            var me = this,
                result;
            
            if (me._stopped) {
                resolve();
                return;
            } else {
                me._stopped = true;
            }
            
            if (typeof contextStop === 'function') {
                try {
                    result = contextStop.call(me);
                } catch (err) {
                    logger.error(err.stack || err);
                    reject(err);
                    return;
                }
            }
            
            if (result) {
                if (result.then) {
                    result.then(resolve, reject);
                } else {
                    resolve(result);
                }
            } else {
                resolve();
            }
        },
 
        _checkGlobalLeaks: function (done) {
            var me = this,
                result;
            
            function makeResult (passed, message) {
                return {
                    results: [{
                        passed: passed,
                        message: message
                    }]
                };
            }
            
            if (typeof checkGlobalLeaks === 'function') {
                try {
                    result = checkGlobalLeaks.call(me);
                } catch (err) {
                    done(makeResult(false, err.stack || err));
                    return;
                }
            }
            
            if (result) {
                if (result.then) {
                    result.then(function (message) {
                        done(makeResult(true, message));
                    }, function (err) {
                        done(makeResult(false, err.stack || err));
                    })
                } else {
                    done(makeResult(!!result));
                }
            } else {
                done(makeResult(true));
            }
        }
 
    });
 
}());