/**
   * Calculates and returns the position of a jumping projectile at the given time.
   *
   * @param t The time at which we want to know the location of this projectile during the jump.
   * @param direction the direction in which the projectile is launched
   * @pre the direction given must be valid |isValidDirection(direction)
   * @return The position where this projectile lands after a potential jump. This projectile is not
   *     facing down | if ( ! isFacingDown()) | result == this.jumpStep(this.jumpTime())
   *     <p>This projectile is facing down, so this projectile doesn't jump | if (
   *     isFacingDown(direction)) | result == this.jumpStep(0)
   * @effect if the projectile overlaps with a worm the worm will be set as the overlappingWorm |
   *     this.setOverlapsWorm(newPosition);
   * @throws IllegalArgumentException the given value for t is not in the interval [0, jumpTime()].
   *     | ! isValidJumpTime(t)
   * @throws IllegalJumpTimeExcecption if for the given t the position is no longer passable for the
   *     movingObject | -New variable: newPosition = new Position(getX() +
   *     calculateVelocity()*cos(alpha)*t, getY() + calculateVelocity*sin(alpha)*t); | !
   *     this.getWorld().isPassableForCircle_BruteForce(this.getRadius(), newPosition)
   * @throws IllegalJumptTimeException if the projectile overlaps with a worm for the given t | -New
   *     variable: newPosition = new Position(getX() + calculateVelocity()*cos(alpha)*t, getY() +
   *     calculateVelocity*sin(alpha)*t); | overlapsWorm(newPosition);
   */
  @Override
  public Position jumpStep(double t, double direction)
      throws IllegalJumpTimeException, IllegalArgumentException {
    assert isValidDirection(direction);
    if (!isValidJumpTime(t)) {
      throw new IllegalArgumentException("negative time");
    }

    if (t == 0) return this.getPosition();
    else {

      if (isFacingDown(direction)) {
        return this.getPosition();
      }
      double alpha = getDirection();
      double v0 = calculateVelocity();
      double v0x = v0 * Math.cos(alpha);
      double v0y = v0 * Math.sin(alpha);
      double x = getX() + v0x * t;
      double y = getY() + v0y * t - g / 2 * t * t;
      Position newPosition = new Position(x, y);

      this.setOverlapsWorm(newPosition);

      return newPosition;
    }
  }
  /**
   * Returns the time it would take for a potential jump from the current position in the direction
   * this Worm is facing.
   *
   * @effect
   * @return returns the time needed for a moving object to make a jump | - New method:
   *     positionAtTime(T) = new Position(getX() + calculateVelocity() * cos(getDirection)*T, getY()
   *     + calculateVelocity()*sin(getDirection())*T -g/2*T²) | if for each T in [0,time-stepSize]:
   *     | (getWorld().isPassableForCircle(getRadius(),getRadius(), positionAtTime(T)) == true | &&
   *     getWorld().isPassableForCircle(getRadius(),getRadius(), positionAtTime(time)) == false) |
   *     && | if for one t in [0,time-stepSize]: | (this.getOverlappingWorm() != null) | then result
   *     == t
   * @return returns the time needed for a moving object to make a jump | - New method:
   *     positionAtTime(T) = new Position(getX() + calculateVelocity() * cos(getDirection)*T, getY()
   *     + calculateVelocity()*sin(getDirection())*T -g/2*T²) | if for each T in [0,time-stepSize]:
   *     | (getWorld().isPassableForCircle(getRadius(),getRadius(), positionAtTime(T)) == true | &&
   *     getWorld().isPassableForCircle(getRadius(),getRadius(), positionAtTime(time)) == false) |
   *     && | if for each t in [0,time-stepSize]: | (this.getOverlappingWorm() == null) | then
   *     result == time |
   * @effect if this projectile overlaps with a worm then the worm is set as overlappingworm |
   *     this.setOverlapsWorm(newPosition)
   */
  @Override
  public double jumpTime(double stepSize)
      throws IllegalJumpTimeException, IllegalArgumentException {
    double t = 0;
    boolean found = false;
    while (!found) {
      double alpha = getDirection();
      double v0 = calculateVelocity();
      double v0x = v0 * Math.cos(alpha);
      double v0y = v0 * Math.sin(alpha);
      double x = getX() + v0x * t;
      double y = getY() + v0y * t - g / 2 * t * t;

      Position newPosition = new Position(x, y);
      t = t + stepSize;
      if (!this.getWorld().isPassableForCircle(this.getRadius(), this.getRadius(), newPosition))
        found = true;

      this.setOverlapsWorm(newPosition);

      if (this.getOverlappingWorm() != null) found = true;
      t = t + stepSize;
    }
    return (t - stepSize);
  }
 /**
  * Returns the cost in actionpoints for a given number of steps in the current direction.
  *
  * @param steps the number of steps the worm is going to move.
  * @return The cost of steps (integer) in the current direction, rounded up to the next integer.
  *     |(steps*(int) Math.round((Math.abs(Math.cos(this.getDirection()))
  *     |+Math.abs((4*Math.sin(this.getDirection()))))))
  */
 @Basic
 @Raw
 private int computeCostStep(int steps) {
   return Math.abs(
       (int)
           Math.round(
               (steps)
                   * (Math.abs(Math.cos(this.getDirection()))
                       + Math.abs((4.0 * Math.sin(this.getDirection()))))));
 }
  /**
   * Returns the worms position during a jump on a given time (after the jump started).
   *
   * @param timeAfterLaunch The time after the jump started
   * @throws IllegalStateException If the worm can't jump the exception is thrown. | ! canJump()
   */
  @Basic
  @Raw
  public double[] jumpStep(double timeAfterLaunch) throws IllegalStateException {
    double[] step;
    step = new double[2];
    if (!this.canJump()) throw new IllegalStateException();

    step[0] =
        ((this.jumpVelocity() * Math.cos(this.getDirection()) * timeAfterLaunch) + this.getXpos());
    step[1] =
        (this.jumpVelocity() * Math.sin(this.getDirection()) * timeAfterLaunch
                - 0.5 * G * Math.pow(timeAfterLaunch, 2))
            + this.getYpos();
    return step;
  }
  /**
   * The method makes the worm move to a next position that is adjacent to impassable terrain
   * following the slope of that terrain in the direction. The worm shall aim to maximize the
   * distance while minimizing the divergence. If no such location exists because all locations in
   * the direction +- 0,7875 are impassable the worm shall remain at its current position. If
   * locations in the direction are passable but not adjacent the worm shall move there and then
   * drop passively.
   *
   * @post If the worm can maximize the distance while minimizing the divergence, the worm has moved
   *     to the optimal location. |for (double a = 0.1;a<=this.getRadius();a=a+(0.01*a)) { | x2 =
   *     x+Math.cos(direction)*a; | y2 = y+Math.sin(direction)*a; | if (world.isAdjacent(x2, y2,
   *     this.getRadius()) && | world.isPassable(x2, y2, this.getRadius())) { | double d =
   *     Math.sqrt(Math.pow((x-x2),2)+Math.pow((y-y2),2)); | double s = Math.atan((x-x2)/(y-y2)); |
   *     if ((d>=maxD) && (s<minS)) { | minS=s; | maxD=d; | x2Max = x2; | y2Max= y2; | direction =
   *     direction +0.0175; |new.getXpos()==x2Max |new.getYpos() == y2Max
   * @post If the worm can't maximize the distance while minimizing the divergence and there is only
   *     impassable terrain in the checked directions, the worm will not have moved. |new.getXpos()
   *     == old.getXpos() |new.getYpos() == old.getYpos()
   * @post If the worm can't maximize the distance while minimizing the divergence and there is only
   *     passable terrain in the checked directions that is not adjacent, the worm will move there.
   *     |new.getXpos() == old.getXpos() + cos(direction)*radius |new.getYpos() == old.getYpos() +
   *     sin(direction)*radius
   * @post The worms actionpoints are correctly reduced. |new.getActionPoints ==
   *     old.getActionPoints() - old.computeCost2(old.getXpos(),old.getYpos())
   * @throws IllegalArgumentException If the worm can't move because he has insufficient
   *     actionpoints the exception is thrown. | ! isValidStep()
   * @throws IllegalStateException If the worm can't move because the worm isn't positioned in
   *     passable terrain and adjacent to impassable terrain the exception is thrown. | ! canMove()
   */
  @Raw
  public void move() throws IllegalArgumentException, IllegalStateException {
    if (!isValidStep()) throw new IllegalArgumentException();
    if (canMove()) {
      World world = this.getWorld();
      double x = this.getXpos();
      double y = this.getYpos();
      double prevx = x;
      double prevy = y;
      double x2 = x;
      double y2 = y;
      double x2Max = x2;
      double y2Max = y2;
      double c = -0.7875;
      double direction = this.getDirection() + c;

      double maxD = 0;
      double minS = this.getDirection();

      // geval 1: na gaan of in direction+-45° er een gischike volgende positie is
      //			en zo ja, ernaar verplaatsen.
      for (double a = 0.1; a <= this.getRadius(); a = a + (0.01 * a)) {
        x2 = x + Math.cos(direction) * a;
        y2 = y + Math.sin(direction) * a;
        if (world.isAdjacent(x2, y2, this.getRadius())
            && world.isPassable(x2, y2, this.getRadius())) {
          double d = Math.sqrt(Math.pow((x - x2), 2) + Math.pow((y - y2), 2));
          double s = Math.atan((x - x2) / (y - y2));
          if ((d >= maxD) && (s < minS)) {
            minS = s;
            maxD = d;
            x2Max = x2;
            y2Max = y2;
          }
        }
        direction = direction + 0.0175;
      }
      if (this.isOutOfTheMap(x2Max, y2Max)) {
        this.killWorm();
      } else {
        this.setXpos(x2Max);
        this.setYpos(y2Max);
        this.setActionPoints(this.getActionPoints() - this.computeCost2(prevx, prevy));
      }

      // geval2: Er werd in direction+-45° geen geschikte plaats gevonden
      //			nagaan of er in direction naar een passable locatie kan verplaatst worden,
      //			daarnaar verplaatsen en dan vallen (fall).
      if ((x2Max == x) && (y2Max == y)) {
        double pasXpos = x;
        double pasYpos = y;

        pasXpos = (x + (Math.cos(this.getDirection()) * this.getRadius()));
        pasYpos = (y + (Math.sin(this.getDirection()) * this.getRadius()));
        if (!world.isAdjacent(pasXpos, pasYpos, this.getRadius())
            && world.isPassable(pasXpos, pasYpos, this.getRadius())) {
          if (this.isOutOfTheMap(pasXpos, pasYpos)) {
            this.killWorm();
          } else {
            this.setXpos(pasXpos);
            this.setYpos(pasYpos);
            this.setActionPoints(this.getActionPoints() - this.computeCost2(prevx, prevy));
          }
        }
      }
      this.consumeFood();
    } else throw new IllegalStateException();
  }
 /**
  * Sets the x-position of the projectile.
  *
  * @param xpos The (new) x-position of the projectile
  * @post the given x-position is the new x-position of the projectile. | new.getXpos() == xpos
  */
 @Raw
 private void setXpos(double xpos) {
   this.xpos =
       xpos + ((this.getRadiusWorm() + this.getRadius()) * 1.1 * Math.cos(this.getDirection()));
   position.setXpos(this.xpos);
 }