from ..utils.constants import *
from ..utils.vector3 import vec3, rgb, extract
from ..utils.random import spherical_caps_pdf, cosine_pdf, mixed_pdf
from functools import reduce as reduce 
from ..ray import Ray, get_raycolor
from .. import lights
import numpy as np
from . import Material
from ..textures import *

class Diffuse(Material):
    def __init__(self, diff_color, diffuse_rays = 20, ambient_weight = 0.5, **kwargs):
        super().__init__(**kwargs)

        if isinstance(diff_color, vec3):
            self.diff_texture = solid_color(diff_color)
        elif isinstance(diff_color, texture):
            self.diff_texture = diff_color

        self.diffuse_rays = diffuse_rays
        self.max_diffuse_reflections = 2
        self.ambient_weight = ambient_weight

    def get_color(self, scene, ray, hit):
        
        hit.point = (ray.origin + ray.dir * hit.distance) # intersection point
        N = hit.material.get_Normal(hit)                  # normal 

        diff_color = self.diff_texture.get_color(hit)


        color = rgb(0.,0.,0.)

        if ray.diffuse_reflections < 1:


            nudged = hit.point + N * .000001
            N_repeated = N.repeat(self.diffuse_rays)


            if ray.n.shape() == 1:
                n_repeated = ray.n
            else:
                n_repeated = ray.n.repeat(self.diffuse_rays)

            nudged_repeated = nudged.repeat(self.diffuse_rays)
            hit_repeated = hit.point.repeat(self.diffuse_rays)


            size = N.shape()[0] * self.diffuse_rays

            pdf1 = cosine_pdf(size, N_repeated)
            pdf2 = spherical_caps_pdf(size, nudged_repeated, scene.importance_sampled_list)
            
            s_pdf = None
            if scene.importance_sampled_list == []:
                s_pdf = cosine_pdf(size, N_repeated)
            else:
                s_pdf = mixed_pdf(size, pdf1, pdf2, self.ambient_weight)

            ray_dir = s_pdf.generate()
            PDF_val = s_pdf.value(ray_dir)

            NdotL = np.clip(ray_dir.dot(N_repeated),0.,1.)
            color_temp = get_raycolor(Ray(nudged_repeated, ray_dir, ray.depth + 1, n_repeated, ray.reflections + 1, ray.transmissions, ray.diffuse_reflections + 1), scene)
            color_temp = color_temp * NdotL  / PDF_val / (np.pi) #  diff_color/np.pi = Lambertian BRDF
            color += diff_color * color_temp.reshape(N.shape()[0], self.diffuse_rays).mean(axis = 1)
            
            return color

        elif ray.diffuse_reflections < self.max_diffuse_reflections:

            """
            when ray.diffuse_reflections > 1 we just call one diffuse ray to solve rendering equation (otherwise is too slow)
            """
            
            nudged = hit.point + N * .000001
            size = N.shape()[0] 
            s_pdf = None

            pdf1 = cosine_pdf(size, N)
            pdf2 = spherical_caps_pdf(size, nudged, scene.importance_sampled_list)

            if scene.importance_sampled_list == []:
                s_pdf = cosine_pdf(size, N)
            else:
                s_pdf = mixed_pdf(size, pdf1, pdf2, self.ambient_weight)

            ray_dir = s_pdf.generate()
            PDF_val = s_pdf.value(ray_dir)

            NdotL = np.clip(N.dot(ray_dir),0.,1.)
            color_temp = diff_color * get_raycolor(Ray(nudged, ray_dir, ray.depth + 1, ray.n, ray.reflections + 1, ray.transmissions, ray.diffuse_reflections + 1), scene)
            color = color_temp * NdotL  / PDF_val / (np.pi) 

            return color
            
        else:
            return color
