function createcoord() {
    this.x = 0;
    this.y = 0;
    this.z = 0;
};

function GAME(size) {
    this.game_size = size;
    this.cube_count = size * size * size;
    this.filled_cubes = 0;
    this.cube_array = {};

    // Initialize the array .

    for (var i = 0 ; i < this.cube_count; i++) {
        this.cube_array[i] = 0;
    }

    this.gravity = new threeD_vector();
    this.coord = new createcoord();

    this.removal_queue = [];
    this.premerges = [];
    this.five_one_two_anims = {};
	
	this.add_random_cube( 2 );
	this.shift_cubes();

	this.showing_view = false;
};

GAME.prototype.create_random_number = function (limit) {
    return Math.floor(Math.random() * 100) % limit;
};

GAME.prototype.index_to_ijk = function ( rand )
{
    coord = new createcoord();
    coord.z = Math.floor(rand / (this.game_size * this.game_size)); //  z level
    rand = rand % (this.game_size * this.game_size); // remaining in level

    coord.y = Math.floor(rand / this.game_size);
    coord.x = rand % this.game_size;
    return coord;
};

GAME.prototype.get_coord = function (rand, coord) {
    var coord = this.index_to_ijk(rand);
    coord.x = Math.floor(coord.x - Math.floor(this.game_size / 2));
    coord.y = Math.floor(coord.y - Math.floor(this.game_size / 2));
    coord.z = Math.floor(coord.z - Math.floor(this.game_size / 2));

    coord.x *= (100 / this.game_size);
    coord.y *= (100/ this.game_size);
    coord.z *= (100 / this.game_size);

    return coord;
};

GAME.prototype.coord_to_index = function( i, j, k ){
    var numb  = 0;
    numb += this.game_size * this.game_size * k;
    numb += j * this.game_size;
    numb += i;

    return numb;
};

GAME.prototype.add_random_cube = function ( count ) {

    if (count == 0)
    {
        while (this.premerges.length)
            this.premerges.pop();

        return;
    }
        
    if( this.filled_cubes == this.cube_count )
        return;

    var rand = this.create_random_number(this.cube_count);

    while (this.cube_array[rand] != 0 ) {
        rand++;
        if (rand == this.cube_count)
            rand = 0;
    }

    this.coord = this.get_coord( rand );
    
    var cube = create_inner_cube(33);
    cube.position.set(this.coord.x, this.coord.y, this.coord.z);
    cube.scale = { x: 0.0, y: 0.0, z: 0.0 };
    var scaling = 1.0;
    new TWEEN.Tween(cube.scale).to({ x: scaling, y: scaling, z: scaling }, 300).delay(400).start();
    new TWEEN.Tween(cube.material).to({ opacity:1.0},300).delay(400).start();
    cube_group.add(cube);
    this.cube_array[rand] = cube;

    this.filled_cubes++;
    var ijk = this.index_to_ijk(rand);
    this.sift_cube( ijk.x, ijk.y, ijk.z, this.gravity.direction );
    this.add_random_cube(--count);
};

function get_map_id( map )
{
    var underscore_idx = map.sourceFile.indexOf("_");
    var dot_idx = map.sourceFile.lastIndexOf(".");
    var index = map.sourceFile.substring(++underscore_idx, dot_idx);
    return parseInt(index);
}

function next_map(map) {
    var index = get_map_id( map );
    return index + index;
};

GAME.prototype.is_in_bound = function( i2, j2 , k2 ){
    if( i2 < 0 || i2 >= this.game_size)
        return false;

    if( j2 < 0 || j2 >= this.game_size)
        return false;

    if( k2 < 0 || k2 >= this.game_size)
        return false;

    return true;
};

GAME.prototype.get_512_and_above_count = function () {
    var count = 0;

    for (var i = 0; i < this.cube_count; i++) {
        if( this.cube_array[i] != 0 )
            if (get_map_id(this.cube_array[i].material.map) >= 512)
                count++;
    }

    return count;
};

GAME.prototype.do_texture_events = function (index, index2, i2, j2, k2) {
    var next_texture = this.cube_array[index].material.map;
    
    // Win!
    if (next_texture == textures[2048]) {
        TWEEN.removeAll();
        this.view_sides( true );            
        return true;
    }
    // Almost there!
    else if (next_texture == textures[512]) {

        var go = new TWEEN.Tween(this.cube_array[index].scale)
                        .to({ x: 1.2, y: 1.2, z: 1.2 }, 500)
                        .easing(TWEEN.Easing.Sinusoidal.In)
                        .start();

        var back = new TWEEN.Tween(this.cube_array[index].scale)
                        .to({ x: 1.0, y: 1.0, z: 1.0 }, 500)
                        .easing(TWEEN.Easing.Sinusoidal.In);

        go.chain(back);
        back.chain(go);

        // Record these for future..

        this.five_one_two_anims[ this.cube_array[index].uuid ] = go;
    }

    return false;
};

GAME.prototype.merge_cubes = function(last_index)
{
    // Remove from 512 array as well

    if (this.five_one_two_anims[this.cube_array[last_index].uuid ] != undefined) {
        this.five_one_two_anims[this.cube_array[last_index].uuid].stop();
    }

    // Cube to be removed expands a little.
    this.removal_queue.push(this.cube_array[last_index]);
    this.cube_array[last_index].transparent = false;
    this.cube_array[last_index] = 0;
    this.filled_cubes--;


    var length = 200;
    var tween = new TWEEN.Tween(this.removal_queue[0].scale).
                    to({ x: 1.3, y: 1.3, z: 1.3 }, length).
                    easing(TWEEN.Easing.Sinusoidal.In)
                    .start();

    var tween2 = new TWEEN.Tween(this.removal_queue[0].scale).
                    to({ x: 1.0, y: 1.0, z: 1.0 }, length/2).
                    easing(TWEEN.Easing.Sinusoidal.In)
                    .onComplete(function () {                       

                        // Remove from removal queue of settles..

                        var removal = CUBE2048.removal_queue.shift();
                        cube_group.remove(removal);
                        delete removal;                        
                    });

    tween.chain(tween2);
};

GAME.prototype.is_premerged = function ( cube ) {

    for (var i = 0; i < this.premerges.length ; i++) {
        if (this.premerges[i] == cube)
            return true;
    }

    return false;
};


var counter = 0;
GAME.prototype.sift_cube = function ( i, j, k, direction ) {

    var start_index = this.coord_to_index(i, j, k)

    this.coord = this.get_coord( start_index );

    // No cube at this location.
    if (this.cube_array[start_index] == 0)
        return false;

    var i2 = i + direction.x;
    var j2 = j + direction.y;
    var k2 = k + direction.z;
    var last_index = this.coord_to_index(i2, j2, k2);

    while( this.is_in_bound( i2, j2, k2) && (this.cube_array[last_index] == 0) )
    {
        i2 += direction.x;
        j2 += direction.y;
        k2 += direction.z;

        last_index = this.coord_to_index(i2, j2, k2);
    }    

    // Either we are out of bound or we are filled..

    if( this.is_in_bound( i2, j2, k2) )
    {
        // We found an existing cube. Is it the same texture?
        // If this was from a previous merge.skip..

        if (    !this.is_premerged(this.cube_array[last_index])
            && (this.cube_array[start_index] != 0)
            && (this.cube_array[last_index] != 0)
            && (this.cube_array[start_index].material.map == this.cube_array[last_index].material.map))
        {
            this.merge_cubes( last_index );
            var next_text = next_map( this.cube_array[start_index].material.map );
            //console.log("Merging " + start_index + " with " + last_index + " New texture : " + next_text);

            this.cube_array[start_index].material.map = textures[next_text];

            // Dont merge on this again.
            this.premerges.push(this.cube_array[start_index]);

            if ( this.do_texture_events(start_index, last_index, i2, j2, k2) )
                return true;
        }
        else{
            // Move to the next free block.
            i2 -= direction.x;
            j2 -= direction.y;
            k2 -= direction.z;
        }
    }
    else{
        // Move to the next free block.
        i2 -= direction.x;
        j2 -= direction.y;
        k2 -= direction.z;
    }

    start_index = this.coord_to_index( i, j, k );
    last_index = this.coord_to_index( i2, j2, k2 );
        

    if (start_index == last_index)
        return false;

    var start_coord = new createcoord();
    var last_coord = new createcoord();

    var start_coord = this.get_coord( start_index );
    var last_coord = this.get_coord( last_index );

    //console.log("Final coord : \t" + last_coord.x + "\t " + last_coord.y+ "\t " + last_coord.z);

    this.settled = false;
    new TWEEN.Tween(this.cube_array[start_index].position)
        .to({ x: last_coord.x, y: last_coord.y, z: last_coord.z }, 600)
        .easing(TWEEN.Easing.Bounce.Out)
        .onComplete( function (){ CUBE2048.settled = true;} )
        .start();

    this.cube_array[last_index] = this.cube_array[start_index];
    this.cube_array[start_index] = 0;

    return true;
};

GAME.prototype.shift_cubes = function () {
    var shifted = false;
    if (this.gravity.direction.x != 0) {
        var start = this.gravity.direction.x > 0 ? this.game_size-1 : 0;
        var end = this.gravity.direction.x > 0 ? -1 : this.game_size;

        start -= this.gravity.direction.x;

        for (var i = start; i != end ; i -= this.gravity.direction.x) {
            for (var j = 0 ; j < this.game_size; j++) {
                for (var k = 0; k < this.game_size; k++) {
                    shifted |= this.sift_cube(i, j, k, this.gravity.direction);                   
                }
            }
        }
    }

    else if (this.gravity.direction.y != 0)
    {
        var start = this.gravity.direction.y > 0 ? this.game_size-1 : 0;
        var end = this.gravity.direction.y > 0 ? -1 : this.game_size;
        start -= this.gravity.direction.y;

        for (var j = start; j != end ; j -= this.gravity.direction.y) {
            for (var i = 0 ; i < this.game_size; i++) {
                for (var k = 0; k < this.game_size; k++) {
                    shifted |= this.sift_cube(i, j, k, this.gravity.direction);
                }
            }
        }

    }

    else if (this.gravity.direction.z != 0) {
        var start = this.gravity.direction.z > 0 ? this.game_size-1 : 0;
        var end = this.gravity.direction.z > 0 ? -1 : this.game_size;
        start -= this.gravity.direction.z;
        for (var k = start; k != end ; k -= this.gravity.direction.z) {
            for (var i = 0 ; i < this.game_size; i++) {
                for (var j = 0; j < this.game_size; j++) {
                    shifted |= this.sift_cube(i, j, k, this.gravity.direction);
                }
            }
        }
    }
    
    return shifted;
};

GAME.prototype.translate = function (i, j, k, direction) {
    var start_index = this.coord_to_index(i, j, k);
    var coord =  this.get_coord(start_index, coord);
    if (this.cube_array[start_index] != 0) {
        new TWEEN.Tween(this.cube_array[start_index].position)
        .to({
            x: coord.x + (121 * direction.x),
            y: coord.y + (121 * direction.y),
            z: coord.z + (121 * direction.z)
        }, 100)
        .start();
    }
};

GAME.prototype.view_sides = function ( is_win ) {

    if (TWEEN.getAll().length > this.get_512_and_above_count())
        return;

    if (this.showing_view)
        return;

    var right = this.gravity.right();
    var left = new THREE.Vector3(0, 0, 0);
    left.copy( right );
    left.negate();

    var dirs = [];
    dirs.push( left );
    dirs.push( new THREE.Vector3(0, 0, 0) );
    dirs.push( right );

    if (this.gravity.direction.x != 0) {
        var start = this.gravity.direction.x > 0 ? this.game_size - 1 : 0;
        var end = this.gravity.direction.x > 0 ? -1 : this.game_size;

        for (var i = start; i != end ; i -= this.gravity.direction.x) {
            for (var j = 0 ; j < this.game_size; j++) {
                for (var k = 0; k < this.game_size; k++) {
                    this.translate(i, j, k,  dirs[i]);
                }
            }
        }
    }

    else if (this.gravity.direction.y != 0) {
        var start = this.gravity.direction.y > 0 ? this.game_size - 1 : 0;
        var end = this.gravity.direction.y > 0 ? -1 : this.game_size;
        for (var j = start; j != end ; j -= this.gravity.direction.y) {
            for (var i = 0 ; i < this.game_size; i++) {
                for (var k = 0; k < this.game_size; k++) {
                    this.translate(i, j, k, dirs[j]);
                }
            }
        }

    }

    else if (this.gravity.direction.z != 0) {
        var start = this.gravity.direction.z > 0 ? this.game_size - 1 : 0;
        var end = this.gravity.direction.z > 0 ? -1 : this.game_size;
        for (var k = start; k != end ; k -= this.gravity.direction.z) {
            for (var i = 0 ; i < this.game_size; i++) {
                for (var j = 0; j < this.game_size; j++) {
                    this.translate(i, j, k, dirs[k]);
                }
            }
        }
    }

    if (is_win) {
        new TWEEN.Tween(camera.position)
        .to({
            x: camera.position.x,
            y: camera.position.y + 100,
            z: camera.position.z
        }, 150)
        .onUpdate(function () {
            camera.lookAt(new THREE.Vector3(50, 50, 0));
        })
        .delay( 2000 )
        .onComplete(function () {
            renderer.render(scene, camera);
            alert(" Did you just win? !!");
            release('Yes you did! You won! Yay!! :) ');
        })
        .start();
    }
    else {
        new TWEEN.Tween(camera.position)
        .to({
            x: camera.position.x,
            y: camera.position.y + 100,
            z: camera.position.z
        }, 150)
        .onUpdate(function () {
            camera.lookAt(new THREE.Vector3(50, 50, 0));
        })       
        .start();
    }
    

    this.showing_view = true;
};

GAME.prototype.reset_positions = function()
{

    if (!this.showing_view)
        return;

    for( var i = 0 ; i < this.cube_count ; i ++ )
    {
        if (this.cube_array[i] != 0) {
            var coord = this.get_coord( i );
            new TWEEN.Tween(this.cube_array[i].position)
            .to({ x: coord.x, y: coord.y, z: coord.z }, 100)
            .start();
        }
    }

    new TWEEN.Tween(camera.position)
       .to({
           x: 50,
           y: dist/5,
           z: dist/6
       },100)
       .onUpdate(function () {
           camera.lookAt(new THREE.Vector3(50, 50, 0));
       })
       .start();

    this.showing_view = false;

}