"use strict";

/*globals DUMAS, $, jQuery */

jQuery.extend(DUMAS, (function () {

    var loadedModules = {},
        pendingModules = {},
        firstModules = [],
        lastModules = [],
        normalModules = [],
        documentReady = false,
        contextReady = false,
        pendingRequests = -1;

    function executeModule(module, root) {
        if (!module.initialized) {
            module.initialized = true;
            if (module.init) {
                module.init(DUMAS);
            }
        }
        if (module.handleHtml) {
            module.handleHtml(root || $('body'));
        }
    }

    function executeModuleGroup(group, modules, root) {
        //todo:jmarnell execute dependencies?
        for (var i = 0; i < group.length; i = i + 1) {
            if (modules[group[i].name]) {
                executeModule(group[i], root);
            }
        }
    }

    function executeModules(root, moduleList) {
        var modules, i;
        if (arguments.length === 1 && typeof root === 'string') {
            moduleList = root;
            root = undefined;
        }

        if (moduleList) {
            modules = {};
            moduleList = moduleList.split(',');
            for (i = 0; i < moduleList.length; i = i + 1) {
                modules[moduleList[i]] = true;
            }
        } else {
            modules = loadedModules;
        }
        //console.log(modules.toSource());
        executeModuleGroup(firstModules, modules, root);
        executeModuleGroup(normalModules, modules, root);
        executeModuleGroup(lastModules, modules, root);
    }    

    function executeModulesIfReady(newModule) {
        if ((pendingRequests === -1 || (pendingRequests === 0 && pendingModules.length)) && documentReady && contextReady) {
            if (newModule && pendingRequests === -1) {
                executeModule(newModule);
            } else {
                executeModules();
            }
            pendingRequests = -1;
        }
    }    

    function registerModule() {
        var module, options;

        if (arguments.length === 3) {
            options = {
                name: arguments[0],
                init: arguments[1],
                handleHtml: arguments[2]
            };
        } else {
            options = arguments[0];
        }

        if (!loadedModules[options.name]) {

            module = {
                name: options.name,
                init: options.init,
                handleHtml: options.handleHtml
            };
            loadedModules[options.name] = module;

            if (pendingModules[options.name]) {
                delete pendingModules[options.name];
                pendingModules.length = pendingModules.length - 1;
            }

            //todo:jmarnell depends on?
            (options.first ? firstModules : options.last ? lastModules : normalModules).push(module);
            executeModulesIfReady(module);
        }
    }

    function requireModule(name, url) {
        if (!loadedModules[name] && !pendingModules[name]) {
            pendingModules[name] = true;
            pendingModules.length = (pendingModules.length || 0) + 1;
            jQuery.getScript(url); //todo:jmarnell make sure this doesn't block progressive rendering + is proper jquery method for loading a .js
        }
    }

    jQuery.fn.extend({
        update: function (moduleList) {
            executeModules(this, moduleList);
            return this;
        },
        set: function (a1, a2) {
            DUMAS._tagData(this, a1, a2);
            return this;
        },
        settings : function (data) {
            var stored, search,	results, prop, numProps, origNumProps, first = this.eq(0);

            if (typeof data === 'string') {
                prop = data.split(/,\s*/);
                numProps = prop.length;
                search = {};
                results = {};
                jQuery.map(prop, function (e) {
                    search[e] = true;
                });
            } else {
                numProps = 0;
                search = jQuery.extend({}, data);
                results = jQuery.extend({}, data);

                for (prop in data) {
                    if (Object.hasOwnProperty.call(data, prop)) {
                        numProps = numProps + 1;
                    }
                }
            }
            origNumProps = numProps;

            first.add(first.parents()).each(function () {
                var prop;

                stored = $(this).data('dumas');
                if (stored) {
                    for (prop in search) {
                        if (stored.hasOwnProperty(prop)) {
                            results[prop] = stored[prop];
                            delete search[prop];
                            numProps = numProps - 1;
                            if (numProps <= 0) {
                                return false;
                            }
                        }
                    }
                }
            });
            return origNumProps > 1 ? results : results[prop];
        }
    });

    jQuery.extend({
        update: executeModules
    });

    $(function () {
        documentReady = true;
        executeModulesIfReady();
    });

    return {
        //modules should register themselves
        registerModule: registerModule,

        //private, don't use these... the JSP does
        _requireModule: requireModule,
        _setContextReady: function () {
            contextReady = true;
            executeModulesIfReady();
        },
        _tagData: function (jq, name, val) {
            var data;
            if (typeof jq === 'string') {
                jq = $('#' + jq);
            }
            if (val !== undefined) {
                data = {};
                data[name] = val;
            } else {
                data = name;
            }

            jq.each(function () {
                var prop;

                jQuery.data(this, 'dumas', jQuery.extend(jQuery.data(this, 'dumas'), data));
                for (prop in data) {
                    if (Object.hasOwnProperty.call(data, prop)) {
                        $(this).attr(prop, data[prop]);
                    }
                }
            });
        }
    };

}()));