/* 
 * Generic lightweight listener/callback registry system.
 *
 * @module: bbop-registry
 */

var us = require('underscore');
var each = us.each;
var bbop = require('bbop-core');

/**
 * Contructor for BBOP registry. Takes a list of event categories as
 * strings.
 * 
 * @constructor
 * @param {Array} evt_list - a list of strings that identify the events to be used
 * @returns {Object} bbop registry object
 */
var registry = function(evt_list){
    this._is_a = 'bbop-registry';

    var registry_anchor = this;

    // Handle the registration of call functions to get activated
    // after certain events.
    this.callback_registry = {};
    each(evt_list, function(item, i){
	registry_anchor.callback_registry[item] = {};
    });
    
    /**
     * Add the specified function from the registry, with an optional
     * relative priority against other callback functions.
     *
     * The in_priority value is relative to others in the category,
     * with a higher priority...getting priority.
     * 
     * See also: <apply>
     *
     * @param {String} category - one of the pre-defined categories
     * @param {Function} in_function - function
     * @param {Number} [in_priority] - the higher the faster
     * @param {String} [function_id] - a unique string to identify a function; generated if one is not given
     * @returns {String} the ID for the registered function in the given category
     */
    this.register = function(category, in_function, in_priority, function_id){

	// Only these categories.
	if( typeof(registry_anchor.callback_registry[category]) === 'undefined'){
	    throw new Error('cannot register unknown category');
	}

	// The default priority is 0.
	var priority = 0;
	if( in_priority ){ priority = in_priority; }

	// The default ID is generated, but take one if given.
	var fid = null;
	if( function_id ){
	    fid = function_id;
	}else{
	    fid = bbop.uuid();
	}

	// Final registration.
	registry_anchor.callback_registry[category][fid] = {
	    runner: in_function,
	    priority: priority
	};

	return fid;
    };

    /**
     * Returns whether or not an id has already been registered to a
     * category. Will return null if the category does not exist.
     * 
     * @param {String} category - one of the pre-defined categories
     * @param {String} function_id - a unique string to identify a function
     * @returns {Boolean|null} true, false, or null
     */
    this.is_registered = function(category, function_id){

	var retval = null;

	var anc = registry_anchor.callback_registry;

	//
	if( typeof(anc[category]) !== 'undefined'){
	    
	    retval = false;

	    if( typeof(anc[category][function_id]) !== 'undefined'){
		retval = true;
	    }
	}

	return retval;
    };

    /**
     * Remove the specified function from the registry. Must specify a
     * legitimate category and the function id of the function in it.
     *
     * @param {String} category - string
     * @param {String} function_id - string
     * @returns {Boolean} boolean on whether something was unregistered
     */
    this.unregister = function(category, function_id){
	var retval = false;
	if( registry_anchor.callback_registry[category] &&
	    registry_anchor.callback_registry[category][function_id] ){
		delete registry_anchor.callback_registry[category][function_id];
		retval = true;
            }
	return retval;
    };
    
    /**
     * Generic getter for callback functions, returns by priority.
     *
     * @param {String} category - string
     * @returns {Array} an ordered (by priority) list of function_id strings
     */
    this.get_callbacks = function(category){

	var cb_id_list = us.keys(registry_anchor.callback_registry[category]);
	// Sort callback list according to priority.
	var ptype_registry_anchor = this;
	cb_id_list.sort(
	    function(a, b){  
		var pkg_a =
			ptype_registry_anchor.callback_registry[category][a];
		var pkg_b =
			ptype_registry_anchor.callback_registry[category][b];
		return pkg_b['priority'] - pkg_a['priority'];
	    });
	
	// Collect the actual stored functions by priority.
	var cb_fun_list = [];
	for( var cbi = 0; cbi < cb_id_list.length; cbi++ ){
	    var cb_id = cb_id_list[cbi];
	    var to_run =
		    registry_anchor.callback_registry[category][cb_id]['runner'];
	    cb_fun_list.push(to_run);
	    // ll('callback: ' + category + ', ' + cb_id + ', ' +
	    //    this.callback_registry[category][cb_id]['priority']);
	}
	
	return cb_fun_list;
    };

    /**
     * Generic runner for prioritized callbacks with various arguments
     * and an optional change in context..
     *
     * @param {String} category - string
     * @param {Array} arg_list - a list of arguments to pass to the function in the category
     * @param {String} [context] - the context to apply the arguments in
     */
    this.apply_callbacks = function(category, arg_list, context){

	// Run all against registered functions.
	var callbacks = registry_anchor.get_callbacks(category);
	for( var ci = 0; ci < callbacks.length; ci++ ){
	    var run_fun = callbacks[ci];
	    //run_fun(arg_list);
	    run_fun.apply(context, arg_list);
	}
    };
};

///
/// Exportable body.
///

module.exports = registry;