var OU = OU || {};

OU.loader = (function(utils, spinner) {
    /**
     * a general purpose loader for various file types/VLE calls
     * @constructor
     */
    function Loader() {
        this.el = null;
        this.spinner = null;
        this.loading = [];
        this.dir = {};
        this.data = null;
        this.types = {};
        this.callback = null;
    }

    Loader.prototype = {
        /**
         * common load function
         * @param {object} data - cfg object
         * @param {string} data.dir - directory to resolve all paths by
         * @param {object} data.load - items to be loaded broken down by type
         * @param {function} data.callback - callback function
         * @param {boolean} data.spinner - show spinner (default true)
         * @param {Element} data.container - containing element for spinner
         */
        load: function (data) {
            if (!data) {
                console.log('No data passed to loader');
                return;
            }

            console.log('data to be loaded: ', data);

            this.data = {};
            this.dir = data.dir || {};
            this.loading.splice(0); // clear array
            this.callback = data.callback || console.log.bind(null, 'No callback provided!');

            if (data.spinner !== false) {
                this.spinner = spinner.construct(data.container);
                this.spinner.init();
            }

            this.queue(data.load);
        },

        /**
         * loops data passed for each type and calls relevant get
         * @param {object} data - items to be loaded broken down by type
         */
        queue: function (data) {
            var type;

            utils.forIn(data, function (list, key) {
                if (typeof list == 'string') {
                    // list = [list];
                }

                if (!Array.isArray(list) || !list.length || !this.get[key]) {
                    return;
                }

                type = this.getType(key);

                if (this.loading.indexOf(type.name) < 0) {
                    this.loading.push(type.name);
                }

                list.forEach(function (data) {
                    type.loaded.push(null);
                    type.data.push(data);

                    this.get[type.name](this, type, data, (type.data.length - 1));
                }.bind(this));

                console.log('type \'' + type.name + '\' queued');
            }.bind(this));

            // if there's nothing to load...
            if (!this.loading.length) {
                this.complete();
            }
        },

        /**
         * returns type object (sets up first if undefined)
         * @param type
         * @returns {object}
         */
        getType: function (type) {
            if (!this.types[type]) {
                this.types[type] = {
                    name: type,
                    data: [],
                    loaded: []
                };
            }

            return this.types[type];
        },

        getDir: function (type) {
            return this.dir[type] || '';
        },

        setDir: function (type, dir) {
            this.dir[type] = dir;
        },

        /**
         * holds get functions for various types
         */
        get: {
            /**
             * loads a css file
             * @param {Loader} loader - reference to this
             * @param {object} type - type data object
             * @param {string} src - url (relative or absolute)
             * @param {number} i - item type index
             */
            css: function (loader, type, src, i) {
                var link = document.createElement('link');
                document.head.appendChild(link);

                link.rel = 'stylesheet';
                link.href = loader.getDir(type.name) + src;

                link.onload = function () {
                    // console.log(type.name + '[' + i + '] ' + src + ' loaded');

                    var obj = {
                        src: src
                    };

                    type.loaded[i] = obj;
                    loader.itemLoaded(type.name, i);
                };
            },

            /**
             * loads an image
             * @param {Loader} loader - reference to this
             * @param {object} type - type data object
             * @param {string} src - url (relative or absolute)
             * @param {number} i - item type index
             */
            image: function (loader, type, src, i) {
                var img = new Image();

                img.src = loader.getDir(type.name) + src;

                img.onload = function () {
                    // console.log(type.name + '[' + i + '] ' + src + ' loaded');

                    var obj = {
                        // TODO sort out default locations - loader.data.image.dir or loader.dir.image?
                        // src: (loader.dir === '' ? 'img/' : '') + src
                        src: src
                    };

                    // crappy IE returns 0 for natural dimensions if svg hence workaround
                    if (src.indexOf('.svg') > -1) {
                        document.body.appendChild(this);
                        obj.width = this.width;
                        obj.height = this.height;
                        document.body.removeChild(this);
                    } else {
                        obj.width = this.naturalWidth;
                        obj.height = this.naturalHeight;
                    }

                    type.loaded[i] = obj;
                    loader.itemLoaded(type.name, i);
                };
            },

            /**
             * gets a files contents (what exactly is determined by format)
             * @param {Loader} loader - reference to this
             * @param {object} type - type data object
             * @param {string} src - url (relative or absolute)
             * @param {number} i - item type index
             */
            file: function (loader, type, src, i) {
                var xhr = new XMLHttpRequest(),
                    format = src.split('.').pop(),
                    doc;

                src = loader.getDir(type.name) + src;

                // hacked in for backward compatibilty
                var normaliseBackgroundImageData = function (data) {
                    data.targets = data.targets || {};

                    if (data.hasOwnProperty('backgroundImage')) {
                        data.targets.backgroundImage = data.backgroundImage;
                        delete data.backgroundImage;
                    }

                    if (data.targets.backgroundImage && typeof data.targets.backgroundImage != 'boolean') {
                        data.images = data.images || {};
                        data.images.files = data.images.files || [];
                        data.images.alt = data.images.alt || [];

                        data.images.files.unshift(data.targets.backgroundImage);
                        data.targets.backgroundImage = true;
                    }
                };

                xhr.onreadystatechange = function () {
                    if (xhr.readyState === 4) {
                        if (xhr.status === 200 || xhr.status === 0) {
                            // console.log(type.name + '[' + i + ']' + ' loaded');

                            switch (format) {
                                case 'svg':
                                case 'xml':
                                    if (window.DOMParser) {
                                        doc = new DOMParser().parseFromString(xhr.responseText, 'text/xml');
                                    } else {
                                        // IE9 support...
                                        doc = new ActiveXObject('Microsoft.XMLDOM');
                                        doc.async = false;
                                        doc.loadXML(xhr.responseText);
                                    }
                                    doc = doc.documentElement;
                                    break;
                                case 'json':
                                    doc = JSON.parse(xhr.responseText);

                                    if (doc.images && doc.images.files) {
                                        doc.images.dir = loader.getDir(type.name);

                                        loader.queue({
                                            image: doc.images.files.map(function (path) {
                                                return path;
                                            })
                                        });
                                    }
                                    if (doc.css) {
                                        if (typeof doc.css === 'string') {
                                            doc.css = [doc.css];
                                        }
                                        if (Array.isArray(doc.css)) {
                                            loader.queue({
                                                css: doc.css.map(function (path) {
                                                    return path;
                                                })
                                            });
                                        }
                                    }
                                    break;
                                default:
                                    doc = xhr.responseText;
                            }

                            type.loaded[i] = doc;
                            loader.itemLoaded(type.name, i);
                        }
                    }
                };

                xhr.open('GET', src);
                xhr.send();
            },

            /**
             * gets
             * @param {Loader} loader - reference to this
             * @param {object} type - type data object
             * @param {object[]} data - doc name and part ([name, part])
             * @param {number} i - item type index
             */
            olink: function (loader, type, data, i) {
                VLE.get_olink_url(
                    data[0],
                    data[1] || '',
                    function (url) {
                        // console.log(type.name + '[' + i + ']' + ' loaded');
                        type.loaded[i] = url.replace(/&/g, '&amp;');
                        loader.itemLoaded(type.name, i);
                    },
                    function (err) {
                        // console.log(type.name + '[' + i + ']' + ' failed to load', error);
                        type.loaded[i] = 'javascript:void(0)';
                        loader.itemLoaded(type.name, i, err);
                    },
                    VLE.get_param('_c') //courseid
                );
            },

            /**
             * loads an image
             * @param {Loader} loader - reference to this
             * @param {object} type - type data object
             * @param {string} name - name attribute of Folder tag (SC)
             * @param {number} i - item type index
             */
            folder: function (loader, type, name, i) {
                var formats = {
                    css: ['css'],
                    file: ['json', 'js'],
                    // file: ['css', 'json', 'js'],
                    image: ['jpg', 'png', 'svg']
                };
                var load = {},
                    format;

                // TODO load assets from JSON if present, otherwise load everything in folder!
                VLE.get_folder(name,
                    function (contents) {
                        // console.log(type.name + '[' + i + ']' + ' loaded');

                        /* contents.forEach(function (data) {
                                data.format = data.path.split('.').pop();

                                if (data.format == 'json') {
                                    //load json...
                                }
                        }); */

                        // load contents by type relative to format
                        contents.forEach(function (data) {
                            format = data.path.split('.').pop();

                            if (format === 'json') {
                                load.file = [data.path];
                            }

                            // utils.forIn(formats, function(list, key) {
                            //     if (list.indexOf(format) > -1) {
                            //         load[key] = load[key] || [];
                            //         load[key].push(data.path);
                            //     }
                            // });

                            // switch (format) {
                            //     case 'css':
                            //         // contents.css = data.url;
                            //         break;
                            //     case 'json':
                            //         contents.json = load.file.length - 1; // TODO sort this out
                            // }
                        });

                        if (contents[0].url.includes('.zip')) {
                            utils.forIn(formats, function (list, key) {
                                loader.setDir(key, contents[0].url.match(/.+?(?=zip)/) + 'zip//');
                            });
                        }

                        // if (contents[0].url.includes('.zip')) {
                        //     console.log(contents[0].url.match(/.+?(?=zip)/) + 'zip//');
                        //     loader.setDir(type.name, contents[0].url.match(/.+?(?=zip)/) + 'zip//');
                        // }

                        loader.queue(load);
                        type.loaded[i] = contents;
                        loader.itemLoaded(type.name, i);
                    },
                    function (err) {
                        // console.log(type.name + '[' + i + ']' + ' failed to load', err);
                        type.loaded[i] = [];
                        loader.itemLoaded(type.name, i, err);
                    });
            }
        },

        /**
         * checks whether items for given type are all loaded
         * @param {string} type - type name
         * @returns {boolean}
         */
        isLoaded: function (type) {
            //compare data and loaded lengths and, if true, check all values are truthy
            return this.types[type].loaded.length === this.types[type].data.length && this.types[type].loaded.every(function (data) {
                return !!data;
            });
        },

        /**
         * resets type data if all items for a given type are loaded and calls this.complete
         * if all items for all types are loaded.
         * @param {string} type - type name
         */
        itemLoaded: function (type, i, err) {
            var index;

            if (typeof err === 'undefined') {
                console.log(type + '[' + i + ']' + ' loaded');
            } else {
                console.log(type + '[' + i + ']' + ' failed to load', err);
            }

            if (this.isLoaded(type)) {
                index = this.loading.indexOf(type);

                if (index > -1) {
                    // remove type from loading array
                    this.loading.splice(index, 1);

                    // set type data on return object
                    this.data[type] = this.data[type] || [];
                    Array.prototype.push.apply(this.data[type], this.types[type].loaded.splice(0));
                    this.types[type].data = [];

                    console.log('type \'' + type + '\' loaded');
                }
            }

            if (this.loading.length === 0) {
                this.complete();
            }
        },

        /**
         * wraps everything up and calls this.callback
         */
        complete: function () {
            console.log('everything loaded');

            // if (this.data.folder) {
            //     this.data.folder.forEach(function(contents) {
            //         var json, images;

            //         if (typeof contents.json === 'number') { // loaded from folder
            //             json = this.data.file[contents.json];

            //             //ensure JSON is at index 0
            //             this.data.file.unshift(this.data.file.splice(contents.json, 1)[0]);

            //             if (this.data.image && this.data.image.length) {
            //                 json.images = json.images || {};
            //                 json.images.dir = this.dir;
            //                 json.images.files = json.images.files || [];

            //                 images = this.data.image.slice(0);

            //                 relativeSort(json.images.files, images);

            //                 if (json.images.files.length > images.length) {
            //                     pad(json.images.files, images);
            //                 }

            //                 json.images.files = images;
            //             }

            //             // if css, set url on JSON object
            //             if (contents.css) {
            //                 json.css = contents.css;
            //             }
            //         }
            //     }.bind(this));
            // }

            // //sort a2 relative to a1
            // function relativeSort (a1, a2) {
            //     a2.sort(function(a, b) {
            //         return (a1.indexOf(a.src.match(/[^/]*$/)[0]) > a1.indexOf(b.src.match(/[^/]*$/)[0])) ? 1 : -1;
            //     }).splice(0, a2.length - a1.length);
            // }

            // //pad a2 with corresponding data from a1
            // function pad (a1, a2) {
            //     a1.forEach(function (src, i) {
            //         if (!a2[i] || src !== a2[i].src) {
            //             a2.splice(i, 0, a2.reduce(function (result, obj) {
            //                 return (typeof result === 'undefined' && src === obj.src) ? obj : result;
            //             }));
            //         }
            //     });
            // }

            this.callback(this.data);
        }
    };

    return {
        /**
         *
         * @returns {Loader}
         */
        construct: function () {
            return new Loader();
        }
    };
})(OU.utils, OU.spinner);