#!/usr/bin/env python
"""
Simple vector classes. (Python3)

Why not use numpy instead? A couple of reasons:
1. performance, working with small numpy arrays introduces a lot of overhead
2. .x and .y class properties
3. clear implementation for readability and porting to other languages.

 Copyright (c) 2021 AIGRO B.V. - Jev Kuznetsov
 License: BSD

Special thanks for these information sources and references:

* [vector.py](https://gist.github.com/mostley/3819375)
* [StackOverflow](https://stackoverflow.com/a/43542669)
* [vector.py](https://github.com/betados/vector_2d/blob/develop/vector_2d/vector.py)

"""

from math import atan2, hypot, sin, cos
import numpy as np


def rotation_matrix(theta):
    """ create 2-d rotation matrix """
    return np.array([[cos(theta), sin(theta)], [-sin(theta), cos(theta)]])


class Vector:
    """ 2d vector, compatible with numpy """

    def __init__(self, *args):
        """ create from x,y or xy """
        try:
            self.x, self.y = float(args[0]), float(args[1])
        except (IndexError, TypeError):
            self.x = float(args[0][0])
            self.y = float(args[0][1])

    def __repr__(self):
        return f"(x={self.x:.3f}, y={self.y:.3f})"

    def __add__(self, other):
        return Vector(self.x + other.x, self.y + other.y)

    def __sub__(self, other):
        return Vector(self.x - other.x, self.y - other.y)

    def __eq__(self, other):
        return (self.x == other.x) & (self.y == other.y)

    def cross(self, other):
        return (self.x * other.y) - (self.y * other.x)

    def dot(self, other):
        return self.x * other.x + self.y * other.y

    def inner(self, other):
        return self.dot(other)

    def rotate(self, angle):
        a = np.dot(self, rotation_matrix(angle))
        return Vector(a[0], a[1])

    def round(self, precision=5):
        r = np.round(self, precision)
        return Vector(r)

    def angle(self, other):
        """ angle between vector, *relative* to other, range (-pi..pi)  """
        return -atan2(self.cross(other), self.inner(other))

    def __mul__(self, scalar):
        """ scalar multiplification """
        return Vector(self.x * scalar, self.y * scalar)

    def __rmul__(self, scalar):
        return self.__mul__(scalar)

    def __truediv__(self, scalar):  # note __div__ was removed in Python3
        return Vector(self.x / scalar, self.y / scalar)

    def __neg__(self):
        return Vector(-self.x, -self.y)

    def __abs__(self):
        return hypot(self.x, self.y)

    def distance(self, other):
        return hypot(self.x-other.x, self.y-other.y)

    @classmethod
    def from_polar(cls, r, theta):
        return Vector(r * cos(theta), r * sin(theta))

    def __getitem__(self, item):
        """ make subscriptable """
        return [self.x, self.y][item]

    def __array__(self, dtype=None) -> np.array:
        """ make this class compatable with numpy operations """
        if dtype:
            return np.array([self.x, self.y], dtype=dtype)
        else:
            return np.array([self.x, self.y])


def point_on_line(a: Vector, b: Vector, p: Vector):
    """
    project point p to line defined by a and b
    """
    ap = p - a
    ab = b - a
    return a + ap.dot(ab) / ab.dot(ab) * ab
