(function () {
    var config = {}, requests = [], scheduled = false, requested = {}, initialized = false, deferred = null;

    function getMessage(str) {
        return "requireAL: " + str;
    };

    function scheduleRequire() {
        scheduled = true;
        setTimeout(require);
    };

    function completeInitialization() {
        if (initialized)
            return;

        typeof deferred === "function" && deferred();

        console.info(getMessage("No require requests remaining, completing require cycle and initializing the Control Add-In."));
        techDays && typeof techDays.initialize === "function" && techDays.initialize();
        initialized = true;
    }

    function require() {
        if (!requests.length) {
            completeInitialization();
            return;
        };

        function createScript(src, callback) {
            console.info(getMessage("Creating script tag for: " + src));
            var script = document.createElement("script");
            script.src = src;
            script.onload = function () {
                console.info(getMessage("Script successfully loaded from \"" + src + "\""));
                callback();
            };
            document.head.appendChild(script);
        };

        function cloneScript(req, callback) {
            console.info(getMessage("Requesting script \"" + req.name + "\" by pattern: " + req.pattern));
            var cloned = false;
            [].slice.call(window.top.document.getElementsByTagName("script")).forEach(function (tag) {
                tag.src.match(req.pattern) && (function (src) {
                    console.info(getMessage("Successfully matched script for pattern " + req.pattern));
                    cloned = true;
                    createScript(src, callback);
                })(tag.src);
            });
            return cloned;
        };

        var request = requests.shift();
        console.info(getMessage("Requesting module \"" + request.name + "\""));

        function requestUrl(req) {
            if (!req.url)
                throw new Error(getMessage("Fallback to requesting script \"" + req.name + "\" by URL has failed, because no URL is specified, and loading script by other methods has failed."));

            console.info(getMessage("Requesting script \"" + req.name + "\" by URL: " + req.url));
            createScript(req.url, function () {
                scheduleRequire();
            });
        };

        function requestNav(req) {
            if (!req.nav)
                return false;

            console.info(getMessage("Requesting script \"" + req.name + "\" from NAV"));
            requested[req.name] = req;
            req.nav_requested = true;
            Microsoft.Dynamics.NAV.InvokeExtensibilityMethod("Require", [req.nav], false, function () {
                setTimeout(function () {
                    req.nav_received ? scheduleRequire() : requestUrl(req);
                });
            });
            return true;
        };

        function requestPattern(req) {
            return req.pattern ? cloneScript(req, function () {
                scheduleRequire();
            }) : false;
        };

        requestPattern(request) || requestNav(request) || requestUrl(request);
    };

    window.requireAL = function (module) {
        var m = config[module];
        if (typeof m !== "object")
            throw new Error(getMessage("Undefined module \"" + module + "\" has been requested."));

        setTimeout(function() {
            requests.length || scheduleRequire();
            requests.push(m);
        });
    };

    window.requireAL.defer = function (callback) {
        if (typeof callback !== "function")
            throw new Error(getMessage("Cannot defer, because function was expected and " + typeof callback + " was received."));

        deferred = callback;
    };

    window.requireAL.complete = function (script) {
        console.info(getMessage("Completed receiving requested script \"" + script + "\" from NAV"));
        requested[script].nav_received = true;
    };

    window.requireAL.init = function () {
        scheduled || scheduleRequire();
    };

    window.requireAL.config = function (setup) {
        var t = typeof setup;
        if (t !== "object")
            throw new Error(getMessage("Call to config expects an object or an array. Instead, a \"" + t + "\""));

        function invalidModuleConfiguration(setup, error, property) {
            property && typeof property === "string" && (error += " in property \"" + property + "\"");
            var message = "Invalid module configuration (" + error + ")";
            setup && (message += ": " + JSON.stringify(setup));
            return message;
        };

        function push(s) {
            if (config.hasOwnProperty(s.name))
                throw new Error(invalidModuleConfiguration(null, "duplicate module configuration requested: \"" + s.name + "\""));
            if (typeof s.name !== "string" || !s.name)
                throw new Error(invalidModuleConfiguration(s, "name missing"));
            if (s.pattern && (typeof s.pattern !== "object" || !(s.pattern instanceof RegExp)))
                throw new Error(invalidModuleConfiguration(s, "regular expression expected", "pattern"));
            if (s.nav && typeof s.nav !== "string")
                throw new Error(invalidModuleConfiguration(s, "string expected", "nav"));
            if (s.url && typeof s.url !== "string")
                throw new Error(invalidModuleConfiguration(s, "string expected", "url"));
            if (!s.pattern && !s.nav && !s.url)
                throw new Error(invalidModuleConfiguration(s, "at least one of \"pattern\", \"nav\", or \"url\" properties are required"));

            config[s.name] = s;
        };
        setup instanceof Array
            ? setup.forEach(function (c) { push(c); })
            : push(setup);
    };
})();

requireAL.config([
    {
        name: "jQuery",
        pattern: /\/jquery-2\./g,
        nav: "jQuery",
        url: "https://code.jquery.com/jquery-2.2.4.min.js"
    },
    {
        name: "jQueryUI",
        pattern: /\/jquery-ui/g,
        nav: "jQueryUI"
    }
]);

requireAL("jQuery");
requireAL("jQueryUI");
