'use strict';

/*@
Ox.get <f> Get a remote file
    # fixme: remote? same-origin-policy? jsonp?
    (url, callback) -> <u> undefined
    url <s> Remote URL
    callback <f> Callback function
        data <s> The contents of the remote resource
@*/
Ox.get = function(url, callback) {
    var req = new XMLHttpRequest();
    req.open('GET', url, true);
    req.onreadystatechange = function() {
        if (req.readyState == 4) {
            if (req.status == 200) {
                callback(req.responseText);
            } else {
                throw new Error(
                    'Cannot get URL "' + url + '" (Status: ' + req.status + ')'
                );
            }
        }
    };
    req.send();                
};

/*@
Ox.getJSON <f> Get and parse one or more remote JSON files
    # fixme: remote? same-origin-policy?
    (url, callback) -> <u> undefined
    url <s|[s]> One or more remote URLs
    callback <f> Callback function
        data <o> The parsed contents of the remote resource(s)
            For multiple URLs, keys are file names, values are contents
@*/
Ox.getJSON = function(url, callback) {
    var urls = Ox.makeArray(url);
    Ox.loadAsync(urls, function(url, callback) {
        Ox.get(url, function(data) {
            var result = {};
            result[url] = JSON.parse(data);
            callback(result);
        });
    }, function(results) {
        callback(urls.length == 1 ? results[url] : results);
    });
}

/*@
Ox.getJSONP <f> Get and parse one or more remote JSONP files
    (url, callback) -> <u> undefined
    url <s|[s]> One or more remote URLs,
        {callback} gets replaced with jsonp callback function name
    callback <f> Callback function
        data <o> The parsed contents of the remote resource(s)
            For multiple URLs, keys are file names, values are contents
@*/
Ox.getJSONP = function(url, callback) {
    var urls = Ox.makeArray(url);
    Ox.loadAsync(urls, function(url, callback) {
        var id = 'callback' + Ox.uid();
        Ox.getJSONP[id] = function(data) {
            delete Ox.getJSONP[id];
            callback(data);
        }
        Ox.$('body').append(Ox.$('<script>').attr({
            'src': url.replace('{callback}', 'Ox.getJSONP.' + id),
            'type': 'text/javascript'
        }));
    }, function(results) {
        callback(urls.length == 1 ? results[url] : results);
    });
}

/*@
Ox.getJSONC <f> Get and parse a remote JSONC file
    JSONC is JSON with JavaScript line or block comments
    (url, callback) -> <u> undefined
    url <s> Remote URL
    callback <f> Callback function
        data <s> The parsed contents of the remote resource
@*/
Ox.getJSONC = function(url, callback) {
    Ox.get(url, function(data) {
        callback(JSON.parse(Ox.minify(data)));
    });
};

/*@
Ox.loadFile <f> Loads a file (image, script or stylesheet)
    (file="script.js", callback)      -> <u> undefined
    (file="stylesheet.css", callback) -> <u> undefined
    (file="image.png", callback)      -> <u> undefined
    file     <s> Local path or remote URL
    callback <f> Callback function
        image <e> DOM element (if the file is an image)
@*/
Ox.loadFile = (function() {
    // fixme: this doesn't handle errors yet
    // fixme: rename to getFile?
    var cache = {};
    return function(files, callback) {
        Ox.loadAsync(files, function(file, callback) {
            loadFile(file, function(images) {
                callback(images ? {file: images} : {});
            });
        }, callback);
    }
    
    function loadFile(file, callback) {
        var element,
            head = document.head || document.getElementsByTagName( "head" )[0] || document.documentElement,
            request,
            type = file.split('.').pop(),
            isImage = type != 'css' && type != 'js';
        if (!cache[file]) {
            if (isImage) {
                element = new Image();
                element.onload = addFileToCache;
                element.src = file;
            } else {
                if (!findFileInHead()) {
                    element = document.createElement(
                        type == 'css' ? 'link' : 'script'
                    );
                    element[type == 'css' ? 'href' : 'src'] = file + '?' + Ox.random(1000000);
                    element.type = type == 'css' ? 'text/css' : 'text/javascript';
                    if (type == 'css') {
                        element.rel = 'stylesheet';
                    }
                    if (/MSIE/.test(navigator.userAgent)) {
                        // fixme: find a way to check if css/js have loaded in msie
                        setTimeout(addFileToCache, 2500);
                    } else {
                        if (type == 'css') {
                            waitForCSS();
                        } else {
                            element.onload = addFileToCache;
                        }                        
                    }
                    head.appendChild(element);
                } else {
                    addFileToCache();
                }
            }
        } else {
            callback();
        }
        function addFileToCache() {
            if (isImage) {
                // for an image, save the element itself,
                // so that it remains in the browser cache
                cache[file] = element;
                callback(element);
            } else {
                cache[file] = true;
                callback();
            }
        }
        function findFileInHead() {
            return Ox.toArray(
                document.getElementsByTagName(type == 'css' ? 'link' : 'script')
            ).map(function(element) {
                return element[type == 'css' ? 'href' : 'src'] == file;
            }).reduce(function(prev, curr) {
                return prev || curr; 
            }, false);
        }
        function waitForCSS() {
            var error = false;
            try {
                element.sheet.cssRule;
            } catch (e) {
                error = true;
                setTimeout(function() {
                    waitForCSS();
                }, 25);
            }
            !error && addFileToCache();
        }
    };
}());

/*@
Ox.loadAsync <f> Runs an asynchonous function on array elements
    (arr, map, callback) -> <u> undefined
    arr <s|a> String or array of either strings or arrays of strings 
        Multiple strings in the same array will be processed simultaneously, but
        multiple arrays of strings will be processed in that order.
    callback <f> Callback function
        results <o> Results
@*/
Ox.loadAsync = function(array, map, callback) {
    array = Ox.makeArray(array);
    var i = 0, n = array.length, results = {};
    if (array.some(Ox.isArray)) {
        iterate();
    } else {
        array.forEach(function(value) {
            map(value, function(result) {
                Ox.extend(results, result);
                ++i == n && callback(results);
            });
        });
    }
    function iterate() {
        Ox.loadAsync(array.shift(), function(result) {
            Ox.extend(results, result);
            array.length ? iterate() : callback(results);
        });
    }
};