import operator
import math


def vop(op, a, b):
    return list(map(op, a, b))


class NVector():
    def __init__(self, *components):
        self.components = list(components)

    def __str__(self):
        return str(self.components)

    def __repr__(self):
        return "NVector(%s)" % ", ".join(map(str, self))

    def __len__(self):
        return len(self.components)

    def to_list(self):
        return list(self.components)

    def __add__(self, other):
        return type(self)(*vop(operator.add, self.components, other.components))

    def __sub__(self, other):
        return type(self)(*vop(operator.sub, self.components, other.components))

    def __mul__(self, scalar):
        if isinstance(scalar, NVector):
            return type(self)(*vop(operator.mul, self.components, scalar.components))
        return type(self)(*(c * scalar for c in self.components))

    def __truediv__(self, scalar):
        return type(self)(*(c / scalar for c in self.components))

    def __iadd__(self, other):
        self.components = vop(operator.add, self.components, other.components)
        return self

    def __isub__(self, other):
        self.components = vop(operator.sub, self.components, other.components)
        return self

    def __imul__(self, scalar):
        if isinstance(scalar, NVector):
            self.components = vop(operator.mul, self.components, scalar.components)
        else:
            self.components = [c * scalar for c in self.components]
        return self

    def __itruediv__(self, scalar):
        self.components = [c / scalar for c in self.components]
        return self

    def __neg__(self):
        return type(self)(*(-c for c in self.components))

    def __getitem__(self, key):
        if isinstance(key, slice):
            return NVector(*self.components[key])
        return self.components[key]

    def __setitem__(self, key, value):
        self.components[key] = value

    def __eq__(self, other):
        return self.components == other.components

    def __abs__(self):
        return type(self)(*(abs(c) for c in self.components))

    @property
    def length(self) -> float:
        return math.hypot(*self.components)

    def dot(self, other):
        return sum(map(operator.mul, self.components, other.components))

    def clone(self):
        return NVector(*self.components)

    def lerp(self, other, t):
        return self * (1-t) + other * t

    @property
    def x(self):
        return self.components[0]

    @x.setter
    def x(self, v):
        self.components[0] = v

    @property
    def y(self):
        return self.components[1]

    @y.setter
    def y(self, v):
        self.components[1] = v

    @property
    def z(self):
        return self.components[2]

    @z.setter
    def z(self, v):
        self.components[2] = v

    def element_scaled(self, other):
        return type(self)(*vop(operator.mul, self.components, other.components))

    def cross(self, other):
        """
        @pre len(self) == len(other) == 3
        """
        a = self
        b = other
        return type(self)(
            a[1] * b[2] - a[2] * b[1],
            a[2] * b[0] - a[0] * b[2],
            a[0] * b[1] - a[1] * b[0],
        )

    @property
    def polar_angle(self):
        """
        @pre len(self) == 2
        """
        return math.atan2(self.y, self.x)


def Point(x, y):
    return NVector(x, y)


def Size(x, y):
    return NVector(x, y)


def Point3D(x, y, z):
    return NVector(x, y, z)


def PolarVector(length, theta):
    return NVector(length * math.cos(theta), length * math.sin(theta))
