// This file is part of www.nand2tetris.org
// and the book "The Elements of Computing Systems"
// by Nisan and Schocken, MIT Press.
// File name: projects/9/Pong/Ball.jack
/**
 * A graphical ball in a Pong game. Characterized by a screen location and 
 * distance of last destination. Has methods for drawing, erasing and moving
 * on the screen. The ball is displayed as a filled, 6-by-6 pixles rectangle. 
 */
class Ball {

    field int x, y;               // the ball's screen location (in pixels)
    field int lengthx, lengthy;   // distance of last destination (in pixels)

    field int d, straightD, diagonalD;   // used for straight line movement computation
    field boolean invert, positivex, positivey;   // (same)
   
    field int leftWall, rightWall, topWall, bottomWall;  // wall locations
   
    field int wall;   // last wall that the ball was bounced off of

    /** Constructs a new ball with the given initial location and wall locations. */
    constructor Ball new(int Ax, int Ay,
                         int AleftWall, int ArightWall, int AtopWall, int AbottomWall) {    	
	    let x = Ax;		
	    let y = Ay;
	    let leftWall = AleftWall;
	    let rightWall = ArightWall - 6;    // -6 for ball size
	    let topWall = AtopWall; 
	    let bottomWall = AbottomWall - 6;  // -6 for ball size
	    let wall = 0;
        do show();
        return this;
    }

    /** Deallocates the Ball's memory. */
    method void dispose() {
        do Memory.deAlloc(this);
        return;
    }

    /** Shows the ball. */
    method void show() {
        do Screen.setColor(true);
        do draw();
        return;
    }

    /** Hides the ball. */
    method void hide() {
        do Screen.setColor(false);
	    do draw();
        return;
    }

    /** Draws the ball. */
    method void draw() {
	    do Screen.drawRectangle(x, y, x + 5, y + 5);
	    return;
    }

    /** Returns the ball's left edge. */
    method int getLeft() {
        return x;
    }

    /** Returns the ball's right edge. */
    method int getRight() {
        return x + 5;
    }

    /** Computes and sets the ball's destination. */
    method void setDestination(int destx, int desty) {
        var int dx, dy, temp;
  	    let lengthx = destx - x;
	    let lengthy = desty - y;
        let dx = Math.abs(lengthx);
        let dy = Math.abs(lengthy);
        let invert = (dx < dy);

        if (invert) {
            let temp = dx; // swap dx, dy
            let dx = dy;
            let dy = temp;
   	        let positivex = (y < desty);
            let positivey = (x < destx);
        }
        else {
	        let positivex = (x < destx);
            let positivey = (y < desty);
        }

        let d = (2 * dy) - dx;
        let straightD = 2 * dy;
        let diagonalD = 2 * (dy - dx);

	    return;
    }

    /**
     * Moves the ball one step towards its destination.
     * If the ball has reached a wall, returns 0.
     * Else, returns a value according to the wall:
     * 1 (left wall), 2 (right wall), 3 (top wall), 4 (bottom wall).
     */
    method int move() {

	    do hide();

        if (d < 0) { let d = d + straightD; }
        else {
            let d = d + diagonalD;

            if (positivey) {
                if (invert) { let x = x + 4; }
                else { let y = y + 4; }
            }
            else {
                if (invert) { let x = x - 4; }
                else { let y = y - 4; }
            }
	    }

        if (positivex) {
            if (invert) { let y = y + 4; }
            else { let x = x + 4; }
	    }
	    else {
            if (invert) { let y = y - 4; }
            else { let x = x - 4; }
	    }

	    if (~(x > leftWall)) {
	        let wall = 1;    
	        let x = leftWall;
	    }
        if (~(x < rightWall)) {
	        let wall = 2;    
	        let x = rightWall;
	    }
        if (~(y > topWall)) {
            let wall = 3;    
	        let y = topWall;
        }
        if (~(y < bottomWall)) {
            let wall = 4;    
	        let y = bottomWall;
        }

	    do show();

	    return wall;
    }

    /**
     * Bounces off the current wall: sets the new destination
     * of the ball according to the ball's angle and the given
     * bouncing direction (-1/0/1=left/center/right or up/center/down).
     */
    method void bounce(int bouncingDirection) {
        var int newx, newy, divLengthx, divLengthy, factor;

	    // Since results are too big, divides by 10
        let divLengthx = lengthx / 10;
        let divLengthy = lengthy / 10;
	    if (bouncingDirection = 0) { let factor = 10; }
	    else {
	        if (((~(lengthx < 0)) & (bouncingDirection = 1)) | ((lengthx < 0) & (bouncingDirection = (-1)))) {
                let factor = 20; // bounce direction is in ball direction
            }
	        else { let factor = 5; } // bounce direction is against ball direction
	    }

	    if (wall = 1) {
	        let newx = 506;
	        let newy = (divLengthy * (-50)) / divLengthx;
            let newy = y + (newy * factor);
	    }
        else {
            if (wall = 2) {
                let newx = 0;
                let newy = (divLengthy * 50) / divLengthx;
                let newy = y + (newy * factor);
	        }
	        else {
                if (wall = 3) {
		            let newy = 250;
		            let newx = (divLengthx * (-25)) / divLengthy;
                    let newx = x + (newx * factor);
		        }
                else { // assumes wall = 4
		            let newy = 0;
		            let newx = (divLengthx * 25) / divLengthy;
                    let newx = x + (newx * factor);
		        }
            }
        }

        do setDestination(newx, newy);
        return;
    }
}
