'''
Created on 2017-02-01
@author: olivier.massot
'''
import math
import sys


G = -3.711

# surface_n = int(input())  # the number of points used to draw the surface of Mars.
# land = [ int(input().split()[1]) for _ in range(surface_n)]


class V(tuple):
    """ 2D vector (dx, dy)
    where dx and dy are float """
    def __new__(cls, dx, dy):
        return tuple.__new__(cls, tuple([float(dx), float(dy)]))

    @property
    def dx(self):
        return self.__getitem__(0)

    @property
    def dy(self):
        return self.__getitem__(1)

    @property
    def angle(self):
        return math.atan2(self.dy, self.dx)

    @property
    def amplitude(self):
        return math.hypot(self.dx, self.dy)

    def unit_vector(self):
        return self / self.amplitude

    def __repr__(self):
        return "<Vector ({}, {})>".format(self.dx, self.dy)

    def __getitem__(self, key):
        return super(V, self).__getitem__(key)

    def __add__(self, value):
        if isinstance(value, self.__class__):
            return V(self.dx + value.dx, self.dy + value.dy)
        elif isinstance(value, int) or isinstance(value, float):
            return V(self.dx + value, self.dy + value)
        else:
            raise TypeError("value has to be a V instance, or a numeric (given: {})".format(value))

    def __sub__(self, value):
        if isinstance(value, self.__class__):
            return V(self.dx - value.dx, self.dy - value.dy)
        elif isinstance(value, int) or isinstance(value, float):
            return V(self.dx - value, self.dy - value)
        else:
            raise TypeError("value has to be a V instance, or a numeric (given: {})".format(value))

    def __mul__(self, value) :
        return V(self.dx * value, self.dy * value)

    def __truediv__(self, value) :
        return V(self.dx / value, self.dy / value)

    @classmethod
    def from_to(cls, point1, point2):
        return cls(*point2) - cls(*point1)

class Point(tuple):
    """ 2D point """
    def __new__(cls, x, y):
        return tuple.__new__(cls, tuple([int(x), int(y)]))

    def __repr__(self):
        return "<Point ({}, {})>".format(self.x, self.y)

    @property
    def x(self):
        return self.__getitem__(0)

    @x.setter
    def x(self, x):
        return self.__setitem__(0, x)

    @property
    def y(self):
        return self.__getitem__(1)

    @y.setter
    def y(self, y):
        return self.__setitem__(1, y)

    def distance_to(self, other_point):
        return V(other_point.x - self.x, other_point.y - self.y).amplitude


class Landscape(list):
    def __init__(self, *args):
        super(Landscape, self).__init__(*args)

    @classmethod
    def x_index(cls, x):
        return int(x / 1000)

    def y(self, x):
        return self.__getitem__(self.x_index(x))

    @property
    def coordinates(self):
        return [Point(index * 1000, y) for index, y in enumerate(self)]

    def landing_zones(self):
        """ "find the index of flat zones where rover can land
         (index is the position of the left point of the zone in the 'land' list) """
        return [ index for index in range(len(land) - 1) if land[index] == land[index + 1] ]

    def nearest_landing_point(self, from_x):
        """return the coordinates of the middle of the
        nearest landing zone as a tuple (x, y)
        for zone going from land[index] to land[index + 1] """
        index = sorted(self.landing_zones(), key=lambda x: abs(1000 * x - from_x))[0]
        return Point((1000 * index + 500), self.__getitem__(index))

    def next_top(self, from_x, to_x):
        """return the next peak as a tuple (x, y) """
        if from_x > to_x:
            from_x, to_x = to_x, from_x
        try:
            return Point(max([(1000 * index, y) for index, y in enumerate(self) \
                        if to_x > (1000 * index) > from_x], key=lambda x: x[1]))
        except ValueError:
            return None

class MarsLander(object):
    max_h_landing_speed = 20.0
    max_v_landing_speed = 40.0
    minimal_flight_altitude = 60

    def __init__(self):
        self._x = 0
        self._y = 0
        self._h_speed = 0.0
        self._v_speed = 0.0
        self._power = 0
        self._rotation = 0
        self._fuel = 0

    def __setattr__(self, attr, val):
        """ override object __setattr__ method """
        # check that the new value has the same type as the old one
        try:
            prev = type(self.__getattribute__(attr))
            if not isinstance(val, prev):
                raise TypeError("{attribute} has to be an {previous_type} (given: {value})\
                ".format(attribute=attr, value=val, previous_type=prev))
        except AttributeError:
            # newly created attribute
            pass
        object.__setattr__(self, attr, val)


    def __repr__(self):
        return "<MarsLander: {}>".format("; ".join(sorted(["{} = {}".format(key, val) for key, val in self.__dict__.items()])))

    @classmethod
    def from_input(cls, instr):
        """ load the parameters from the given input """
        lander = cls()
        lander.x, lander.y, h_speed, v_speed, lander.fuel, lander.rotation, lander.power = [int(i) for i in instr.split()]
        lander.h_speed, lander.v_speed = float(h_speed), float(v_speed)
        return lander

    @property
    def x(self):
        return self._x

    @x.setter
    def x(self, val):
        if not val in range(7000):
            raise ValueError("x has to be an integer in range 0 to 6999 (given: {value})".format(value=val))
        self._x = val

    @property
    def y(self):
        return self._y

    @y.setter
    def y(self, val):
        if not val in range(3000):
            raise ValueError("y has to be an integer in range 0 to 2999 (given: {value})".format(value=val))
        self._y = val

    @property
    def position(self):
        return Point(self.x, self.y)

    @property
    def h_speed(self):
        return self._h_speed

    @h_speed.setter
    def h_speed(self, val):
        # if not val in range(-500, 500):
        #    raise ValueError("h_speed has to be an integer in range -499 to 499 (given: {value})".format(value=val))
        self._h_speed = val

    @property
    def v_speed(self):
        return self._v_speed

    @v_speed.setter
    def v_speed(self, val):
        # if not val in range(-500, 500):
        #    raise ValueError("v_speed has to be an integer in range -499 to 499 (given: {value})".format(value=val))
        self._v_speed = val

    @property
    def speed_vector(self):
        return V(self.h_speed, self.v_speed)

    @property
    def power(self):
        return self._power

    @power.setter
    def power(self, val):
        if not val in range(5):
            raise ValueError("power has to be an integer in range 0 to 4 (given: {value})".format(value=val))
        self._power = val

    @property
    def rotation(self):
        return self._rotation

    @rotation.setter
    def rotation(self, val):
        if not val in range(-91, 91):
            raise ValueError("rotation has to be an integer in range -90 to 90 (given: {value})".format(value=val))
        self._rotation = val

    @property
    def fuel(self):
        return self._fuel

    @fuel.setter
    def fuel(self, val):
        if not val in range(2001):
            raise ValueError("fuel has to be an integer in range 0 to 2000 (given: {value})".format(value=val))
        self._fuel = val

    @classmethod
    def compute_acceleration(cls, power, rotation):
        """ return the resulting acceleration as a 2d vector (ax, ay)
        with that power / rotation configuration"""
        return V(-1 * math.sin(math.radians(rotation)) * power, math.cos(math.radians(rotation)) * power + G)

    @property
    def acceleration(self):
        """ return the current acceleration as a 2d vector (ax, ay) """
        return self.compute_acceleration(self.power, self.rotation)

    def speed_in(self, t):
        """ compute the speed in 't' seconds with the same acceleration,
        returns a 2d vector (vx, vy) """
        if not isinstance(t, int):
            raise TypeError("'t' has to be an integer (given:{})".format(t))
        if not t >= 0:
            raise ValueError("'t' has to be positive (given:{})".format(t))
        ax, ay = self.acceleration
        return V(self.h_speed + ax * t, self.v_speed + ay * t)

    def update_speed(self, dt):
        """ update the current speed, after 't' sec. with current acceleration """
        self.h_speed, self.v_speed = self.speed_in(dt)

    def configurations(self):
        """return the available (power, rotation) depending on the current configuration"""
        return [(p, 15 * r) for p in range(5) for r in range(-6, 7)]
#                 if abs(p - self.power) <= 1 and abs(15 * r - self.rotation) <= 15]

    def compute_trajectory(self, landscape):
        """ return the route to follow to each the landing point
        as a list of Points
        The last point will be above the landing point of 'minimal_flight_altitude'
        If that point has been reached, return an empty list"""
        landing_vector = V.from_to(self.position, landscape.nearest_landing_point(self.x))
        steps = []

        if abs(landing_vector.dx) < 100 and abs(landing_vector.dy) <= self.minimal_flight_altitude:
            # landing point has been reached
            return []

        # could be shorter, could be quicker, but robustness and clarity are needed
        for point in landscape.coordinates:
            top_vector = V.from_to(self.position, point) + V(0, self.minimal_flight_altitude)

            if top_vector.dx * landing_vector.dx < 0:
                # top is not in the landing point direction
                continue

            if abs(top_vector.dx) > abs(landing_vector.dx):
                # too far
                continue

            if (landing_vector * top_vector.dx / abs(landing_vector.dx)).dy > top_vector.dy:
                # top is under the trajectory
                continue

            steps.append(Point(*(V(*self.position) + top_vector)))

        steps.sort(key=lambda p: p.x, reverse=(landing_vector.dx < 0))
        last_step_vector = (V(*self.position) + landing_vector + V(0, self.minimal_flight_altitude))
        steps.append(Point(*last_step_vector))

        return steps

    def land(self, landscape, callback=None, output=None):
        """ (generator)
            Mars lander will land on the given landscape
        """
        # callback function is called at each iteration
        if not callback:
            callback = (lambda x: x)
        # output function is called to print lander's thoughts
        if not output:
            output = (lambda x: print(x))

        t = 0
        while 1:
            t += 1
            output("Hi, this is Mars Lander, and it is {} s.".format(t))
            output("I am currently at ({} , {})".format(self.x, self.y))

            trajectory = self.compute_trajectory(landscape)
            output("My trajectory is {}".format(trajectory))

            try:
                target = trajectory[0]
                output("For now, I try to reach {}".format(target))
            except IndexError:
                output("Right: I'm landing right now")
                self.power = 4 if self.v_speed > 1 else 3
                self.rotation = 0
                callback(self)
                yield t
                continue

            target_direction = V.from_to(self.position, target).unit_vector()
            output("Direction unit vector is {}".format(target_direction))

            # current speed vector
            output("My current speed vector is {}".format(self.speed_vector))

            # optimal speed vector
            if abs(target_direction.dx * self.max_h_landing_speed) >= abs(target_direction.dy * self.max_v_landing_speed):
                needed_speed_vector = target_direction * self.max_h_landing_speed
            else:
                needed_speed_vector = target_direction * self.max_v_landing_speed
            output("I need a speed like: {}".format(needed_speed_vector))

            # compute the new acceleration vector
            new_acc_vector = needed_speed_vector - self.speed_vector
            output("I can reach that with that acceleration: {}".format(new_acc_vector))

            # look for the closest possible acceleration (p is set to 0.1 instead of 0 in order to give sense to vectors comparison)
            configs = [(p, r) for p in (0.1, 1, 2, 3, 4) for r in range(-90, 91, 15)]
            closest_config = min([(p, r, self.compute_acceleration(p, r)) for p, r in configs], \
                                 key=lambda c: Point(*new_acc_vector).distance_to(Point(*c[2])))
            power, rotation, new_acc_vector = closest_config
            if power < 1:
                power = 0
            output("The closest acceleration I could reach is {} ms-2, with power {} and rotation {}".format(new_acc_vector, power, rotation))

            # limits the changes if needed
            if abs(power - self.power) > 1 or abs(rotation - self.rotation) > 15:
                if power != self.power:
                    power = self.power + int((power - self.power) / abs(power - self.power))
                if rotation != self.rotation:
                    rotation = self.rotation + 15 * int((rotation - self.rotation) / abs(rotation - self.rotation))
                new_acc_vector = self.compute_acceleration(power, rotation)
                output("Hmmph, the best I can reach is power {}, rotation {}, for an acceleration of {} ms-2".format(power, rotation, new_acc_vector))

            # update the lander attributes
            self.power = power
            self.rotation = rotation
            self.fuel -= power

            # new position
            new_x, new_y = V(*self.position) + self.speed_vector
            self.x, self.y = int(new_x), int(new_y)

            # new speed
            self.h_speed, self.v_speed = self.speed_vector + new_acc_vector

            callback(self)
            yield t

    def hyperspace(self):
        raise NotImplementedError()

    def distance_to(self, x, y):
        return math.hypot((x - self.x), (y - self.y))

    def angle_to(self, x, y):
        return math.atan2((y - self.y), (x - self.x))

def send_data(lander):
    print("{rotation} {power}".format(rotation=lander.rotation, power=lander.power))
    # input()

def output(msg):
    print(msg, file=sys.stderr)


surface_n = 7
land = [150, 500, 2000, 150, 150, 500, 2000]
# land = [3000, 150, 150, 500, 3000, 600, 1000]

landscape = Landscape(land)

lander = MarsLander()
lander.x = 3000
lander.y = 2000
lander.fuel = 1000
gen = lander.land(landscape)

for i in range(5):
    t = next(gen)
    print(t, lander)
    input()

# lander = MarsLander.from_input( input() )
# landing_point_x, landing_point_y = nearest_landing_zone(lander.x)
# target_x, target_y = landing_point_x, landing_point_y
# #move_generator = lander.move_to(target_x, target_y, callback=send_data, landing=True, output=output, land_max_elevation=max(land))
#
# while True:
#     target = next_peak(lander.x, landing_point_x)
#     if target:
#         if target != (target_x, target_y):
#             target_x, target_y = target
#             move_generator = lander.move_to(target_x, target_y + 100, callback=send_data, landing=False)
#     else:
#         move_generator = lander.move_to(landing_point_x, landing_point_y, callback=send_data, landing=True)
#
#     t = next(move_generator)
#     print(t, lander, file=sys.stderr)
#
#     x, y, h_speed, v_speed, fuel, rotate, power = [int(i) for i in input().split()]
#     #print(x, y, h_speed, v_speed, fuel, rotate, power, file=sys.stderr)
#
#
#     # rotate power. rotate is the desired rotation angle. power is the desired thrust power.
#     # print("{rotation} {power}".format(rotation=lander.rotation, power=lander.power))
#
#     #input() # get the unused input
#     #t = next(move)
