import numpy as np
from ..utils.constants import *
from ..utils.vector3 import vec3
from ..geometry.primitive import Primitive
from ..geometry.collider import Collider


class Triangle(Primitive): 
    def __init__(self,center,  material, p1 , p2, p3, max_ray_depth,shadow = True):
        super().__init__(center,  material, max_ray_depth, shadow = shadow)  
        self.collider_list += [Triangle_Collider(assigned_surface = self, p1 =p1, p2 = p2, p3 = p3)]

    def get_uv(self, M, collider):
        return collider.get_uv(M)


class Triangle_Collider(Collider):
    def __init__(self,assigned_surface, p1, p2, p3):


        self.assigned_primitive = assigned_surface
        self.p1 = p1
        self.p2 = p2
        self.p3 = p3
        self.normal = ((self.p2 - self.p1).cross( self.p3 - self.p1)).normalize()


        self.centroid = (self.p1 + self.p2  + self.p3)/3  #one possible definition of center. Used for intersect().


        self.n31 = (self.p3 - self.p1).cross(self.normal)
        self.n12 = (self.p1- self.p2).cross(self.normal)
        self.n23 = (self.p2 - self.p3).cross(self.normal)



    def intersect(self, O, D):
        N = self.normal  
        
        NdotD = N.dot(D) 
        NdotD = np.where(NdotD == 0., NdotD + 0.0001, NdotD) #avoid zero division
        
        NdotC_O = N.dot(self.centroid - O)
        d =  D * NdotC_O / NdotD
        M = O +  d     # intersection point
        dis =  d.length()
        M_C = M - self.centroid
        hit_inside = (self.n31.dot(M-self.p1) >= 0) & (self.n12.dot(M-self.p2) >= 0)& (self.n23.dot(M-self.p3) >= 0) & (NdotC_O * NdotD > 0) 
        hit_UPWARDS  = (NdotD < 0)
        hit_UPDOWN  = np.logical_not(hit_UPWARDS)


        pred1 = hit_inside & hit_UPWARDS
        pred2 = hit_inside & hit_UPDOWN
        pred3 = True
        return np.select([pred1,pred2,pred3] , [[dis, np.tile(UPWARDS, dis.shape) ], [dis,np.tile(UPDOWN, dis.shape)], FARAWAY])

    def rotate(self,M, center):
        self.p1 = center + (self.p1 -center).matmul(M)
        self.p2 = center + (self.p2 -center).matmul(M)
        self.p3 = center + (self.p3 -center).matmul(M)

        self.n31 = self.n31.matmul(M)
        self.n12 = self.n12.matmul(M)
        self.n23 = self.n23.matmul(M)
        self.normal = self.normal.matmul(M)
        self.centroid = center + (self.centroid-center).matmul(M)
        
    def get_uv(self, hit):
        M_C = hit.point - self.center
        u = ((self.pu.dot(M_C)/self.w + 1 ) /2 + self.uv_shift[0])
        v = ((self.pv.dot(M_C)/self.h + 1 ) /2  + self.uv_shift[1])
        return u,v


    def get_Normal(self, hit):
        return self.normal
