from __future__ import annotations

import numpy as np

from util import iszero


class Vector:
  def __init__(self, a):
    self.a: np.ndarray = np.array(a, dtype=float)
    self.a.setflags(write=False)

    assert len(self.a.shape) == 2

  @staticmethod
  def fromiterable(iterable):
    a = np.zeros((len(iterable), 1), dtype=float)

    for i, j in enumerate(iterable):
      a[i, 0] = j

    return Vector(a)

  @staticmethod
  def fromargs(*args):
    return Vector.fromiterable(args)

  @staticmethod
  def random():
    return Vector.fromiterable(np.random.uniform(low=-10, high=10, size=3))

  def __add__(self, other: Vector) -> Vector:
    return Vector(self.a + other.a)

  def __sub__(self, other: Vector) -> Vector:
    return Vector(self.a - other.a)

  def __matmul__(self, other: Vector) -> float:
    """
    Dot product
    """
    # noinspection PyTypeChecker
    return np.sum(self.a * other.a)

  def __or__(self, other: Vector) -> Vector:
    """
    Cross product
    """

    if self.a.shape != other.a.shape or self.a.shape != (3, 1):
      raise ValueError()

    return Vector.fromargs(
      self.a[1, 0] * other.a[2, 0] - self.a[2, 0] * other.a[1, 0],
      self.a[2, 0] * other.a[0, 0] - self.a[0, 0] * other.a[2, 0],
      self.a[0, 0] * other.a[1, 0] - self.a[1, 0] * other.a[0, 0],
    )

  def __abs__(self) -> float:
    """
    Norm of self
    """
    return np.sqrt(self @ self)

  def __eq__(self, other: Vector):
    if self.a.shape != other.a.shape:
      return False

    return (self - other).iszero()

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

  def __repr__(self):
    return f'Vector({repr(self.a)})'

  def __mul__(self, other):
    return Vector(self.a * other)

  def __rmul__(self, other):
    return Vector(other * self.a)

  def __truediv__(self, other):
    return Vector(self.a / other)

  def __mod__(self, other):
    """
    Is self perpendicular to other?
    """
    return iszero(self @ other)

  def __neg__(self):
    return -1 * self

  def iszero(self):
    return iszero(self @ self)

  def normalize(self):
    if self.iszero():
      raise ValueError()
    return self / abs(self)

  def __hash__(self):
    return hash(self.a)
