(function () {

if(typeof window.Tetris != 'undefined') {
    var _Tetris = window.Tetris;
}

var NUM_ROWS = 14, 
    NUM_COLS = 12;

var SHAPES = [
    {
        name: 'I',
        blocks: [[0, 1], [1, 1], [2, 1], [3, 1]],
        background: '33ffcc',
        score: 2
    },
    {
        name: 'O',
        blocks: [[1, 1], [1, 2], [2, 1], [2, 2]],
        background: 'ffff33',
        score: 2
    },
    {
        name: 'J',
        blocks: [[0, 1], [1, 1], [2, 1], [2, 2]],
        background: '0033ff',
        score: 3
    },
    {
        name: 'L',
        blocks: [[1, 1], [2, 1], [3, 1], [1, 2]],
        background: 'ff9933',
        score: 3
    },
    {
        name: 'T',
        blocks: [[0, 1], [1, 1], [2, 1], [1, 2]],
        background: 'ff33ff',
        score: 3
    },
    {
        name: 'S',
        blocks: [[0, 1], [1, 1], [1, 2], [2, 2]],
        background: '00ff33',
        score: 10
    },
    {
        name: 'Z',
        blocks: [[1, 1], [2, 1], [2, 2], [2, 3]],
        background: 'ff0033',
        score: 10
    }
];

function draw_grid(id_prefix, rows, columns) {
    var lines = ['<table cellspacing="0" cellpadding="0">'];
    for(var i = 0; i < rows; ++i) {
        lines.push('<tr>');
        for(var j = 0; j < columns; ++j) {
            lines.push('<td id = "' + id_prefix + j + 'x' + i + '">&nbsp;</td>');
        }
        lines.push('</tr>')
    }
    document.getElementById(id_prefix).innerHTML = lines.join('');

    // Believe it or not, IE doesn't treat the class attributes you set with
    // innerHTML to be the same as those you get with getAttribute.  So
    // we programmatically set a class on each just so IE will like us.
    for(var i = 0; i < rows; ++i) {
        for(var j = 0; j < columns; ++j) {
            var cell = cell_by_pos(j, i, id_prefix);
            cell.setAttribute('class', 'blank');
            cell.style.backgroundColor = '#000000';
        }
    }
};

function cell_by_pos(x, y, prefix) {
    prefix = prefix || 'game';
    return document.getElementById(prefix + x + 'x' + y);
};

function grid_cell(position, block, prefix) {
    var x = position[0] + block[0],
        y = position[1] + block[1];
    if(x < 0 || x >= NUM_COLS || y < 0 || y >= NUM_ROWS) {
        return false;
    }
    var grid_cell = cell_by_pos(x, y, prefix);
    return grid_cell;
};

function is_legal_position(new_blocks, new_pos) {
    for(var i = 0, len = new_blocks.length; i < len; ++i) {
        var cell = grid_cell(new_blocks[i], new_pos, 'game');
        if(cell === false) {
            return false;
        }
        if(cell.getAttribute('class') != 'blank') {
            return false;
        }
    }
    return true;
};

function Shape(index, position) {
    this.shape = SHAPES[index];
    this.rotated_blocks = this.shape.blocks.slice();
    this.position = position;
};

Shape.prototype = {

    each_block: function(fn) {
        for(var i = 0, len = this.rotated_blocks.length; i < len; ++i) {
            fn.call(this, this.rotated_blocks[i]);
        }
    },

    draw: function(where) {
        where = where || 'game';
        this.each_block(function(block) {
            var cell = grid_cell(this.position, block, where);
            cell.setAttribute('class', '');
            cell.style.backgroundColor = '#' + this.shape.background;
        });
    },

    erase: function(where) {
        where = where || 'game';
        this.each_block(function(block) { 
            var cell = grid_cell(this.position, block, where);
            cell.setAttribute('class', 'blank');
            cell.style.backgroundColor = '#000000'; 
        });
    },

    move: function(delta) {
        this.erase();
        var new_position = this.position.slice();
        new_position[0] += delta[0];
        new_position[1] += delta[1];

        var legal = is_legal_position(this.rotated_blocks, new_position);
        if(legal) {
            this.position = new_position;
        }
        this.draw();
        return legal;
    },

    rotate: function() {
        this.erase();
        var new_blocks = []
        for(var i = 0, len = this.rotated_blocks.length; i < len; ++i) {
            var block = this.rotated_blocks[i];
            new_blocks.push([3 - block[1], block[0]]);
        }
        
        var legal = is_legal_position(new_blocks, this.position);
        if(legal) {
            this.rotated_blocks = new_blocks;
        }
        this.draw();
        return legal;
    }
};

function test_draw() {
    new Shape(0, [2, 11]).draw();
    new Shape(1, [8, 11]).draw();
    new Shape(2, [2, 9]).draw();
    new Shape(3, [8, 9]).draw();
    new Shape(4, [5, 6]).draw();
    new Shape(5, [2, 2]).draw();
    new Shape(6, [7, 2]).draw();
};

function is_line_filled(row) {
    for(var i = 0; i < NUM_COLS; ++i) {
        var cell = cell_by_pos(i, row);
        if(cell.getAttribute('class') == 'blank') {
            return false;
        }
    }
    return true;
};

function fill_line_from(dest_row, src_row) {
    for(var i = 0; i < NUM_COLS; ++i) {
        var from_cell = cell_by_pos(i, src_row),
            to_cell = cell_by_pos(i, dest_row);
        to_cell.setAttribute('class', from_cell.getAttribute('class'));
        to_cell.style.backgroundColor = from_cell.style.backgroundColor;
    }
};

function clear_filled_lines() {
    var filled_lines = [];
    for(var i = NUM_ROWS - 1; i >= 0; i--) {
        if(is_line_filled(i)) {
            filled_lines.push(i);
        }
    }
    for(var i = 0, len = filled_lines.length; i < len; ++i) {
        var fill_until = (i < len - 1 ? filled_lines[i + 1] : 0) + i + 1; 
        for(var j = filled_lines[i] + i; j > fill_until; --j) {
            fill_line_from(j, j - i - 1);
        }
    }
    for(var i = 0, ilen = filled_lines.length; i < ilen; ++i) {
        for(var j = 0, jlen = NUM_COLS; j < jlen; ++j) {
            var cell = cell_by_pos(j, i);
            cell.setAttribute('class', 'blank');
            cell.style.backgroundColor = '#000000';
        }
    }
    if(filled_lines.length) {
        lines += filled_lines.length;
        score += 100 * Math.pow(2, filled_lines.length);
    }

    if(lines > level * 20) {
        level++;
        window.clearInterval(loop_timer);
        loop_timer = window.setInterval(game_loop, 1000 * Math.pow(0.95, level));
    }
};

var current_shape, next_shape, next_area, score_callback, loop_timer, 
    score = 0, lines = 0, level = 1;

var KEY_LEFT = 37,
    KEY_UP = 38,
    KEY_RIGHT = 39,
    KEY_DOWN = 40;

function key_dispatcher(e) {
    var capture = true;
    if(!e) e = event;

    switch(e.keyCode) {
        case KEY_UP: current_shape.rotate(); break;
        case KEY_LEFT: current_shape.move([-1, 0]); break;
        case KEY_RIGHT: current_shape.move([1, 0]); break;
        case KEY_DOWN: current_shape.move([0, 1]); break;
        default: capture = false;
    }

    if(capture) {
        if(e.preventDefault) {
            e.preventDefault();
        } else if(event.preventDefault) {
            event.preventDefault();
        }
    }
    return !capture;
}

function add_key_listener(key_handler) {
    if(document.addEventListener) {
        document.addEventListener("keydown", key_handler, false);
    } else if(document.attachEvent) {
        document.attachEvent("onkeydown", key_handler);
    } else {
        document.onkeydown = key_handler;
    }
};

function random_piece() {
    return new Shape(Math.floor(Math.random() * 7), [0, 0]);
};

function next_piece() {
    next_shape.erase(next_area);

    current_shape = next_shape;
    current_shape.position = [NUM_COLS / 2, 0];

    next_shape = random_piece();
    next_shape.draw(next_area);
};

function game_loop() {
    if(!current_shape.move([0, 1])) {
        score += current_shape.shape.score;
        clear_filled_lines();
        next_piece();

        score_callback(score, lines, level, next_shape);
        if(!is_legal_position(current_shape.rotated_blocks, current_shape.position)) {
            alert('Game over.  Hit refresh to play again.');
            window.clearInterval(loop_timer);
        }
    }
};

var Tetris = window.Tetris = {

    no_conflict: function() {
        window.Tetris = _Tetris;
        return Tetris;
    },

    draw_grid: draw_grid,

    game_init: function(rows, cols, callback, next_id) {
        score_callback = callback || function() {};
        NUM_ROWS = rows;
        NUM_COLS = cols;
        next_area = next_id;

        next_shape = random_piece();
        next_piece();

        add_key_listener(key_dispatcher);
        loop_timer = window.setInterval(game_loop, 950);
    }
};

})();
