/**
     * The stars in our starfield!
     * Stars coordinate system is relative to the CENTER of the canvas
     * @param  {number} x
     * @param  {number} y
     */
    var Star = function(x, y, maxSpeed) {
        this.x = x;
        this.y = y;
        this.slope = y / x; // This only works because our origin is always (0,0)
        this.opacity = 0;
        this.speed = Math.max(Math.random() * maxSpeed, 1);
    };

    /**
     * Compute the distance of this star relative to any other point in space.
     *
     * @param  {int} originX
     * @param  {int} originY
     *
     * @return {float} The distance of this star to the given origin
     */
    Star.prototype.distanceTo = function(originX, originY) {
        return Math.sqrt(Math.pow(originX - this.x, 2) + Math.pow(originY - this.y, 2));
    };

    /**
     * Reinitializes this star's attributes, without re-creating it
     *
     * @param  {number} x
     * @param  {number} y
     *
     * @return {Star} this star
     */
    Star.prototype.resetPosition = function(x, y, maxSpeed) {
        Star.apply(this, arguments);
        return this;
    };

    /**
     * The BigBang factory creates stars (Should be called StarFactory, but that is
     * a WAY LESS COOL NAME!
     * @type {Object}
     */
    var BigBang = {
        /**
         * Returns a random star within a region of the space.
         *
         * @param  {number} minX minimum X coordinate of the region
         * @param  {number} minY minimum Y coordinate of the region
         * @param  {number} maxX maximum X coordinate of the region
         * @param  {number} maxY maximum Y coordinate of the region
         *
         * @return {Star} The random star
         */
        getRandomStar: function(minX, minY, maxX, maxY, maxSpeed) {
            var coords = BigBang.getRandomPosition(minX, minY, maxX, maxY);
            return new Star(coords.x, coords.y, maxSpeed);
        },

        /**
         * Gets a random (x,y) position within a bounding box
         *
         *
         * @param  {number} minX minimum X coordinate of the region
         * @param  {number} minY minimum Y coordinate of the region
         * @param  {number} maxX maximum X coordinate of the region
         * @param  {number} maxY maximum Y coordinate of the region
         *
         * @return {Object} An object with random {x, y} positions
         */
        getRandomPosition: function(minX, minY, maxX, maxY) {
            return {
                x: Math.floor((Math.random() * maxX) + minX),
                y: Math.floor((Math.random() * maxY) + minY)
            };
        }
    };

    /**
     * Constructor function of our starfield. This just prepares the DOM nodes where
     * the scene will be rendered.
     *
     * @param {string} canvasId The DOM Id of the <div> containing a <canvas> tag
     */
    var StarField = function(containerId) {
        this.container = document.getElementById(containerId);
        this.canvasElem = this.container.getElementsByTagName('canvas')[0];
        this.canvas = this.canvasElem.getContext('2d');

        this.width = this.container.offsetWidth;
        this.height = this.container.offsetHeight;

        this.starField = [];
    };

    /**
     * Updates the properties for every star for the next frame to be rendered
     */
    StarField.prototype._updateStarField = function() {
        var i,
            star,
            randomLoc,
            increment;

        for (i = 0; i < this.numStars; i++) {
            star = this.starField[i];

            increment = Math.min(star.speed, Math.abs(star.speed / star.slope));
            star.x += (star.x > 0) ? increment : -increment;
            star.y = star.slope * star.x;

            star.opacity += star.speed / 100;

            // Recycle star obj if it goes out of the frame
            if ((Math.abs(star.x) > this.width / 2) ||
                (Math.abs(star.y) > this.height / 2)) {
                //randomLoc = BigBang.getRandomPosition(
                //    -this.width / 2, -this.height / 2,
                //       this.width, this.height
                //);
                randomLoc = BigBang.getRandomPosition(
                    -this.width / 10, -this.height / 10,
                       this.width / 5, this.height / 5
                );
                star.resetPosition(randomLoc.x, randomLoc.y, this.maxStarSpeed);
            }
        }
    };

    /**
     * Renders the whole starfield (background + stars)
     * This method could be made more efficient by just blipping each star,
     * and not redrawing the whole frame
     */
    StarField.prototype._renderStarField = function() {
        var i,
            star;
        // Background
        this.canvas.fillStyle = "rgba(0, 0, 0, .5)";
        this.canvas.fillRect(0, 0, this.width, this.height);
        // Stars
        for (i = 0; i < this.numStars; i++) {
            star = this.starField[i];
            this.canvas.fillStyle = "rgba(188, 213, 236, " + star.opacity + ")";
            this.canvas.fillRect(
                star.x + this.width / 2,
                star.y + this.height / 2,
                2, 2);
        }
    };

    /**
     * Function that handles the animation of each frame. Update the starfield
     * positions and re-render
     */
    StarField.prototype._renderFrame = function(elapsedTime) {
        var timeSinceLastFrame = elapsedTime - (this.prevFrameTime || 0);

        window.requestAnimationFrame(this._renderFrame.bind(this));

        // Skip frames unless at least 30ms have passed since the last one
        // (Cap to ~30fps)
        if (timeSinceLastFrame >= 30 || !this.prevFrameTime) {
            this.prevFrameTime = elapsedTime;
            this._updateStarField();
            this._renderStarField();
        }
    };

    /**
     * Makes sure that the canvas size fits the size of its container
     */
    StarField.prototype._adjustCanvasSize = function(width, height) {
        // Set the canvas size to match the container ID (and cache values)
        this.width = this.canvasElem.width = width || this.container.offsetWidth;
        this.height = this.canvasElem.height = height || this.container.offsetHeight;
    };

    /**
     * This listener compares the old container size with the new one, and caches
     * the new values.
     */
    StarField.prototype._watchCanvasSize = function(elapsedTime) {
        var timeSinceLastCheck = elapsedTime - (this.prevCheckTime || 0),
            width,
            height;

        window.requestAnimationFrame(this._watchCanvasSize.bind(this));

        // Skip frames unless at least 333ms have passed since the last check
        // (Cap to ~3fps)
        if (timeSinceLastCheck >= 333 || !this.prevCheckTime) {
            this.prevCheckTime = elapsedTime;
            width = this.container.offsetWidth;
            height = this.container.offsetHeight;
            if (this.oldWidth !== width || this.oldHeight !== height) {
                this.oldWidth = width;
                this.oldHeight = height;
                this._adjustCanvasSize(width, height);
            }
        }
    };

    /**
     * Initializes the scene by resizing the canvas to the appropiate value, and
     * sets up the main loop.
     * @param {int} numStars Number of stars in our starfield
     */
    StarField.prototype._initScene = function(numStars) {
        var i;
        for (i = 0; i < this.numStars; i++) {
            this.starField.push(
                BigBang.getRandomStar(-this.width / 2, -this.height / 2, this.width, this.height, this.maxStarSpeed)
            );
        }

        // Intervals not stored because I don't plan to detach them later...
        window.requestAnimationFrame(this._renderFrame.bind(this));
        window.requestAnimationFrame(this._watchCanvasSize.bind(this));
    };

    /**
     * Kicks off everything!
     * @param {int} numStars The number of stars to render
     * @param {int} maxStarSpeed Maximum speed of the stars (pixels / frame)
     */
    StarField.prototype.render = function(numStars, maxStarSpeed) {
        this.numStars = numStars || 100;
        this.maxStarSpeed = maxStarSpeed || 3;

        this._initScene(this.numStars);
    };

    /**
     * requestAnimationFrame shim layer with setTimeout fallback
     * @see http://paulirish.com/2011/requestanimationframe-for-smart-animating
     */
    (function() {
        var lastTime = 0;
        var vendors = ['ms', 'moz', 'webkit', 'o'];
        for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
            window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
            window.cancelAnimationFrame =
              window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];
        }

        if (!window.requestAnimationFrame)
            window.requestAnimationFrame = function(callback, element) {
                var currTime = new Date().getTime();
                var timeToCall = Math.max(0, 16 - (currTime - lastTime));
                var id = window.setTimeout(function() { callback(currTime + timeToCall); },
                  timeToCall);
                lastTime = currTime + timeToCall;
                return id;
            };

        if (!window.cancelAnimationFrame)
            window.cancelAnimationFrame = function(id) {
                clearTimeout(id);
            };
    }());

    // Kick off!
    var starField = new StarField('fullScreen').render(333, 3);