/*
 * Lab 5, Part 2 - UFO Game
 *
 * For this lab you will be working on writing a UFO game.  The
 * UFO will fall from the top of the screen, the player will control
 * a tank at the bottom of the screen attempting to shoot it, and
 * the game will end if the UFO is shot or if it lands.
 */

/*
 * First of all, we need to import some classes that are built into
 * ProfessorJ.
 */

import draw.*;

/* This includes the following:
 * 
 * The class Posn, an (x,y) location.
 *
 * The class World, which handles things like drawing, events,
 * etc.  You'll create a subclass of World later in this lab.
 *
 * The class Canvas, which handles the actual drawing of shapes on 
 * the screen.
 *
 * The Color class is an abstract class from which all the
 * various color classes (Blue, Red, etc.) are derived.
 * The color classes are created like so:
 *
 * Color c = new Blue();
 *
 * Your choices are Red, White, Green, Blue, and Black.
 *
 * You won't need to use Random directly in this lab as we'll
 * be giving you code that uses it.
 */

// We need random for the UFO's horizontal movement.
import java.util.Random;

/*
 * To get you started, we provide the UFO class and the corresponding
 * manipulation of the World and Canvas to draw the UFO. If you run
 * this lab without any modifications, you will see a UFO decending
 * from the sky.
 *
 * From here, you will need to add a Tank and a list of Shots, which
 * come from the tank and travel up toward the UFO.
 *
 * First you should create the class to represent a Tank.
 * Like UFOs, Tanks have a position in the game world,
 * and that's all the information it needs to store.
 *
 * You also need to add a move() method that takes the
 * distances to move the Tank in the x and y directions and
 * also the size of the world (to do checks in case the tank or
 * ufo is moving outside the world, in which case it should stop
 * moving in that direction). See the UFO move() method as an example.
 *
 * You should also write a same() method for each in order to test
 * the move() method (and others later) appropriately.
 */

/*
 * Now we'll start working on how to draw these onto a canvas.
 * Write a method in Tank with the following contract, similar to that
 * in UFO.
 *
 * boolean draw(World w)
 *
 * The World class has one field, theCanvas of type Canvas. The following methods
 * of class Canvas will be useful for writing this methods (you only really need
 * drawRect for the Tank):
 *
 * boolean drawDisk(Posn p, int radius, Color c)
 * boolean drawRect(Posn p, int width, int height, Color c)
 */

/*
 * We will also need shots in this game as the tank will be attempting to stop the
 * UFO from landing, so create a Shot class and a series of classes to model lists
 * of shots (ALoS, MTLoS, ConsLoS).  Each should have move(), draw(), and same()
 * methods like the UFO and Tank classes.  You may also want to write a method for
 * lists of classes that removes shots from the list once they've left the canvas,
 * but that's not necessary.
 *
 * Also write a method in the Tank class that creates a new Shot (which will be
 * useful when we make the game interactive).
 */

/*
 * Now that you can draw the UFO, Tank, and Shot(s), you should modify the UFOGameWorld
 * (which is a subclass of World) to hold all the information about our game.
 * It already contains a UFO object, you should add a Tank object and a list of shots
 * Make it so that the constructor has the following header:
 *
 * UFOGameWorld(int width, int height, UFO ufo, Tank tank, ALoS los)
 *
 * This way you can use the example given to you in the Examples class.
 *
 * You will need to modify the following methods for UFOGameWorld to include the
 * appropriate handling of your new Tank and list of Shots. The code you add will
 * be similar to that for the UFO that already exists.
 *
 * // Draws the world
 * boolean draw()
 *
 * You should use calls to the other classes' draw() methods, and you will also
 * want to use drawRect to "clear" the canvas before doing so by redrawing the
 * background.
 */

/*
 * Now you can draw the world, but it's pretty boring just sitting there, so let's
 * make it do something.  In order to handle key events (like pressing the arrow
 * keys), you'll need to modify the following method in your UFOGameWorld:
 *
 * World onKeyEvent(String ke)
 *
 * ke will be a string describing the key pressed.  For this game, you will only
 * need to know about three possible values: "left" (for pressing the left arrow
 * key), "right" (for pressing the right arrow key) and "up" (for pressing the up
 * arrow key).
 *
 * The only object that responds to key events is the tank: you want to make it
 * so that the following happens:
 *
 *  - Pressing the left arrow key moves the tank left.
 *  - Pressing the right arrow key moves the tank right.
 *  - Pressing the up arrow key makes the tank shoot.
 */

/*
 * Now we need to add a last bit of functionality so that the UFO and shots move --
 * otherwise it's a very boring and pointless game!  Modify the following method
 * in UFOGameWorld:
 *
 * World onTick()
 *
 * You should handle the shots moving upward here. This method already handles
 * the UFO moving downwards.
 *
 * If the UFO lands (i.e. gets too close to the bottom of the screen), the game
 * ends.  In order to end the game, the following method of World (and thus
 * of UFOGameWorld) is be called:
 *
 * World endOfWorld()
 *
 */

/*
 * Everything moves now, but there's still a problem -- the UFO just goes through the
 * shots!  In Shot, create a method that creates an appropriate BoundingBox
 * (which is already provided), then create a method for lists of shots which tests
 * to see if any of the shots in the list hits the UFO. UFO already contains a method
 * to create its BoundingBox.
 *
 * Once you have that written, make sure to end the game if any of the shots hit the
 * UFO during gameplay and you're done!
 */

/*
 * Everything below are the classes we're giving to you -- a class that implements
 * bounding boxes, a UFO class, a UFOGameWorld class that extend World, and an
 * Examples class with some definitions that start the game (when you make an instance
 * Examples e = new Examples();)
 */

// We use rectangles for bounding boxes -- the main thing you
// want them for is to tell when they overlap.
class BoundingBox {
  Posn loc; // Upper left of bounding box
  int width;
  int height;

  BoundingBox(Posn loc, int width, int height) {
    this.loc = loc;
    this.width = width;
    this.height = height;
  }

  // When two bounding boxes overlap, we want to return true.
  // We do this by the following:
  //
  // Check the sides of the boxes.  If the we have that there's
  // an overlap between the boxes both horizontally and vertically,
  // then we have an overlap.
  //
  // For each, we check the left-most (top-most) right (bottom) side
  // and subtract from it the right-most (bottom-most) left (top)
  // side.  Draw it out and you'll see what I mean.
  boolean intersects(BoundingBox bb) {
    return (Math.min(this.loc.x + this.width, bb.loc.x + bb.width) -
            Math.max(this.loc.x, bb.loc.x) > 0) &&
           (Math.min(this.loc.y + this.height, bb.loc.y + bb.height) -
            Math.max(this.loc.y, bb.loc.y) > 0);
  }
}

// This class represents the UFO object in the game.
class UFO {
  Posn loc; // Position of the middle of the UFO

  UFO(Posn loc) {
    this.loc = loc;
  }

  // Checks to see if the UFO is still within the bounds of the world
  // after moving and if so, returns a new one that's moved the appropriate
  // distance.  If it would move outside the left or right of the world,
  // then we only change the y.  If it would move below the world, then
  // we don't move at all.
  UFO move(int diffx, int diffy, int width, int height) {
     if((0 < (this.loc.y + diffy)) && ((this.loc.y + diffy) < height))
       if((0 < (this.loc.x + diffx)) && ((this.loc.x + diffx) < width))
         return new UFO(new Posn(this.loc.x + diffx, this.loc.y + diffy));
       else  
         return new UFO(new Posn(this.loc.x, this.loc.y + diffy));
     else return this;
  }

  // Given n, we pick (randomly) a distance to move horizontally whose absolute
  // value is <= n.  We also move the UFO down by 10 pixels.
  //
  // width and height are the dimensions of the world, which we use in move().
  UFO moveRandom(int n, int width, int height) {
    return this.move(-n + (new Random()).nextInt(2 * n), 10, width, height);
  }

  // bb() returns the bounding box for this UFO.  Since this.loc represents
  // the middle of the UFO, we need to adjust it for the bounding box, whose
  // loc is in the upper left.
  BoundingBox bb() {
    return new BoundingBox(new Posn(this.loc.x - 10, this.loc.y - 5), 20, 10);
  }

  // draw() draws the UFO to the world.  UFOs look like flying saucers --
  // they have a spherical body with a halo-like disk.  Of course, we're seeing
  // these UFOs on edge-wise so we use a circle for the body and a rectangle for
  // the disk.
  boolean draw(World w) {
    return w.theCanvas.drawDisk(this.loc, 5, new Blue())
        && w.theCanvas.drawRect(new Posn(this.loc.x - 10, this.loc.y - 2), 20, 4, new Green());
  }

  // Test and see if two UFOs are the same -- we can use this to test move().
  boolean same(UFO u) {
    return this.loc.x == u.loc.x &&
           this.loc.y == u.loc.y;
  } 
}

// Represents a world in the UFO game
class UFOGameWorld extends World {
  // The world has a width and a height (dimensions of the canvas)
  int width;
  int height;

  // Here's our major players.
  UFO  ufo;
  //Tank tank;
  //ALoS los;

  UFOGameWorld(int width, int height, UFO ufo) {
    this.width  = width;
    this.height = height;
    this.ufo    = ufo;
    //this.tank   = tank;
    //this.los    = los;
  }

  // To draw the world, we draw the players.
  boolean draw() {
    return this.theCanvas.drawRect(new Posn(0,0), this.width, this.height, new White()) &&
           this.ufo.draw(this);
  }

  // The game's over when a) a shot hits the UFO or b) the UFO lands.
  boolean gameOver(UFO ufo) {
     return (ufo.loc.y >= (this.height - 15)); // The UFO has landed
  }

  // If the game isn't over, return the new world.  If it is, then
  // call endOfTime().
  UFOGameWorld move(UFO ufo) {
      return new UFOGameWorld(this.width, this.height, ufo);
  }
  
  // When KeyEvents happen (which are represented by strings), then we need to do
  // the appropriate action, which is:
  //
  //  - "up"    : the tank shoots
  //  - "left"  : the tank moves left
  //  - "right" : the tank moves right.
  //
  // The tank move method handles the latter two, and we handle telling the tank to shoot.
  World onKeyEvent(String ke) {
    return this;
  }

  // When the tick event happens, then we need to move the UFO and the shots
  // (the tank only moves when the player moves it).
  World onTick() {
    if(this.gameOver(this.ufo))
        return this.endOfWorld();
    else
        return this.move(this.ufo.moveRandom(10, this.width, this.height));
  }

  // run() takes the amount of time between ticks as a double and calls all the necessary
  // functions.
  boolean run(double tick) {
    return this.bigBang(this.width, this.height, tick);
  }

  // erase the canvas
  boolean erase() {
    return this.theCanvas.drawRect(new Posn(0,0), this.width, this.height, new White());
  }
}

// Examples class
class Examples {
  int width  = 200; // How wide we want the game to be
  int height = 500; // How tall we want it to be
 
  Examples() {}

  // The UFO should start in the middle of the canvas, a little bit down from the top.
  UFO  ufo  = new UFO(new Posn(this.width / 2, 10));
  
  // The tank should start in the middle of the canvas, a little bit up from the bottom.
  // Tank tank = new Tank(new Posn(this.width / 2, this.height - 10));
  
  // There are no shots at the start of the game.
  // ALoS los  = new MTLoS();

  // Create a new game world with the actors defined above.
  UFOGameWorld ugw = new UFOGameWorld(this.width, this.height, this.ufo);

  // When you are finished, the UFOGameWould should include a tank and a list of shots, along
  // with the UFO
  // UFOGameWorld ugw = new UFOGameWorld(this.width, this.height, this.ufo, this.tank, this.los);

  // Upon instantiation of the example class, a test game runs.
  boolean b1 = this.ugw.run(0.3);
}

