/**
* MooTris
* An implementation of the popular computer game 'Tetris' using the
* MooTools JavaScript framework.
*
* This file consists of the Shape class.
*
* @author  Alan Shaw
* @link    http://www.freestyle-developments.co.uk
* @version 1.0
* @package uk.co.fsd.Tetris
*/

/**
* Represents an arrangement of blocks until they are locked into the grid.
*
* This class is responsible for creating uk.co.fsd.Block's and moving them
* around the grid.
*
* @package uk.co.fsd.Tetris
*/
var Shape = new Class({

    state: null,
    blocks: null,
    options: {},
    
    /**
    * Sets up a new shape, by creating a number of blocks and arranging them
    * according to the type of shape needed
    * @param integer type type of the shape (0-6)
    * @param array state current state of the tetris board
    * @param object options the game options
    */
    initialize: function(type, state, options) {

        this.state = state;
        this.blocks = new Array();
        this.options = options;

        var middle = Math.floor(options.width / 2);
        
        switch(type) {
            case 0:
            default:
                // [0][1][2][3]
                this.blocks[0] = new Block(type, middle - 2, 0, options);
                this.blocks[1] = new Block(type, middle - 1, 0, options);
                this.blocks[2] = new Block(type, middle, 0, options);
                this.blocks[3] = new Block(type, middle + 1, 0, options);
            break;
            case 1:
                // [0]
                // [1][2][3]
                this.blocks[0] = new Block(type, middle - 1, 0, options);
                this.blocks[1] = new Block(type, middle - 1, 1, options);
                this.blocks[2] = new Block(type, middle, 1, options);
                this.blocks[3] = new Block(type, middle + 1, 1, options);
            break;
            case 2:
                //       [0]
                // [1][2][3]
                this.blocks[0] = new Block(type, middle+1, 0, options);
                this.blocks[1] = new Block(type, middle-1, 1, options);
                this.blocks[2] = new Block(type, middle, 1, options);
                this.blocks[3] = new Block(type, middle+1, 1, options);
            break;
            case 3:
                // [0][1]
                // [2][3]
                this.blocks[0] = new Block(type, middle-1, 0, options);
                this.blocks[1] = new Block(type, middle, 0, options);
                this.blocks[2] = new Block(type, middle-1, 1, options);
                this.blocks[3] = new Block(type, middle, 1, options);
            break;
            case 4:
                //    [0][1]
                // [3][2]
                // This one different, since we rotate around block 2
                this.blocks[0] = new Block(type, middle, 0, options);
                this.blocks[1] = new Block(type, middle+1, 0, options);
                this.blocks[2] = new Block(type, middle, 1, options);
                this.blocks[3] = new Block(type, middle-1, 1, options);
            break;
            case 5:
                //    [0]
                // [1][2][3]
                this.blocks[0] = new Block(type, middle, 0, options);
                this.blocks[1] = new Block(type, middle-1, 1, options);
                this.blocks[2] = new Block(type, middle, 1, options);
                this.blocks[3] = new Block(type, middle+1, 1, options);
            break;
            case 6:
                // [0][1]
                //    [2][3]
                this.blocks[0] = new Block(type, middle-1, 0, options);
                this.blocks[1] = new Block(type, middle, 0, options);
                this.blocks[2] = new Block(type, middle, 1, options);
                this.blocks[3] = new Block(type, middle+1, 1, options);
            break;
        }
    },
    
    /**
    * Determines whether or not a point on the grid is allowed to be occupied.
    * @param object point coordinates of the point e.g. <code>{x:5,y:6}</code>
    */
    isValidPosition: function(point) {

        // check if coords are in bounds of grid
        if(point.x > this.options.width -1 ||
           point.x < 0                     ||
           point.y > this.options.height - 1) {

            return false;

        } else {

            // check nothing exists in this space
            if(this.state[point.x + (point.y * this.options.width)] != null) {
                return false;
            }
        }

        return true;
    },
    
    /**
    * Determines if it is possible to rotate the shape by the given radians, 
    * about the given rotation point.
    * @param float angle angle in radians to rotate by
    * @param object rotationPoint coordinates to rotate about
    */
    tryRotate: function(angle, rotationPoint) {
    
        var blockPoint;
        var rotatedPoint;
        
        var sinus = Math.sin(angle);
        var cosinus = Math.cos(angle);

        for(var i = 0; i < this.blocks.length; i++) {
        
            // copy the blocks point as we'll not actually be doing the rotation
            blockPoint = {x: this.blocks[i].x, y: this.blocks[i].y};

            // we want to rotate about rotationPoint, so subtract from our
            // current position, move about origin, and move back
            blockPoint.x -= rotationPoint.x;
            blockPoint.y -= rotationPoint.y;

            // apply the transformation
            rotatedPoint = {
                x: Math.round(blockPoint.x*cosinus - blockPoint.y*sinus),
                y: Math.round(blockPoint.y*cosinus + blockPoint.x*sinus)
            }
            
            // move the rotated point back
            rotatedPoint.x += rotationPoint.x;
            rotatedPoint.y += rotationPoint.y;
            
            // now has this broken anything?
            if(this.isValidPosition(rotatedPoint) == false) {
                return false;
            }
        }
        
        return true;
    },
    
    /**
    * Rotates the shape by the given radians about the given rotation point.
    * @param float angle angle in radians to rotate by
    * @param object rotationPoint coordinates to rotate about
    */
    rotate: function(angle, rotationPoint) {

        var blockPoint;

        var sinus = Math.sin(angle);
        var cosinus = Math.cos(angle);

        this.blocks.each(function(block) {

            // We want to rotate about rotationPoint, so subtract from our
            // current position, move about origin, and move back
            block.x -= rotationPoint.x;
            block.y -= rotationPoint.y;
            
            // We need to use the original coords in the next calculation
            blockPoint = {x: block.x, y: block.y};

            block.x = Math.round(blockPoint.x*cosinus - blockPoint.y*sinus);
            block.y = Math.round(blockPoint.y*cosinus + blockPoint.x*sinus);
            
            block.x += rotationPoint.x;
            block.y += rotationPoint.y;

        }, this);
        
        return true;
    },
    
    /**
    * Determines if it is possible to translate the shape by the given offset
    * coordinates
    * @param object offset offset coordinates relative the the blocks coordinates
    */
    tryMove: function(offset) {
    
        var point; // the new point
        
        // move each block right
        for(var i = 0; i < this.blocks.length; i++) {

            // calculate new point
            point = {
                x: this.blocks[i].x + offset.x,
                y: this.blocks[i].y + offset.y
            }

            if(this.isValidPosition(point) == false) {
                return false;
            }
        }
        
        return true;
    },
    
    /**
    * Moves the shape by the offset coordinates.
    * @param object offset offset coordinates relative the the blocks coordinates
    */
    move: function(offset) {
        
        // move each block
        this.blocks.each(function(block) {
            block.x = block.x + offset.x;
            block.y = block.y + offset.y;
        }, this);

        return true;
    },
    
    /**
    * Helper function to move the shape one place to the right.
    */
    moveRight: function() {
        return (this.tryMove({x:1,y:0})) ? this.move({x:1,y:0}) : false;
    },
    
    /**
    * Helper function to move the shape one place down.
    */
    moveDown: function() {
        return (this.tryMove({x:0,y:1})) ? this.move({x:0,y:1}) : false;
    },
    
    /**
    * Helper function to move the shape one place to the left.
    */
    moveLeft: function() {
        return (this.tryMove({x:-1,y:0})) ? this.move({x:-1,y:0}) : false;
    },
    
    /**
    * Helper function to move the shape 90 degrees clockwise.
    */
    rotateClockwise: function() {

        var angle = (90 * Math.PI/180);
        var rotationPoint = {x: this.blocks[2].x, y: this.blocks[2].y};
        
        return (this.tryRotate(angle, rotationPoint)) ? this.rotate(angle, rotationPoint) : false;
    },
    
    /**
    * Helper function to move the shape 90 degrees anticlockwise.
    */
    rotateAntiClockwise: function() {

        var angle = -(90 * Math.PI/180);
        var rotationPoint = {x: this.blocks[2].x, y: this.blocks[2].y};
        
        return (this.tryRotate(angle, rotationPoint)) ? this.rotate(angle, rotationPoint) : false;
    }
});

Shape.implement(new Options);
