(function() {
    if (window.JSLoader) {
        return;
    }

    var jsSources = {};

    function registerSource(source, callback) {
        if (jsSources[source]) {
            throw "JS-Source already loading '" + source + "' (-> specified multiple times?)";
        }
        jsSources[source] = {callbacks: []};
        if (typeof callback === 'function') {
            jsSources[source].callbacks.push(callback);
        }
    }

    //function registerRecursively(definition, callback) {
    //    if (definition.source) {
    //        registerSource(definition.source, callback);
    //        return registerDependencies(definition, function() {
    //            // Load source ..
    //            loadInternal(definition.source);
    //        });
    //    } else if (definition.sourceWithDependencies) {
    //        return registerDependencies(definition, registerRecursively(definition.sourceWithDependencies, callback));
    //    } else {
    //        var sourceCount = definition.sources.length;
    //        var loaded = 0;
    //        var sourceLoadingCallback = function() {
    //            if (++loaded == sourceCount) {
    //                callback(definition.sources)
    //            }
    //        };
    //        var allSourcesCallbacks = [];
    //        for (var i = 0; i < sourceCount; ++i) {
    //            // Register all sources as processing ..
    //            allSourcesCallbacks.push(registerRecursively(definition.sources[i], sourceLoadingCallback));
    //        }
    //        return registerDependencies(definition, function(dependencies) {
    //            // Notify that all dependencies were loaded ..
    //            // -> Trigger loading sources (-> definition.sources) ..
    //            for (var i = 0; i < allSourcesCallbacks.length; ++i) {
    //                allSourcesCallbacks[i](dependencies);
    //            }
    //        });
    //    }
    //}
    //
    //function registerDependencies(definition, notifyIfDependenciesLoaded) {
    //    if (definition.dependency) {
    //        // Prepare this dependency too ..
    //        registerSource(definition.dependency, notifyIfDependenciesLoaded);
    //        return function() {
    //            // Start loading this dependency now ..
    //            loadInternal(definition.dependency);
    //        };
    //    } else if (definition.dependencies) {
    //        // Register dependencies to make sure source wont be loaded before all dependencies finished loading ..
    //        var depCount = definition.dependencies.length;
    //        var loaded = 0;
    //        var dependencyLoadedCallback = function() {
    //            if (++loaded == depCount) {
    //                notifyIfDependenciesLoaded(definition.dependencies)
    //            }
    //        };
    //        for (var i = 0; i < depCount; ++i) {
    //            registerSource(definition.dependencies[i], dependencyLoadedCallback);
    //        }
    //        return function() {
    //            // Start loading all dependencies now ..
    //            for (var i = 0; i < depCount; ++i) {
    //                loadInternal(definition.dependencies[i]);
    //            }
    //        };
    //    }
    //    return notifyIfDependenciesLoaded;
    //}
    //
    //function convertDefinition(definition) {
    //    if (typeof definition == 'string') {
    //        return {source: definition};
    //    } else if (definition instanceof Array) {
    //        // Must be an array of sources .. ?
    //        if (definition.length == 1) {
    //            return convertDefinition(definition[0]);
    //        }
    //        var sources = [];
    //        for (var i = 0; i < definition.length; ++i) {
    //            sources.push(convertDefinition(definition[i]));
    //        }
    //        return {sources: sources};
    //    } else {
    //        // Must be "complex" ..
    //        if (!definition.source) {
    //            throw "Found invalid complex source definition: No value for source specified!";
    //        } else if ((definition.source instanceof Array) && !definition.source.length) {
    //            throw "Found invalid complex source definition: Empty array for source specified!";
    //        }
    //        var unifiedDefinition = convertDefinition(definition.source);
    //        if (definition.dependencies) {
    //            if (unifiedDefinition.dependency || unifiedDefinition.dependencies) {
    //                // We need an extra level for dependencies ..
    //                unifiedDefinition = {sourceWithDependencies: unifiedDefinition};
    //            }
    //            // Dependencies can only be string or Array ..
    //            if (definition.dependencies instanceof Array) {
    //                if (!definition.dependencies.length) {
    //                    // Empty array -> remove extra level ..
    //                    unifiedDefinition = unifiedDefinition.sourceWithDependencies;
    //                } else if (definition.dependencies.length == 1) {
    //                    unifiedDefinition.dependency = definition.dependencies[0];
    //                } else {
    //                    // TODO: Check array entries ..
    //                    unifiedDefinition.dependencies = definition.dependencies;
    //                }
    //            } else if (typeof definition.dependencies === 'string') {
    //                unifiedDefinition.dependency = definition.dependencies;
    //            } else {
    //                throw "Invalid dependencies definition: " + definition.dependencies + " (Type: " + (typeof definition.dependencies) + ")";
    //            }
    //        }
    //        return unifiedDefinition;
    //    }
    //}
    //
    //function loadWithDependencies(definition, callback) {
    //    //console.log('Original:', definition);
    //    //console.log('Converted:', convertDefinition(definition));
    //    definition = convertDefinition(definition);
    //    // Remove dependencies from first level .. (as they might already be loaded) ..
    //    var sourceDefinition;
    //    if (definition.source) {
    //        sourceDefinition = {source: definition.source};
    //    } else if (definition.sourceWithDependencies) {
    //        sourceDefinition = {sourceWithDependencies: definition.sourceWithDependencies};
    //    } else {
    //        sourceDefinition = {sources: definition.sources};
    //    }
    //    // Register all source definitions ..
    //    var onDependencyLoadingFinished = registerRecursively(sourceDefinition, callback);
    //    if (definition.dependency) {
    //        // Load dependency ..
    //        load(definition.dependency, onDependencyLoadingFinished);
    //    } else if (definition.dependencies) {
    //        // Load all dependencies ..
    //        load(definition.dependencies, onDependencyLoadingFinished)
    //    }
    //}

    function load(jsSource, callback) {
        if (jsSource instanceof Array) {
            var srcCount = jsSource.length;
            if (!srcCount) {
                throw 'Invalid jsSource defined: empty array!';
            }
            var loaded = 0;
            var srcLoadedCallback = function() {
                if (++loaded == srcCount) {
                    callback(jsSource);
                }
            };
            for (var i = 0; i < srcCount; ++i) {
                load(jsSource[i], srcLoadedCallback);
            }
        } else if (typeof jsSource !== 'string') {
            throw 'Invalid jsSource defined: ' + jsSource + ' (expected type string, but found: ' + (typeof jsSource) + ').';
        } else if (jsSources[jsSource]) {
            if (typeof callback === 'function') {
                if (jsSources[jsSource].done) {
                    // Invoke callback directly ..
                    callback(jsSource);
                } else {
                    // Register callback ..
                    jsSources[jsSource].callbacks.push(callback);
                }
            }
        } else {
            registerSource(jsSource, callback);
            loadInternal(jsSource);
        }
    }

    /**
     * Load the resource and invoke registered callbacks when finished.
     * @param jsSource the JS-file to be loaded ..
     */
    function loadInternal(jsSource) {
        var loaded = false;
        var helper = function () {
            if (!loaded) {
                loaded = true;
                jsSources[jsSource].done = true;
                var callbacks = jsSources[jsSource].callbacks;
                delete jsSources[jsSource].callbacks;
                for (var i = 0; i < callbacks.length; ++i) {
                    try {
                        callbacks[i](jsSource);
                    } catch (e) {
                        window.console && window.console.log(e);
                    }
                }
            }
        };
        var script = document.createElement('script');
        script.type = 'text/javascript';
        script.onreadystatechange = function () {
            if (this.readyState == 'loaded' || this.readyState == 'complete') {
                helper();
            }
        };
        script.onload = helper;
        script.src = jsSource;
        document.getElementsByTagName('head')[0].appendChild(script);
    }

    window.JSLoader = {
        load: load,
        rLoad: function() {
            var lastEntry = arguments[arguments.length - 1];
            var finalCallback = (typeof lastEntry === 'function') ? lastEntry : undefined;
            var array = Array.prototype.slice.call(arguments, 0, finalCallback ? (arguments.length - 1) : arguments.length);
            var idx = 0;
            var recursiveLoading = function() {
                if (idx < array.length) {
                    load(array[idx++], recursiveLoading);
                } else if (finalCallback) {
                    finalCallback(array);
                }
            };
            recursiveLoading();
        },
        createGate: function(count, callback, debug) {
            var callCount = 0;
            return function() {
                debug && window.console && console.log("Gate called for:", arguments);
                if (++callCount == count) {
                    callback();
                }
            }
        }
    }
})();