var g_sprites = new Array();
var g_enum = new SpriteEnum();
var g_enumKey = new KeyEnum();
var g_game = new Game();
var g_misc = new MiscLibrary();
var g_imageSprites = new Array();

function init(scoreText) {
    g_game.initSprites();
    if (g_game.fullRender) {
        g_game.preloadImages();
    }
    else {
        g_game.initImageSprites();
    }

    g_game.running = true;

    g_game.canvas1 = document.getElementById('canvas1');
    g_game.canvas2 = document.getElementById('canvas2');
    g_game.context1 = g_game.canvas1.getContext('2d');
    g_game.context2 = g_game.canvas2.getContext('2d');
    g_game.scoreElement = document.getElementById('mainScore');

    g_game.scoreText = scoreText;
    g_game.intervalID = setInterval(run, g_game.delayMS);
}

function disableFullRender() {
    g_game.fullRender = false;
}

function run() {
    var i = 0;
    g_game.scoreOld = g_game.score;
    g_game.countdown();

    for (i in g_sprites) {
        g_sprites[i].resize();
        g_sprites[i].handleType();
        g_sprites[i].move();
        g_game.checkCollision(i);
        g_sprites[i].flagged = false;
    }
    for (i in g_sprites) {
        if (g_sprites[i].energy <= 0) {
            g_sprites.splice(i, 1);
        }
    }

    if (g_game.score != g_game.scoreOld) {
        g_game.scoreEnergy = 50; // Math.abs(g_game.score - g_game.scoreOld) * 20;
    }
    g_game.draw();
}

function increaseScoreBy(scoreIncrease) {
    g_game.score += scoreIncrease - 1;
    g_game.scoreBonus += scoreIncrease - 1;
    g_game.increaseScore();
}

function handleKeyPress(e) {
    var step = 4;
    var defaultY = 200;
    var bubbleEvent = false;

    var key = String.fromCharCode(e.charCode ? e.charCode : e.keyCode);
    switch (key) {
        case 'p':
            g_game.pause();
            break;

        /*
        case 'm':
            g_game.useFlipping = !g_game.useFlipping;
            break;
        */

        case ' ':
            if (g_game.running && !g_game.paused) {
                if (g_game.running) {
                    g_game.shootBall();
                }
            }
            break;

        default:
            bubbleEvent = true;
    }

    return bubbleEvent;
}

function handleKeyDown(e) {
    var keyID = window.event ? event.keyCode : e.keyCode;
    var step = 20;
    var defaultY = 200;
    var bubbleEvent = true;
    var margin = 50;

    if (g_game.running) {
        bubbleEvent= false;
        switch (keyID) {

            case g_enumKey.left:
                if (g_game.cursorX > -margin) {
                    g_game.cursorX -= step;
                    g_game.cursorY = Math.abs(g_game.cursorX - g_game.width / 2);
                }
                break;

            case g_enumKey.right:
                if (g_game.cursorX < g_game.width + margin) {
                    g_game.cursorX += step;
                    g_game.cursorY = Math.abs(g_game.cursorX - g_game.width / 2);
                }
                break;

            default:
                bubbleEvent = true;
        }
    }
    return bubbleEvent;
}

function handleMoved(e) {
    if (g_game.running) {
        var x = 0, y = 0;
        if (!e) var e = window.event;
        if (e.layerX) {
            x = e.layerX;
            y = e.layerY;
        }
        else {
            x = e.x;
            y = e.y;
        }
        if (x && y && x >= 1 && y >= 1) {
            g_game.cursorX = x;
            g_game.cursorY = y;
        }
    }
}

function handleClicked(e) {
    if (g_game.running) {
        g_game.shootBall();
    }
}


/**** Game follows ****/

function Game() {
    this.running = false;
    this.countdownBallStart = 100;
    this.countdownBall = 10;
    this.countdownPlayerBallStart = 20;
    this.countdownPlayerBall = 10;
    this.score = 0;
    this.scoreBonus = 0;
    this.scoreEnergy = 0;
    this.scoreOld = 0;
    this.scoreElement = null;
    this.scoreText = '';
    this.delayMS = 30;
    this.intervalID = 0;
    this.currentCanvas = 1;
    this.width = 695;
    this.height = 500;
    this.fullCircle = Math.PI * 2;
    this.barrierWidth = 270;
    this.normalRadius = 14;
    this.cursorX = -1;
    this.cursorY = -1;
    this.maxColors = 5;
    this.maxColorsLimit = 7;
    this.chainreactAt = 3;
    this.bonusAt = this.chainreactAt + 1;
    this.maxPlayerBalls = 4;
    this.barrierTop = 320;
    this.canvas1 = null;
    this.canvas2 = null;
    this.context1 = null;
    this.context2 = null;
    this.testMode = false;
    this.fullRender = true;
    this.paused = false;
    this.maxImageSprites = 1000;
    this.minOpacityForDisplay = .4;
    this.images = new Array();
    this.useFlipping = false;
}

Game.prototype.getImage = function(path) {
    if (!this.images[path]) {
        this.images[path] =  new Image();
        this.images[path].src = path;
    }
    return this.images[path];
}

Game.prototype.preloadImages = function() {
    var temp = null;
    for (var i = 1; i <= g_game.maxColorsLimit; i++) {
        // temp = this.getImage('image/ball-' + i + '.png');
        temp = this.getImage('image/bonus-' + i + '.png');
    }
    // temp = this.getImage('image/sparkle.png');
}

Game.prototype.shootBall = function() {
    var sprite = this.getPlayerBallSprite();
    if (sprite) {
        this.countdownPlayerBall = this.countdownPlayerBallStart;
        sprite.parent = 0;
        var speedReduce = 18;
        var y = this.cursorY;
        var padding = 65;
        if (y > this.height - padding) {
            y = this.height - padding;
        }
        sprite.speedX = -( (this.width / 2 - this.cursorX) / speedReduce );
        sprite.speedY = -( (this.height - y) / speedReduce );
        if ( Math.abs(sprite.speedX) + Math.abs(sprite.speedY) <= 4 ) {
            sprite.speedX *= 2;
            sprite.speedY *= 2;
        }
        this.countdownPlayerBall = this.countdownPlayerBallStart;
    }
}

Game.prototype.pause = function() {
    if (this.running) {
        this.paused = !this.paused;
        if (this.paused) {
            clearInterval(this.intervalID);
            g_misc.showLayer('canvasOverlayPaused');
        }
        else {
            g_misc.hideLayer('canvasOverlayPaused');
            this.intervalID = setInterval(run, this.delayMS);
        }
    }
}

Game.prototype.checkCollision = function(i) {
    if (g_sprites[i].type == g_enum.playerBall) {
        var didHitLimit = g_sprites[i].checkLimits();
        if (didHitLimit) {
            g_game.addSparkles(g_sprites[i].x, g_sprites[i].y, false);
        }
    }

    if ( (g_sprites[i].speedX != 0 || g_sprites[i].speedY != 0) && g_sprites[i].type != g_enum.barrierBall ) {
        for (var j in g_sprites) {
            if (j != i) {
                g_sprites[i].handleCollision(g_sprites[j]);
            }
        }
    }
}

Game.prototype.getTypeCount = function(thisType) {
    var thisCount = 0;
    for (var i in g_sprites) {
        if (g_sprites[i].type == thisType) {
            thisCount++;
        }
    }
    return thisCount;
}

Game.prototype.flagChained = function(sprite) {
    sprite.flagged = true;
    for (var i in g_sprites) {
        if ( g_sprites[i] != sprite && g_sprites[i].type == g_enum.normalBall &&
                !g_sprites[i].flagged && g_sprites[i].subtype == sprite.subtype &&
                sprite.doesCollide(g_sprites[i], 18) ) {
            g_game.flagChained(g_sprites[i]);
        }
    }
}

Game.prototype.countdown = function()  {
    if (--this.countdownBall == 0) {
        this.countdownBall = this.countdownBallStart;
        this.addNormalBallSprite();
    }
    if ( this.countdownPlayerBall > 0 &&
            g_game.getTypeCount(g_enum.playerBall) < g_game.maxPlayerBalls && --this.countdownPlayerBall == 0 ) {
        this.addPlayerBallSprite();        
    }
    if (this.scoreEnergy > 0) {
        this.scoreEnergy--;
    }
}

Game.prototype.draw = function() {
    if (this.useFlipping) {
        var oldCanvas = this.currentCanvas == 1 ? this.canvas1 : this.canvas2;
        this.currentCanvas = this.currentCanvas == 1 ? 2 : 1;
        var canvas = this.currentCanvas == 1 ? this.canvas1 : this.canvas2;
        var context = this.currentCanvas == 1 ? this.context1 : this.context2;
    
        context.clearRect(0, 0, this.width, this.height);
        if (!g_game.fullRender) {
            this.clearImageSprites();
        }
        for (var i in g_sprites) {
            g_sprites[i].draw(context);
        }

        canvas.style.zIndex = 100;
        oldCanvas.style.zIndex = 0;
    }
    else {
        this.context1.clearRect(0, 0, this.width, this.height);
        if (!g_game.fullRender) {
            this.clearImageSprites();
        }
        for (var i in g_sprites) {
            g_sprites[i].draw(this.context1);
        }
    }

    if (this.scoreEnergy > 0) {
        this.scoreElement.innerHTML = this.scoreText + ' <strong>' + this.score + '</strong>';
    }
    else {
        this.scoreElement.innerHTML = this.scoreText + ' ' + this.score;
    }
}

Game.prototype.getFreeImageSpriteElement = function() {
    var index = -1;
    for (var i = 1; i <= g_game.maxImageSprites; i++) {
        if (!g_imageSprites[i].active) {
            g_imageSprites[i].active = true;
            index = i;
            break;
        }
    }
    return g_imageSprites[index].element;
}

Game.prototype.clearImageSprites = function() {
    for (var i = 1; i <= g_game.maxImageSprites; i++) {
        if (g_imageSprites[i].active) {
            g_imageSprites[i].active = false;
            g_imageSprites[i].element.style.display = 'none';
        }
    }
}

Game.prototype.end = function() {
    clearInterval(this.intervalID);
    g_misc.writeInLayer('gameOverScore', g_game.score);
    g_misc.showLayer('gameOverShade');
    g_misc.showLayer('gameOver');
}

Game.prototype.initSprites = function() {
    this.addBarrierSprites();
    this.addCannonSprite();
}

Game.prototype.initImageSprites = function() {
    var i = 0, s = '';

    for (i = 1; i <= g_game.maxImageSprites; i++) {
        s += '<img src="image/ball-' + g_misc.getRandomInt(1, g_game.maxColors) + '.png" ' +
                'alt="" class="imageSprite" id="imageSprite' + i + '" /> ';
    }
    g_misc.writeInLayer('imageSpriteWrapper', s);

    for (var i = 1; i <= g_game.maxImageSprites; i++) {
        g_imageSprites[i] = new ImageSprite();
        g_imageSprites[i].element = document.getElementById('imageSprite' + i);
    }
}

Game.prototype.getPlayerBallSprite = function() {
    var sprite = null;
    for (var i in g_sprites) {
        if (g_sprites[i].type == g_enum.playerBall && g_sprites[i].parent == g_enum.player) {
            sprite = g_sprites[i];
            break;
        }
    }

    return sprite;
}

Game.prototype.addNormalBallSprite = function() {
    var i = g_sprites.length;
    g_sprites[i] = new Sprite();
    g_sprites[i].x = this.getThrowPosition();
    g_sprites[i].y = -10;
    g_sprites[i].speedX = 0;
    g_sprites[i].speedY = 2; // 5;
    g_sprites[i].radius = this.normalRadius;
    g_sprites[i].radiusTarget = g_sprites[i].radius;
    g_sprites[i].subtype = g_misc.getRandomInt(1, g_game.maxColors);
    g_sprites[i].setSubtypeRGB();
    g_sprites[i].type = g_enum.normalBall;    
}

Game.prototype.addPlayerBallSprite = function() {
    var i = g_sprites.length;
    g_sprites[i] = new Sprite();
    g_sprites[i].x = this.width / 2;
    g_sprites[i].y = this.height;
    g_sprites[i].radius = 2;
    g_sprites[i].radiusTarget = this.normalRadius;
    g_sprites[i].subtype = g_misc.getRandomInt(1, g_game.maxColors);
    g_sprites[i].setSubtypeRGB();
    g_sprites[i].type = g_enum.playerBall;
    g_sprites[i].parent = g_enum.player;
}

Game.prototype.addBarrierSprites = function() {
    for (var x = 1; x <= this.barrierWidth; x += this.normalRadius * 2) {
        var i = g_sprites.length;
        g_sprites[i] = new Sprite();
        g_sprites[i].radius = this.normalRadius;
        g_sprites[i].radiusTarget = g_sprites[i].radius;
        g_sprites[i].x = x + g_game.width / 2 - this.barrierWidth / 2 + g_sprites[i].radius / 2;
        g_sprites[i].y = g_game.barrierTop;
        g_sprites[i].setRGB(100, 100, 100);
        g_sprites[i].type = g_enum.barrierBall;
    }
}

Game.prototype.addCannonSprite = function() {
    var i = g_sprites.length;
    g_sprites[i] = new Sprite();
    g_sprites[i].x = this.width / 2;
    g_sprites[i].y = this.height;
    g_sprites[i].radius = 30;
    g_sprites[i].radiusTarget = g_sprites[i].radius;
    g_sprites[i].type = g_enum.cannon;
    g_sprites[i].setRGB(140, 120, 120);
}

Game.prototype.getFlagCount = function() {
    var flagCount = 0;
    for (var i in g_sprites) {
        if (g_sprites[i].flagged) { flagCount++; }
    }
    return flagCount;
}

Game.prototype.explodeFlagged = function(speedX, speedY) {
    for (var i in g_sprites) {
        if (g_sprites[i].flagged) {
            g_sprites[i].active = false;
            g_sprites[i].energy = 50;
            g_sprites[i].radiusTarget = g_sprites[i].radius * 3;
            g_sprites[i].type = g_enum.normalBall;
            g_sprites[i].halt();
            g_game.increaseScore();
            g_game.addSparkles(g_sprites[i].x, g_sprites[i].y, true);
        }
    }
    for (var i in g_sprites) {
        var s = g_sprites[i];
        if (s.active && s.type == g_enum.normalBall && s.speedY == 0 && s.y + s.radius < g_game.barrierTop - g_game.normalRadius) {
            s.speedY = 2;
            s.y -= 3;
        }
    }
}

Game.prototype.increaseScore = function() {
    this.score++;
    this.scoreBonus++;

    if (this.scoreBonus == 100) {
        this.scoreBonus = 0;
        if (this.score >= 1500) {
            this.countdownBallStart -= 5;
            if (this.countdownBallStart < 50) {
                this.countdownBallStart = 50;
            }
        }
    }

    switch (this.score) {
        case 500:
        case 1000:
            if (this.maxColors < this.maxColorsLimit) {
                this.maxColors++;
            }
            break;

        case 2000:
        case 2500:
        case 3000:
        case 3500:
        case 4000:
        case 4500:
        case 5000:
            this.throwMegaBonus();
            break;
    }
}

Game.prototype.throwMegaBonus = function() {
    var alternate = false;
    var subtype = g_misc.getRandomInt(1, this.maxColors);
    for (var x = 1; x <= this.barrierWidth; x += this.normalRadius * 2) {
        var point = new Point();
        point.x = x + this.width / 2 - this.barrierWidth / 2 + this.normalRadius / 2;
        point.y = -this.normalRadius - alternate * this.normalRadius;
        this.throwBonus(point, subtype);
        alternate = !alternate;
    }
}

Game.prototype.throwBonus = function(point, subtype) {
    var i = g_sprites.length;
    g_sprites[i] = new Sprite();
    g_sprites[i].x = point.x;
    g_sprites[i].y = point.y;
    g_sprites[i].speedX = 0;
    g_sprites[i].speedY = 1.2;
    g_sprites[i].radius = this.normalRadius;
    g_sprites[i].radiusTarget = g_sprites[i].radius;
    g_sprites[i].subtype = subtype;
    g_sprites[i].type = g_enum.bonus;
}

Game.prototype.collectBonus = function() {
    var scoreIncrease = 5;
    for (var i = 1; i <= scoreIncrease; i++) {
        this.increaseScore()
    }
}

Game.prototype.getThrowPosition = function() {
    return this.width / 2 - this.barrierWidth / 2 + this.normalRadius + g_misc.getRandomInt(0, this.barrierWidth - this.normalRadius * 2);
}

Game.prototype.addSparkles = function(x, y, strong) {
    var sparklesLimit = 40;
    var currentSparkles = this.getTypeCount(g_enum.sparkle);
    var max = strong ? g_misc.getRandomInt(4, 8) : 2;
    for (var i = 1; i <= max && currentSparkles < sparklesLimit; i++) {
        this.addSparkle(x, y, strong, 250, 250, 250);
        currentSparkles++;
    }
}

Game.prototype.addSparklesBonus = function(x, y, color) {
    var sparklesLimit = 40;
    var currentSparkles = this.getTypeCount(g_enum.sparkle);
    var max = 12;
    for (var i = 1; i <= max && currentSparkles < sparklesLimit; i++) {
        this.addSparkle(x, y, true, color.r, color.g, color.b);
        currentSparkles++;
    }
}

Game.prototype.addSparkle = function(x, y, strong, r, g, b) {
    var size = 4;
    var index = g_sprites.length;
    g_sprites[index] = new Sprite();
    var s = g_sprites[index];
    s.x = x - size / 2;
    s.y = y - size / 2;
    s.width = size;
    s.height = size;
    s.speedX = g_misc.getRandomInt(-4, 4);
    s.speedY = g_misc.getRandomInt(0, -6);
    s.energy = 25;
    s.opacity = 1;
    s.active = 0;
    s.setRGB(r, g, b);
    s.type = g_enum.sparkle;
}

Game.prototype.getColorBySubtype = function(subtype) {
    var colors = new Array();
    colors[1] = new Color(71, 184, 88);
    colors[2] = new Color(222, 217, 18);
    colors[3] = new Color(40, 175, 215);
    colors[4] = new Color(200, 73, 55);
    colors[5] = new Color(166, 32, 223);
    colors[6] = new Color(90, 90, 90);
    colors[7] = new Color(253, 53, 168);
    return colors[subtype];
}


/**** Color follows ****/

function Color(r, g, b) {
    this.r = r;
    this.g = g;
    this.b = b;
}

/**** Image Sprite follows ****/

function ImageSprite() {
    this.active = false;
    this.element = null;
}


/**** Key enums follow ****/

function KeyEnum() {
    this.left = 37;
    this.right = 39;
}


/**** Point follows ****/

function Point(x, y) {
    this.x = x;
    this.y = y;
}

/**** Sprite enums follow ****/

function SpriteEnum() {
    this.normalBall = 1;
    this.playerBall = 2;
    this.barrierBall = 3;
    this.cannon = 4;
    this.sparkle = 5;
    this.bonus = 6;

    this.player = 10;
}


/**** Sprites follow ****/

function Sprite() {
    this.active = true;
    this.x = 0;
    this.y = 0;
    this.width = 0;
    this.height = 0;
    this.radius = 0;
    this.speedX = 0;
    this.speedY = 0;
    this.radius = 0;
    this.colorR = 0;
    this.colorG = 0;
    this.colorB = 0;
    this.type = 0;
    this.subtype = 0;
    this.parent = 0;
    this.radiusTarget = 0;
    this.rotation = 0;
    this.energy = 400;
    this.flagged = false;
    this.opacity = 1;
}

Sprite.prototype.handleType = function() {
    switch (this.type) {
        case g_enum.normalBall:
            if (!this.active) {
                this.energy--;
                if (this.energy <= 40) {
                    if ( this.opacity > 0 ) {
                        this.opacity -= .3;
                        if (this.opacity < 0) {
                            this.opacity = 0;
                        }
                    }
                }
            }
            else {
                if (this.y > g_game.barrierTop) {
                    this.y = g_game.barrierTop;
                    this.halt();
                }
            }
            break;

        case g_enum.playerBall:
            if (this.speedX != 0 && this.speedY != 0 && this.energy > 0) {
                this.energy--;
                if (this.energy == 10) {
                    this.active = false;
                    this.radiusTarget = 2;
                }
            }
            break;

        case g_enum.sparkle:
            if (--this.energy > 0) {
                this.opacity -= .02; // .1
                if (this.opacity < 0) {
                    this.opacity = 0;
                }
                this.speedY += .1;
            }
            break;

        case g_enum.bonus:
            if (this.y - this.radius > g_game.height) {
                this.energy = 0;
                this.active = 0;
            }
    }
}

Sprite.prototype.setRGB = function(r, g, b) {
    this.colorR = r;
    this.colorG = g;
    this.colorB = b;
}

Sprite.prototype.setSubtypeRGB = function() {
    var color = g_game.getColorBySubtype(this.subtype);
    this.colorR = color.r;
    this.colorG = color.g;
    this.colorB = color.b;
}

Sprite.prototype.setRGBRandom = function() {
    this.colorR = g_misc.getRandomInt(0, 255);
    this.colorG = g_misc.getRandomInt(0, 255);
    this.colorB = g_misc.getRandomInt(0, 255);
}

Sprite.prototype.resize = function() {
    var resizeSpeed = 1;
    if (this.radius < this.radiusTarget) {
        this.radius += resizeSpeed;
        if (this.radius > this.radiusTarget) {
            this.radius = this.radiusTarget;
        }
    }
    else if (this.radius > this.radiusTarget) {
        this.radius -= resizeSpeed;
        if (this.radius < this.radiusTarget) {
            this.radius = this.radiusTarget;
        }
    }
}

Sprite.prototype.move = function() {
    this.x += this.speedX;
    this.y += this.speedY;
}

Sprite.prototype.draw = function(ctx) {
    switch (this.type) {
        case g_enum.normalBall:
        case g_enum.playerBall:

            if (this.type == g_enum.playerBall) {
                // glow
                ctx.beginPath();
                ctx.fillStyle = 'rgba(255,255,255,.4)';
                ctx.arc(this.x, this.y, this.radius * 1.5, 0, g_game.fullCircle, true);
                ctx.fill();
                ctx.closePath();
            }

            if (g_game.fullRender) {

                // base ball
    
                ctx.beginPath();
                ctx.fillStyle = 'rgba(' + this.colorR + ',' + this.colorG + ',' + this.colorB + ',' + this.opacity + ')';
                ctx.arc(this.x, this.y, this.radius, 0, g_game.fullCircle, true);
                ctx.fill();
                ctx.closePath();
    
                // lolight
                ctx.beginPath();
                var light = -20;
                ctx.arc(this.x, this.y, this.radius / 1.5, 0, g_game.fullCircle, true);
                ctx.fillStyle = 'rgba(' + g_misc.forceColor(this.colorR + light) + ',' + g_misc.forceColor(this.colorG + light) + ',' +
                        g_misc.forceColor(this.colorB + light) + ', ' + this.opacity + ')';
                ctx.fill();
                ctx.closePath();
    
                // highlight
                ctx.beginPath();
                light = 40;
                ctx.arc(this.x - this.radius / 3, this.y - this.radius / 3, this.radius / 3, 0, g_game.fullCircle, true);
                ctx.fillStyle = 'rgba(' + g_misc.forceColor(this.colorR + light) + ',' +
                        g_misc.forceColor(this.colorG + light) + ',' + g_misc.forceColor(this.colorB + light) + ',' + this.opacity + ')';
                ctx.fill();
                ctx.closePath();

            }
            else if (this.opacity >= g_game.minOpacityForDisplay) {
                var s = g_game.getFreeImageSpriteElement();
                s.src = 'image/ball-' + this.subtype + '.png';
                s.style.left = (this.x - this.radius) + 'px';
                s.style.top = (this.y - this.radius) + 'px';
                s.style.width = (this.radius * 2) + 'px';
                s.style.height = (this.radius * 2) + 'px';
                s.style.display = 'block';
            }

            break;

        case g_enum.cannon:

            ctx.beginPath();

            ctx.strokeStyle = 'rgba(' + this.colorR + ',' + this.colorG + ',' + this.colorB + ', 0.7)';
            ctx.lineWidth = 8;
            ctx.lineCap = 'round';

            ctx.moveTo(this.x, this.y);

            var toX = g_misc.forceMin(this.x + g_game.cursorX, 1) / 2;
            var toY = g_misc.forceMin(this.y + g_game.cursorY, 1) / 2;

            ctx.lineTo( parseInt(toX), parseInt(toY) );

            ctx.stroke();
            ctx.closePath();

            break;

        case g_enum.bonus:
            if (g_game.fullRender) {
                var image = g_game.getImage('image/bonus-' + this.subtype + '.png');
                ctx.drawImage(image, this.x - this.radius, this.y - this.radius);
            }
            else if (this.opacity >= g_game.minOpacityForDisplay) {
                var s = g_game.getFreeImageSpriteElement();
                s.src = 'image/bonus-' + this.subtype + '.png';
                s.style.left = (this.x - this.radius) + 'px';
                s.style.top = (this.y - this.radius) + 'px';
                s.style.width = (this.radius * 2) + 'px';
                s.style.height = (this.radius * 2) + 'px';
                s.style.display = 'block';
            }
            break;

        case g_enum.sparkle:
            if (g_game.fullRender) {
                ctx.fillStyle = 'rgba(' + this.colorR + ',' + this.colorG + ',' + this.colorB +  ',' + this.opacity + ')';
                ctx.fillRect(this.x, this.y, this.width, this.height);
            }
            else if (this.opacity >= g_game.minOpacityForDisplay) {
                var s = g_game.getFreeImageSpriteElement();
                s.src = 'image/sparkle.png';
                s.style.left = this.x + 'px';
                s.style.top = this.y + 'px';
                s.style.width = this.width + 'px';
                s.style.height = this.height + 'px';
                s.style.display = 'block';
            }

            break;

    }
}

Sprite.prototype.handleCollision = function(other) {
    if (this.active && this.speedX != 0 || this.speedY != 0) {

        if (this.type == g_enum.normalBall && other.type == g_enum.normalBall) {
            if ( other.speedY == 0 && this.doesCollide(other) ) {
                this.halt();
                this.y -= this.speedY;
                other.halt();
                if (this.y <= 0) {
                    g_game.end();
                }
            }
        }
        else if (this.type == g_enum.normalBall && other.type == g_enum.barrierBall) {
            if ( this.doesCollide(other) ) {
                this.halt();
                other.halt();
                if (this.y <= 0) {
                    g_game.end();
                }
            }
        }
        else if ( (this.type == g_enum.playerBall && other.type == g_enum.barrierBall) ) {
            if ( this.doesCollide(other) ) {
                this.speedY *= -1;
                this.y += this.speedY;
                if ( this.speedY > 0 && Math.abs(this.speedX) < 4 ) {
                    this.speedX *= 3;
                }
                g_game.addSparkles(this.x, this.y, false);
            }
        }
        else if ( (this.type == g_enum.playerBall && other.type == g_enum.bonus) ) {
            if ( this.doesCollide(other) ) {
                this.energy = 0;
                other.energy = 0;
                other.active = false;
                g_game.collectBonus();
                g_game.addSparklesBonus( this.x, this.y, g_game.getColorBySubtype(other.subtype) );
            }
        }
        else if ( (this.type == g_enum.playerBall && other.type == g_enum.normalBall) ) {
            if ( this.y + this.radius < g_game.barrierTop && this.doesCollide(other) ) {

                g_game.addSparkles(this.x, this.y, false);
                g_game.flagChained(this);
                var flagCount = g_game.getFlagCount();
                if (flagCount >= g_game.chainreactAt) {
                    g_game.explodeFlagged(this.speedX, this.speedY);
                    if (flagCount >= g_game.bonusAt) {
                        g_game.throwBonus( g_game.getAverageFlaggedPoint(), this.subtype );
                    }
                }
                else {
                    if (other.speedY != 0) {
                        this.speedY = other.speedY;
                        this.speedX = 0;
                        this.type = g_enum.normalBall;
                    }
                    else {
                        this.halt();
                        this.type = g_enum.normalBall;
                    }
                }

            }
        }
    }
}

Game.prototype.getAverageFlaggedPoint = function() {
    var flagCount = 0;
    var averageX = 0;
    var averageY = 0;
    for (var i in g_sprites) {
        if (g_sprites[i].flagged) {
            flagCount++;
            averageX += g_sprites[i].x;
            averageY += g_sprites[i].y;
        }
    }
    averageX /= flagCount;
    averageY /= flagCount;

    return new Point(averageX, averageY);
}

Sprite.prototype.doesCollide = function(other, thisPadding) {
    var does = false;
    var padding = thisPadding ? thisPadding : 0;
    if (this.active && other.active) {
        var distance = Math.abs(this.x - other.x) + Math.abs(this.y - other.y);
        var does = distance <= this.radius * 2 + padding
    }
    return does;
}

Sprite.prototype.halt = function() {
    this.speedX = 0;
    this.speedY = 0;
}

Sprite.prototype.checkLimits = function() {
    var padding = 8;
    var didHitLimit = false;

    if ( (this.speedX < 0 && this.x - this.radius <= 0 + padding) ||
            (this.speedX > 0 && this.x + this.radius >= g_game.width - padding) ) {
        this.speedX *= -1;
        didHitLimit = true;
    }

    if ( (this.speedY < 0 && this.y - this.radius <= 0 + padding) ||
            (this.speedY > 0 && this.y + this.radius >= g_game.height - padding) ) {
        this.speedY *= -1;
        didHitLimit = true;
    }

    return didHitLimit;
}


/**** Misc. functions ****/

function MiscLibrary() {
}

MiscLibrary.prototype.getRandomInt = function(min, max) {
    return Math.floor( ( (max + 1 - min) * Math.random() ) + min );
}

MiscLibrary.prototype.showLayer = function(id) {
    var elm = document.getElementById(id);
    if (elm) {
        elm.style.display = 'block';
    }
}

MiscLibrary.prototype.hideLayer = function(id) {
    var elm = document.getElementById(id);
    if (elm) {
        elm.style.display = 'none';
    }
}

MiscLibrary.prototype.debug = function(s) {
    this.writeInLayer('debug', 'Debug = ' + s);
}

MiscLibrary.prototype.writeInLayer = function(id, html) {
    var elm = document.getElementById(id);
    if (elm) {
        elm.innerHTML = html;
    }
}

MiscLibrary.prototype.forceColor = function(v) {
    return this.forceMinMax(v, 0, 255);
}

MiscLibrary.prototype.forceMinMax = function(v, min, max) {
    if (v < min) {
        v = min;
    }
    else if (v > max) {
        v = max;
    }
    return v;
}

MiscLibrary.prototype.forceMin = function(v, min) {
    if (v < min) {
        v = min;
    }
    return v;
}