;(function(){
    'use strict';

    // can we support addEventListener
    var hasNative = 'addEventListener' in (new Image());

    var preLoader = function(images, options){
        this.options = {
            pipeline: false,
            auto: true,
            prefetch: false,
            /* onProgress: function(){}, */
            /* onError: function(){}, */
            onComplete: function(){}
        };

        options && typeof options == 'object' && this.setOptions(options);

        this.addQueue(images);
        this.queue.length && this.options.auto && this.processQueue();
    };

    preLoader.prototype.setOptions = function(options){
        // shallow copy
        var o = this.options,
            key;

        for (key in options) options.hasOwnProperty(key) && (o[key] = options[key]);

        return this;
    };

    preLoader.prototype.addQueue = function(images){
        // stores a local array, dereferenced from original
        this.queue = images.slice();

        return this;
    };

    preLoader.prototype.reset = function(){
        // reset the arrays
        this.completed = [];
        this.errors = [];

        return this;
    };

    preLoader.prototype.addEvents = function(image, src, index){
        var self = this,
            o = this.options,
            cleanup = function(){
                if (hasNative){
                    this.removeEventListener('error', abort);
                    this.removeEventListener('abort', abort);
                    this.removeEventListener('load', load);
                }
                else {
                    this.onerror = this.onabort = this.onload = null;
                }
            },
            abort = function(){
                console.log('src error:' + src);
                cleanup.call(this);

                self.errors.push(src);
                o.onError && o.onError.call(self, src);
                checkProgress.call(self, src);
                o.pipeline && self.loadNext(index);
            },
            load = function(){
                console.log('src load:' + src);
                cleanup.call(this);

                // store progress. this === image
                self.completed.push(src); // this.src may differ
                checkProgress.call(self, src, this);
                o.pipeline && self.loadNext(index);
            };

        if (hasNative){
            image.addEventListener('error', abort, false);
            image.addEventListener('abort', abort, false);
            image.addEventListener('load', load, false);
        }
        else {
            image.onerror = image.onabort = abort;
            image.onload = load;
        }

    };

    preLoader.prototype.load = function(src, index){
        /*jshint -W058 */
        var image = new Image;

        this.addEvents(image, src, index);

        // actually load
        image.src = src;

        return this;
    };

    preLoader.prototype.loadNext = function(index){
        // when pipeline loading is enabled, calls next item
        index++;
        this.queue[index] && this.load(this.queue[index], index);

        return this;
    };

    preLoader.prototype.processQueue = function(){
        // runs through all queued items.
        var i = 0,
            queue = this.queue,
            len = queue.length;

        // process all queue items
        this.reset();

        if (!this.options.pipeline) for (; i < len; ++i) this.load(queue[i], i);
        else this.load(queue[0], 0);

        return this;
    };

    /*jshint validthis:true */
    function checkProgress(src, image){
        // intermediate checker for queue remaining. not exported.
        // called on preLoader instance as scope
        var args = [],
            o = this.options;

        // call onProgress
        o.onProgress && src && o.onProgress.call(this, src, image, this.completed.length);

        if (this.completed.length + this.errors.length === this.queue.length){
            args.push(this.completed);
            this.errors.length && args.push(this.errors);
            o.onComplete.apply(this, args);
        }

        return this;
    }
    /*jshint validthis:false */

    if (typeof define === 'function' && define.amd){
        // we have an AMD loader.
        define(function(){
            return preLoader;
        });
    }
    else {
        this.preLoader = preLoader;
    }
}).call(this);