/*

Quicktetris published by Thomas Kjeldahl Nilsson under 
Creative Commons Attribution 3.0 license.
License terms: http://creativecommons.org/licenses/by/3.0/

*/

// Key codes
var DIR_KEY_DOWN = 40;
var DIR_KEY_UP = 38;
var DIR_KEY_LEFT = 37;
var DIR_KEY_RIGHT = 39;

var NUM_KEY_ZERO = 48;
var NUM_KEY_ONE = 49;
var NUM_KEY_TWO = 50;
var NUM_KEY_THREE = 51;
var NUM_KEY_FOUR = 52;
var NUM_KEY_FIVE = 53;
var NUM_KEY_SIX = 54;
var NUM_KEY_SEVEN = 55;
var NUM_KEY_EIGHT = 56;
var NUM_KEY_NINE = 57;

var SPACE_KEY = 32;

// Used for immediate reaction to keypress
function setKeyReaction(keyEventHandler) {
    document.onkeydown = function(e) {
        if (window.event) // IE
        {
            keyEventHandler(window.event.keyCode);
        }
        else if (e.which) // Netscape/Firefox/Opera
        {
            keyEventHandler(e.which);
        }
    };
}

// Used for delayed reaction to keypresses, just store keycode
var currentKeyPress = null;
function setKeyMemory() {
    document.onkeydown = function(e) {
        if (window.event) // IE
        {
            currentKeyPress = window.event.keyCode;
        }
        else if (e.which) // Netscape/Firefox/Opera
        {
            currentKeyPress = e.which;
        }
    };
    document.onkeyup = function(e) {
        currentKeyPress = null;
    };
}

// Assert methods needed by test framework
function assertTrue(boolean, errorMsg) {
    if (boolean === false) {
        throw (errorMsg);
    }
    return;
}

function assertFalse(boolean, errorMsg) {
    if (boolean === true) {
        throw (errorMsg);
    }
    return;
}




// D. Crockford idiom for clean object inheritance 
if (typeof Object.create !== 'function') {
    Object.create = function(o) {
        var F = function() {};
        F.prototype = o;
        return new F();
    };
}

// D. Crockford idiom for function mixin
Function.prototype.method = function(name, func) {
    this.prototype[name] = func;
    return this;
};

// Rect data maker
function makeRect(rectX, rectY, rectWidth, rectHeight) {
    var rLeft = rectX;
    var rRight = rectX + rectWidth;
    var rTop = rectY;
    var rBottom = rectY + rectHeight;

    var rect = {
        x: rectX,
        y: rectY,
        width: rectWidth,
        height: rectHeight,
        left: rLeft,
        right: rRight,
        top: rTop,
        bottom: rBottom
    };

    return rect;
}

// Checks if two rectangles overlap
function intersectRect(r1, r2) {
    return ! (r1.left + 1 > r2.right || r2.left + 1 > r1.right || r1.top + 1 > r2.bottom || r2.top + 1 > r1.bottom);
}

// Returns two dimensional array, every element initiated to given value
function get2dArray(width, height, initValue) {
    var arr2d = [];

    for (var x = 0; x < width; x++) { // For each row
        arr2d[x] = [];
    }

    for (x = 0; x < width; x++) {
        for (var y = 0; y < height; y++) {
            arr2d[x][y] = initValue;
        }
    }

    return arr2d;
}

// Array mixins for 2d grid functionality
Array.method('getWidth',
function() {
    return this.length;
});

Array.method('getHeight',
function() {
    return this[0].length;
});

Array.method('isTwoDimensional',
function() {
    return (this[0].constructor == Array);
});

Array.method('each',
function(appliedFunction) {
    for (var x = 0; x < this.getWidth(); x++) {
        if (this.isTwoDimensional()) {
            for (var y = 0; y < this.getHeight(); y++) {
                appliedFunction(this[x][y]);
            }
        }
        else {
            appliedFunction(this[x]);
        }
    }
});

Array.method('eachWithIndexes',
function(appliedFunction) {
    for (var x = 0; x < this.getWidth(); x++) {
        if (this.isTwoDimensional()) {
            for (var y = 0; y < this.getHeight(); y++) {
                appliedFunction(this[x][y], x, y);
            }
        }
        else {
            appliedFunction(this[x], x);
        }
    }
});

Array.method('eachRowWithIndex',
function(appliedFunction) {
    for (rowCount = 0; rowCount < this.getHeight(); rowCount++) {
        var row = [];
        for (columnCount = 0; columnCount < this.getWidth(); columnCount++) {
            row[columnCount] = this[columnCount][rowCount];
        }
        appliedFunction(row, rowCount);
    }
});

Array.method('map',
function(appliedFunction) {
    var mappedArr = null;
    if (this.isTwoDimensional()) {
        mappedArr = get2dArray(this.getWidth(), this.getHeight(), null);
    }
    else {
        mappedArr = [];
    }

    for (var x = 0; x < this.getWidth(); x++) {
        if (this.isTwoDimensional()) {
            for (var y = 0; y < this.getHeight(); y++) {
                mappedArr[x][y] = appliedFunction(this[x][y], x, y);
            }
        }
        else {
            mappedArr[x] = appliedFunction(this[x]);
        }
    }

    return mappedArr;
});