import numpy as np
import numbers

def extract(cond, x):
    if isinstance(x, numbers.Number):
        return x
    else:
        return np.extract(cond, x)

class vec3():
    
    def __init__(self, x, y, z):
        self.x = x
        self.y = y
        self.z = z
    
    def __str__(self):
        # Used for debugging. This method is called when you print an instance  
        return "(" + str(self.x) + ", " + str(self.y) + ", " + str(self.z) + ")"

             
    def __add__(self, v):
        if isinstance(v, vec3):
            return vec3(self.x + v.x, self.y + v.y, self.z + v.z)
        elif isinstance(v, numbers.Number) or isinstance(v, np.ndarray):
            return vec3(self.x + v, self.y + v, self.z + v)
    def __radd__(self, v):
        if isinstance(v, vec3):
            return vec3(self.x + v.x, self.y + v.y, self.z + v.z)
        elif isinstance(v, numbers.Number) or isinstance(v, np.ndarray):
            return vec3(self.x + v, self.y + v, self.z + v)
    def __sub__(self, v):
        if isinstance(v, vec3):
            return vec3(self.x - v.x, self.y - v.y, self.z - v.z)
        elif isinstance(v, numbers.Number) or isinstance(v, np.ndarray):
            return vec3(self.x - v, self.y - v, self.z - v)
    def __rsub__(self, v):
        if isinstance(v, vec3):
            return vec3(v.x - self.x, v.y - self.y ,  v.z - self.z)
        elif isinstance(v, numbers.Number) or isinstance(v, np.ndarray):
            return vec3(v  - self.x, v  - self.y ,  v - self.z)

    def __mul__(self, v):
        if isinstance(v, vec3):
            return vec3(self.x * v.x , self.y *  v.y , self.z * v.z )
        elif isinstance(v, numbers.Number) or isinstance(v, np.ndarray):
            return vec3(self.x * v, self.y * v, self.z * v) 
    def __rmul__(self, v):
        if isinstance(v, vec3):
            return vec3(v.x *self.x  , v.y * self.y, v.z * self.z )
        elif isinstance(v, numbers.Number) or isinstance(v, np.ndarray):
            return vec3(v *self.x  , v * self.y, v * self.z ) 
    def __truediv__(self, v):
        if isinstance(v, vec3):
            return vec3(self.x / v.x , self.y /  v.y , self.z / v.z )
        elif isinstance(v, numbers.Number) or isinstance(v, np.ndarray):
            return vec3(self.x / v, self.y / v, self.z / v)


    def __rtruediv__(self, v):
        if isinstance(v, vec3):
            return vec3(v.x / self.x, v.y / self.y, v.z / self.z)
        elif isinstance(v, numbers.Number) or isinstance(v, np.ndarray):
            return vec3(v / self.x, v / self.y, v / self.z)
    
    def __neg__(self):
        return vec3(-self.x, -self.y, -self.z)
    
    def __abs__(self):
        return vec3(np.abs(self.x), np.abs(self.y), np.abs(self.z))
    
    def real(v):
        return vec3(np.real(v.x), np.real(v.y), np.real(v.z))
    
    def imag(v):
        return vec3(np.imag(v.x), np.imag(v.y), np.imag(v.z))  

    def yzx(self):
        return vec3(self.y, self.z, self.x)  
    def xyz(self):
        return vec3(self.x, self.y, self.z)
    def zxy(self):
        return vec3(self.z, self.x, self.y)
    def xyz(self):
        return vec3(self.x, self.y, self.z)  


    def average(self):
        return (self.x + self.y +  self.z)/3
    
    def matmul(self, matrix):
        if isinstance(self.x, numbers.Number):
            return array_to_vec3(np.dot(matrix,self.to_array()))
        elif isinstance(self.x, np.ndarray):
            return array_to_vec3(np.tensordot(matrix,self.to_array() , axes=([1,0])))

    def change_basis(self, new_basis):
        return vec3(self.dot(new_basis[0]),  self.dot(new_basis[1]),   self.dot(new_basis[2]))

    def __pow__(self, a):
        return vec3(self.x**a, self.y**a, self.z**a)
    
    def dot(self, v):
        return self.x*v.x + self.y*v.y + self.z*v.z
    
    def exp(v):
        return vec3(np.exp(v.x) , np.exp(v.y) ,np.exp(v.z))
    
    def sqrt(v):
        return vec3(np.sqrt(v.x) , np.sqrt(v.y) ,np.sqrt(v.z)) 
    
    def to_array(self):
        return np.array([self.x , self.y , self.z])

    def cross(self, v):
        return vec3(self.y*v.z - self.z*v.y, -self.x*v.z + self.z*v.x,  self.x*v.y - self.y*v.x)
    
    def length(self):
        return np.sqrt(self.dot(self))
    
    def square_length(self):
        return self.dot(self)

    def normalize(self):
        mag = self.length()
        return self * (1.0 / np.where(mag == 0, 1, mag))
 


    def components(self):
        return (self.x, self.y, self.z)
    
    def extract(self, cond):
        return vec3(extract(cond, self.x),
                    extract(cond, self.y),
                    extract(cond, self.z))

    def where(cond, out_true, out_false):
        return vec3(np.where(cond, out_true.x, out_false.x),
                    np.where(cond, out_true.y, out_false.y),
                    np.where(cond, out_true.z, out_false.z))

    def select(mask_list, out_list):
        out_list_x = [i.x for i in out_list]
        out_list_y = [i.y for i in out_list]
        out_list_z = [i.z for i in out_list]

        return vec3(np.select(mask_list, out_list_x),
                    np.select(mask_list, out_list_y),
                    np.select(mask_list, out_list_z))

    def clip(self, min, max):
        return vec3(np.clip(self.x, min, max),
                    np.clip(self.y, min, max),
                    np.clip(self.z, min, max))

    def place(self, cond):
        r = vec3(np.zeros(cond.shape), np.zeros(cond.shape), np.zeros(cond.shape))
        np.place(r.x, cond, self.x)
        np.place(r.y, cond, self.y)
        np.place(r.z, cond, self.z)
        return r
    
    def place_values(self, cond, val):
        r = vec3(self.x.copy(), self.y.copy(), self.z.copy())
        np.place(r.x, cond, val.x)
        np.place(r.y, cond, val.y)
        np.place(r.z, cond, val.z)
        return r

    def repeat(self, n):
        return vec3(np.repeat(self.x , n), np.repeat(self.y , n),   np.repeat(self.z , n))

    def reshape(self, *newshape):
        return vec3(self.x.reshape(*newshape), self.y.reshape(*newshape),   self.z.reshape(*newshape))

    def shape(self, *newshape):
        if isinstance(self.x, numbers.Number):
            return 1
        elif isinstance(self.x, np.ndarray):
                return self.x.shape

    def mean(self, axis):
        return vec3(np.mean(self.x,axis = axis), np.mean(self.y,axis = axis),   np.mean(self.z,axis = axis))

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

    def zeros_like(self):
        return vec3(np.zeros_like(self.x), np.zeros_like(self.y), np.zeros_like(self.z))
    
def array_to_vec3(array):
    return vec3(array[0],array[1],array[2])



global rgb 
rgb = vec3