/*global define: true */ 

(function(global) {

    /*
     * PixelLab Resource Loader
     * Loads resources while providing progress updates.
     */
    function PxLoader(settings) {

        // merge settings with defaults
        settings = settings || {};
        this.settings = settings;

        // how frequently we poll resources for progress
        if (settings.statusInterval == null) {
            settings.statusInterval = 5000; // every 5 seconds by default
        }

        // delay before logging since last progress change
        if (settings.loggingDelay == null) {
            settings.loggingDelay = 20 * 1000; // log stragglers after 20 secs
        }

        // stop waiting if no progress has been made in the moving time window
        if (settings.noProgressTimeout == null) {
            settings.noProgressTimeout = Infinity; // do not stop waiting by default
        }

        var entries = [],
            // holds resources to be loaded with their status
            progressListeners = [],
            timeStarted, progressChanged = Date.now();

        /**
         * The status of a resource
         * @enum {number}
         */
        var ResourceState = {
            QUEUED: 0,
            WAITING: 1,
            LOADED: 2,
            ERROR: 3,
            TIMEOUT: 4
        };

        // places non-array values into an array.
        var ensureArray = function(val) {
            if (val == null) {
                return [];
            }

            if (Array.isArray(val)) {
                return val;
            }

            return [val];
        };

        // add an entry to the list of resources to be loaded
        this.add = function(resource) {

            // TODO: would be better to create a base class for all resources and
            // initialize the PxLoaderTags there rather than overwritting tags here
            resource.tags = new PxLoaderTags(resource.tags);

            // ensure priority is set
            if (resource.priority == null) {
                resource.priority = Infinity;
            }

            entries.push({
                resource: resource,
                status: ResourceState.QUEUED
            });
        };

        this.addProgressListener = function(callback, tags) {
            progressListeners.push({
                callback: callback,
                tags: new PxLoaderTags(tags)
            });
        };

        this.addCompletionListener = function(callback, tags) {
            progressListeners.push({
                tags: new PxLoaderTags(tags),
                callback: function(e) {
                    if (e.completedCount === e.totalCount) {
                        callback(e);
                    }
                }
            });
        };

        // creates a comparison function for resources
        var getResourceSort = function(orderedTags) {

            // helper to get the top tag's order for a resource
            orderedTags = ensureArray(orderedTags);
            var getTagOrder = function(entry) {
                var resource = entry.resource,
                    bestIndex = Infinity;
                for (var i = 0; i < resource.tags.length; i++) {
                    for (var j = 0; j < Math.min(orderedTags.length, bestIndex); j++) {
                        if (resource.tags.all[i] === orderedTags[j] && j < bestIndex) {
                            bestIndex = j;
                            if (bestIndex === 0) {
                                break;
                            }
                        }
                        if (bestIndex === 0) {
                            break;
                        }
                    }
                }
                return bestIndex;
            };
            return function(a, b) {
                // check tag order first
                var aOrder = getTagOrder(a),
                    bOrder = getTagOrder(b);
                if (aOrder < bOrder) { return -1; }
                if (aOrder > bOrder) { return 1; }

                // now check priority
                if (a.priority < b.priority) { return -1; }
                if (a.priority > b.priority) { return 1; }
                return 0;
            };
        };

        this.start = function(orderedTags) {
            timeStarted = Date.now();

            // first order the resources
            var compareResources = getResourceSort(orderedTags);
            entries.sort(compareResources);

            // trigger requests for each resource
            for (var i = 0, len = entries.length; i < len; i++) {
                var entry = entries[i];
                entry.status = ResourceState.WAITING;
                entry.resource.start(this);
            }

            // do an initial status check soon since items may be loaded from the cache
            setTimeout(statusCheck, 100);
        };

        var statusCheck = function() {
            var checkAgain = false,
                noProgressTime = Date.now() - progressChanged,
                timedOut = (noProgressTime >= settings.noProgressTimeout),
                shouldLog = (noProgressTime >= settings.loggingDelay);

            for (var i = 0, len = entries.length; i < len; i++) {
                var entry = entries[i];
                if (entry.status !== ResourceState.WAITING) {
                    continue;
                }

                // see if the resource has loaded
                if (entry.resource.checkStatus) {
                    entry.resource.checkStatus();
                }

                // if still waiting, mark as timed out or make sure we check again
                if (entry.status === ResourceState.WAITING) {
                    if (timedOut) {
                        entry.resource.onTimeout();
                    } else {
                        checkAgain = true;
                    }
                }
            }

            // log any resources that are still pending
            if (shouldLog && checkAgain) {
                log();
            }

            if (checkAgain) {
                setTimeout(statusCheck, settings.statusInterval);
            }
        };

        this.isBusy = function() {
            for (var i = 0, len = entries.length; i < len; i++) {
                if (entries[i].status === ResourceState.QUEUED || entries[i].status === ResourceState.WAITING) {
                    return true;
                }
            }
            return false;
        };

        var onProgress = function(resource, statusType) {
            
            var entry = null,
                i, len, numResourceTags, listener, shouldCall;

            // find the entry for the resource    
            for (i = 0, len = entries.length; i < len; i++) {
                if (entries[i].resource === resource) {
                    entry = entries[i];
                    break;
                }
            }

            // we have already updated the status of the resource
            if (entry == null || entry.status !== ResourceState.WAITING) {
                return;
            }
            entry.status = statusType;
            progressChanged = Date.now();

            numResourceTags = resource.tags.length;

            // fire callbacks for interested listeners
            for (i = 0, len = progressListeners.length; i < len; i++) {
                
                listener = progressListeners[i];
                if (listener.tags.length === 0) {
                    // no tags specified so always tell the listener
                    shouldCall = true;
                } else {
                    // listener only wants to hear about certain tags
                    shouldCall = resource.tags.intersects(listener.tags);
                }

                if (shouldCall) {
                    sendProgress(entry, listener);
                }
            }
        };

        this.onLoad = function(resource) {
            onProgress(resource, ResourceState.LOADED);
        };
        this.onError = function(resource) {
            onProgress(resource, ResourceState.ERROR);
        };
        this.onTimeout = function(resource) {
            onProgress(resource, ResourceState.TIMEOUT);
        };

        // sends a progress report to a listener
        var sendProgress = function(updatedEntry, listener) {
            // find stats for all the resources the caller is interested in
            var completed = 0,
                total = 0,
                i, len, entry, includeResource;
            for (i = 0, len = entries.length; i < len; i++) {
                
                entry = entries[i];
                includeResource = false;

                if (listener.tags.length === 0) {
                    // no tags specified so always tell the listener
                    includeResource = true;
                } else {
                    includeResource = entry.resource.tags.intersects(listener.tags);
                }

                if (includeResource) {
                    total++;
                    if (entry.status === ResourceState.LOADED ||
                        entry.status === ResourceState.ERROR ||
                        entry.status === ResourceState.TIMEOUT) {

                        completed++;
                    }
                }
            }

            listener.callback({
                // info about the resource that changed
                resource: updatedEntry.resource,

                // should we expose StatusType instead?
                loaded: (updatedEntry.status === ResourceState.LOADED),
                error: (updatedEntry.status === ResourceState.ERROR),
                timeout: (updatedEntry.status === ResourceState.TIMEOUT),

                // updated stats for all resources
                completedCount: completed,
                totalCount: total
            });
        };

        // prints the status of each resource to the console
        var log = this.log = function(showAll) {
            if (!window.console) {
                return;
            }

            var elapsedSeconds = Math.round((Date.now() - timeStarted) / 1000);
            window.console.log('PxLoader elapsed: ' + elapsedSeconds + ' sec');

            for (var i = 0, len = entries.length; i < len; i++) {
                var entry = entries[i];
                if (!showAll && entry.status !== ResourceState.WAITING) {
                    continue;
                }

                var message = 'PxLoader: #' + i + ' ' + entry.resource.getName();
                switch(entry.status) {
                    case ResourceState.QUEUED:
                        message += ' (Not Started)';
                        break;
                    case ResourceState.WAITING:
                        message += ' (Waiting)';
                        break;
                    case ResourceState.LOADED:
                        message += ' (Loaded)';
                        break;
                    case ResourceState.ERROR:
                        message += ' (Error)';
                        break;
                    case ResourceState.TIMEOUT:
                        message += ' (Timeout)';
                        break;
                }

                if (entry.resource.tags.length > 0) {
                    message += ' Tags: [' + entry.resource.tags.all.join(',') + ']';
                }

                window.console.log(message);
            }
        };
    }


    // Tag object to handle tag intersection; once created not meant to be changed
    // Performance rationale: http://jsperf.com/lists-indexof-vs-in-operator/3
     
    function PxLoaderTags(values) {
     
        this.all = [];
        this.first = null; // cache the first value
        this.length = 0;

        // holds values as keys for quick lookup
        this.lookup = {};
     
        if (values) {

            // first fill the array of all values
            if (Array.isArray(values)) {
                // copy the array of values, just to be safe                
                this.all = values.slice(0);
            } else if (typeof values === 'object') {
                for (var key in values) {
                    if(values.hasOwnProperty(key)) {
                        this.all.push(key);
                    }
                }
            } else {
                this.all.push(values);
            }

            // cache the length and the first value
            this.length = this.all.length;
            if (this.length > 0) {
                this.first = this.all[0];
            }
     
            // set values as object keys for quick lookup during intersection test
            for (var i = 0; i < this.length; i++) {
                this.lookup[this.all[i]] = true;
            }
        }
    }

    // compare this object with another; return true if they share at least one value
    PxLoaderTags.prototype.intersects = function(other) {

        // handle empty values case
        if (this.length === 0 || other.length === 0) {
            return false;
        } 

        // only a single value to compare?
        if (this.length === 1 && other.length === 1) {
            return this.first === other.first;
        }

        // better to loop through the smaller object
        if (other.length < this.length) {
            return other.intersects(this); 
        }
         
        // loop through every key to see if there are any matches
        for (var key in this.lookup) {
            if (other.lookup[key]) {
                return true;
            }
        }

        return false;
    };

    // AMD module support
    if (typeof define === 'function' && define.amd) {
        define('PxLoader', [], function() {
            return PxLoader;
        });
    }

    // exports
    global.PxLoader = PxLoader;

}(this));

// Date.now() shim for older browsers
if (!Date.now) {
    Date.now = function now() {
        return new Date().getTime();
    };
}

// shims to ensure we have newer Array utility methods
// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/isArray
if (!Array.isArray) {
    Array.isArray = function(arg) {
        return Object.prototype.toString.call(arg) === '[object Array]';
    };
}




/*global PxLoader: true, define: true */ 

// PxLoader plugin to load images
function PxLoaderImage(url, tags, priority) {
    var self = this,
        loader = null;

    this.img = new Image();
    this.tags = tags;
    this.priority = priority;

    var onReadyStateChange = function() {
        if (self.img.readyState === 'complete') {
            removeEventHandlers();
            loader.onLoad(self);
        }
    };

    var onLoad = function() {
        removeEventHandlers();
        loader.onLoad(self);
    };

    var onError = function() {
        removeEventHandlers();
        loader.onError(self);
    };

    var removeEventHandlers = function() {
        self.unbind('load', onLoad);
        self.unbind('readystatechange', onReadyStateChange);
        self.unbind('error', onError);
    };

    this.start = function(pxLoader) {
        // we need the loader ref so we can notify upon completion
        loader = pxLoader;

        // NOTE: Must add event listeners before the src is set. We
        // also need to use the readystatechange because sometimes
        // load doesn't fire when an image is in the cache.
        self.bind('load', onLoad);
        self.bind('readystatechange', onReadyStateChange);
        self.bind('error', onError);

        self.img.src = url;
    };

    // called by PxLoader to check status of image (fallback in case
    // the event listeners are not triggered).
    this.checkStatus = function() {
        if (self.img.complete) {
            removeEventHandlers();
            loader.onLoad(self);
        }
    };

    // called by PxLoader when it is no longer waiting
    this.onTimeout = function() {
        removeEventHandlers();
        if (self.img.complete) {
            loader.onLoad(self);
        } else {
            loader.onTimeout(self);
        }
    };

    // returns a name for the resource that can be used in logging
    this.getName = function() {
        return url;
    };

    // cross-browser event binding
    this.bind = function(eventName, eventHandler) {
        if (self.img.addEventListener) {
            self.img.addEventListener(eventName, eventHandler, false);
        } else if (self.img.attachEvent) {
            self.img.attachEvent('on' + eventName, eventHandler);
        }
    };

    // cross-browser event un-binding
    this.unbind = function(eventName, eventHandler) {
        if (self.img.removeEventListener) {
            self.img.removeEventListener(eventName, eventHandler, false);
        } else if (self.img.detachEvent) {
            self.img.detachEvent('on' + eventName, eventHandler);
        }
    };

}

// add a convenience method to PxLoader for adding an image
PxLoader.prototype.addImage = function(url, tags, priority) {
    var imageLoader = new PxLoaderImage(url, tags, priority);
    this.add(imageLoader);

    // return the img element to the caller
    return imageLoader.img;
};

// AMD module support
if (typeof define === 'function' && define.amd) {
    define('PxLoaderImage', [], function() {
        return PxLoaderImage;
    });
}
!function(e){if("object"==typeof exports)module.exports=e();else if("function"==typeof define&&define.amd)define(e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.PixelPalette=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){
var PixelPalette = function () {
        function PixelPalette(imgPath) {
            this.imgPath = imgPath;
        }
        PixelPalette.prototype.Load = function (callback) {
            var loader = new PxLoader();
            var img = loader.addImage(this.imgPath);
            var canvas = document.createElement('canvas');
            var context = canvas.getContext('2d');
            loader.addCompletionListener(function () {
                var len = img.width;
                context.drawImage(img, 0, 0, len, 1);
                var imgd = context.getImageData(0, 0, len, 1);
                var pal = imgd.data;
                var palette = [];
                for (var i = 0; i < len * 4; i += 4) {
                    palette[i / 4] = 'rgba(' + pal[i] + ', ' + pal[i + 1] + ', ' + pal[i + 2] + ', 1)';
                }
                callback(palette);
            });
            loader.start();
        };
        return PixelPalette;
    }();
module.exports = PixelPalette;
},{}]},{},[1])
(1)
});