import math

class Creature(object):
    def __init__(self, position, health,dy = 0, walkSpeed = 5, flying = False, flySpeed = 10, height = 1, jumpHeight = 1.0):
        # Current (x, y, z) position in the world, specified with floats. Note
        # that, perhaps unlike in math class, the y-axis is the vertical axis.
        self.position = position
        # First element is rotation of the player in the x-z plane (ground
        # plane) measured from the z-axis down. The second is the rotation
        # angle from the ground plane up. Rotation is in degrees.
        #
        # The vertical plane rotation ranges from -90 (looking straight down) to
        # 90 (looking straight up). The horizontal rotation range is unbounded.
        self.rotation = (0, 0)
        # Strafing is moving lateral to the direction you are facing,
        # e.g. moving to the left or right while continuing to face forward.
        #
        # First element is -1 when moving forward, 1 when moving back, and 0
        # otherwise. The second element is -1 when moving left, 1 when moving
        # right, and 0 otherwise.
        self.strafe = [0, 0]
        # Velocity in the y (upward) direction.
        self.dy = dy
        #
        self.walkSpeed = walkSpeed
        #the height that creature can reach by jumping (default : About the height of a block)
        self.JumpHeight = jumpHeight
        #max falling velocity
        self.terminalVelocity = 50
        #weather creature are flying
        self.flying = flying
        #how fast could the creature fly
        self.flySpeed = flySpeed
        #the height of the creature
        self.height = height
        #the health of the creatures
        self.health = health

    def rotate(self,x,y):
        y = max(-90, min(90, y))
        self.rotation = (x, y)

    def move(self,direction):
        if direction == "FORWARD":
            self.strafe[0] -= 1
        elif direction == "BACKWARD":
            self.strafe[0] += 1
        elif direction == "LEFT":
            self.strafe[1] -= 1
        elif direction == "RIGHT":
            self.strafe[1] += 1
        else:
            raise ValueError("The direction has to be forward, backward, left or right.")

    def stopMove(self,direction):
        if direction == "FORWARD":
            self.strafe[0] += 1
        elif direction == "BACKWARD":
            self.strafe[0] -= 1
        elif direction == "LEFT":
            self.strafe[1] += 1
        elif direction == "RIGHT":
            self.strafe[1] -= 1
        else:
            raise ValueError("The direction has to be forward, backward, left or right.")

    def jump(self,gravity):
        # To derive the formula for calculating jump speed, first solve
        #    v_t = v_0 + a * t
        # for the time at which you achieve maximum height, where a is the acceleration
        # due to gravity and v_t = 0. This gives:
        #    t = - v_0 / a
        # Use t and the desired MAX_JUMP_HEIGHT to solve for v_0 (jump speed) in
        #    s = s_0 + v_0 * t + (a * t^2) / 2
        self.dy = math.sqrt(2 * gravity * self.JumpHeight)

    def update(self,dt,world):
        """ Private implementation of the `update()` method. This is where most
        of the motion logic lives, along with gravity and collision detection.

        Parameters
        ----------
        dt : float
            The change in time since the last call.

        """
        # walking
        speed = self.walkSpeed
        d = dt * speed # distance covered this tick.
        dx, dy, dz = self.get_motion_vector()
        # New position in space, before accounting for gravity.
        dx, dy, dz = dx * d, dy * d, dz * d
        # gravity
        if not self.flying:
            # Update your vertical speed: if you are falling, speed up until you
            # hit terminal velocity; if you are jumping, slow down until you
            # start falling.
            self.dy -= dt * world.gravity
            self.dy = max(self.dy, - self.terminalVelocity)
            dy += self.dy * dt
        # collisions
        x, y, z = self.position
        x, y, z = world.collide((x + dx, y + dy, z + dz), self)
        self.position = (x, y, z)

    def get_motion_vector(self):
        """ Returns the current motion vector indicating the velocity of the
        creature.

        Returns
        -------
        vector : tuple of len 3
            Tuple containing the velocity in x, y, and z respectively.

        """
        if any(self.strafe):
            x, y = self.rotation
            strafe = math.degrees(math.atan2(*self.strafe))
            y_angle = math.radians(y)
            x_angle = math.radians(x + strafe)
            if self.flying:
                m = math.cos(y_angle)
                dy = math.sin(y_angle)
                if self.strafe[1]:
                    # Moving left or right.
                    dy = 0.0
                    m = 1
                if self.strafe[0] > 0:
                    # Moving backwards.
                    dy *= -1
                # When you are flying up or down, you have less left and right
                # motion.
                dx = math.cos(x_angle) * m
                dz = math.sin(x_angle) * m
            else:
                dy = 0.0
                dx = math.cos(x_angle)
                dz = math.sin(x_angle)
        else:
            dy = 0.0
            dx = 0.0
            dz = 0.0
        return (dx, dy, dz)