/*jslint white: true, browser: true, devel: true, onevar: true, nomen: true,
  eqeqeq: true, plusplus: true, bitwise: true, regexp: true, newcap: true,
  immed: true, strict: true, maxlen: 78 */
/*global Event */
'use strict';

/**
 * MPB namespace
 * @namespace mpb namespace
 */
var MPB = MPB || {};

/**
 * a class to reliably use multiple tracking mechanisms before activating
 * an action
 * @constructor
 * @param {Function} trackedAction The action to call after tracking.
 * @param {number} timeout the maximum running time for all tracking
 *                         attempts.
 */
MPB.multiTracker = function (trackedAction, timeout) {

    var registry, waiting, timer, debug;
    /**
     * var {Object.<string, function>}
     */
    registry = {};
    waiting = {};
    debug = false;

    /**
     * output debug information to console if activated and console available
     * @private
     * @param {Object|string} msg thing to output.
     */
    function logdebug(msg) {
        if (debug && typeof console !== 'undefined') {
            console.debug(msg);
        }
    }


    /**
     * call the ultimate (tracked) action if not called already
     * @private
     */
    function finalAction(sender) {
        clearTimeout(timer);
        logdebug('launching the REAL action');
        trackedAction(sender);
    }


    /**
     * check if all trackers are done (registry is empty)
     * @private
     * @return {boolean} true if all trackers are done.
     */
    function allTrackersFinished() {
        for (var i in waiting) {
            if (waiting.hasOwnProperty(i)) { // filter prototype additions
                return false;
            }
        }
        return true;
    }

    /**
     * dump all outstanding trackers
     * @private
     */
    function dumpOutstandingTrackers() {
        for (var i in waiting) {
            if (waiting.hasOwnProperty(i)) { // filter prototype
                logdebug('Still waiting for "' + i + '"');
            }
        }
    }

    /**
     * launch the final action if no more trackingAgents are busy
     * @private
     */
    function checkAllDone() {
        if (allTrackersFinished()) {
            finalAction();
        } else {
            dumpOutstandingTrackers();
        }
    }

    /**
     * register a trackingAgent which the multitracker
     * @private
     * @param {String} trackerName Name to use for registry
     * @param {MPB.TrackingAgent} trackingAgent tracking agent
     * @return {MPB.TrackingAgent} tracking agent
     */
    function registerTrackingAgent(trackerName, trackingAgent) {
        trackingAgent.setUnregisterCallback(
            function () {
                logdebug("tracker '" + trackerName + "' finished.");
                delete waiting[trackerName];
                checkAllDone();
            }
        );
        registry[trackerName] = trackingAgent;
        logdebug('Added tracker "' + trackerName + '"');
        return trackingAgent;
    }

    return {
        /**
         * create a callback based tracker and add it to the registry
         *
         * @public
         * @param {String} trackerName name to register this tracker under.
         * @param {Function} fireAction function to call for this tracker.
         * @return {Object} tracker object.
         */
        addTracker : function (trackerName, fireAction) {
            return registerTrackingAgent(
                trackerName,
                new MPB.TrackingAgent(fireAction)
            );
        },


        /**
         * shortcut for creating pixel based trackers
         *
         * @public
         * @param {String} trackerName name to register this tracker under.
         * @param {String} pixelUrl url to load when firing.
         * @return {Object} tracker object.
         */
        addTrackingPixel : function (trackerName, pixelUrl) {
            return this.addTracker(
                trackerName,
                function () {
                    var img, that;
                    img = new Image();
                    that = this;
                    $(img).bind('error', that.done);
                    $(img).bind('load', that.done);
                    logdebug('loading: ' + pixelUrl);
                    img.src = pixelUrl;
                }
            );
        },

        
        /**
         * activate all trackers in the registry by calling their start()-
         * method and passing them their individual 'completed'-closures
         *
         * @public
         */
        fire : function (sender) {
            timer = setTimeout(
                function () {
                    logdebug('Timeout!');
                    finalAction(sender);
                }, timeout
            );
            for (var trackerName in registry) {
                if (registry.hasOwnProperty(trackerName)) {
                    logdebug('starting Tracker "' + trackerName + '"');
                    waiting[trackerName] = true;
                    registry[trackerName].start();
                }
            }
        },


        /**
         * toggle debug state
         *
         * @param {boolean} state turn debug mode on or off.
         */
        debug : function (state) {
            debug = state;
        }
    };
};

/**
 * a single tracking job
 * @constructor
 * @param {Function} fireAction function to start this tracker.
 */
MPB.TrackingAgent = function (fireAction) {

    var doneAction = function () {};

    return {
        start : fireAction,

        /**
         * function to call when tracker is finished
         */
        done : function () {
            doneAction();
        },

        /**
         * alias for done()
         */
        completed : function () {
            doneAction();
        },

        /**
         * set the callback for deregistering this agent
         * @param {Function} callback function to call when agent is done.
         */
        setUnregisterCallback : function (callback) {
            doneAction = callback;
        }
    };
};

