use crate::hittable::HitRecord;
use crate::ray::Ray;
use crate::rtweekend::random_f64;
use crate::vec3::{random_unit_vector, reflect, refract, Color};

//More rusty
/*
    pub struct ScatterData {
        attenuation: Color,
        scattered: Ray
    }

    pub trait Material {
        fn scatter(&self, r_in: Ray, rec: HitRecord) -> Option<ScatterData>;
    }
*/

pub trait Material: Send + Sync {
    fn emitted(&self) -> Color {
        Color::new(0.0, 0.0, 0.0)
    }

    fn scatter(
        &self,
        r_in: Ray,
        rec: HitRecord,
        attenuation: &mut Color,
        scattered: &mut Ray,
    ) -> bool;
}

pub struct Lambertian {
    pub albedo: Color,
}

impl Lambertian {
    pub const fn new(albedo: Color) -> Self {
        Lambertian { albedo }
    }
}

impl Material for Lambertian {
    fn scatter(
        &self,
        _r_in: Ray,
        rec: HitRecord,
        attenuation: &mut Color,
        scattered: &mut Ray,
    ) -> bool {
        let mut scatter_direction = rec.normal + random_unit_vector();

        if scatter_direction.near_zero() {
            scatter_direction = rec.normal;
        }

        *scattered = Ray::new(rec.p, scatter_direction);
        *attenuation = self.albedo;
        return true;
    }
}

pub struct Metal {
    pub albedo: Color,
    pub fuzz: f64,
}

impl Metal {
    pub const fn new(albedo: Color, fuzz: f64) -> Self {
        Metal { albedo, fuzz }
    }
}

impl Material for Metal {
    fn scatter(
        &self,
        r_in: Ray,
        rec: HitRecord,
        attenuation: &mut Color,
        scattered: &mut Ray,
    ) -> bool {
        let mut reflected = reflect(&r_in.direction(), &rec.normal);
        reflected = reflected.unit_vector() + (self.fuzz * random_unit_vector());
        *scattered = Ray::new(rec.p, reflected + self.fuzz * random_unit_vector());
        *attenuation = self.albedo;

        scattered.direction().dot(&rec.normal) > 0.0
    }
}

pub struct Dielectric {
    ir: f64,
}

impl Dielectric {
    pub const fn new(ir: f64) -> Self {
        Dielectric { ir }
    }

    fn reflectance(&self, cosine: f64, ref_idx: f64) -> f64 {
        // Schlick
        let mut r0 = (1.0 - ref_idx) / (1.0 + ref_idx);
        r0 = r0 * r0;

        return r0 + (1.0 - r0) * f64::powf(1.0 - cosine, 5.0);
    }
}

impl Material for Dielectric {
    fn scatter(
        &self,
        r_in: Ray,
        rec: HitRecord,
        attenuation: &mut Color,
        scattered: &mut Ray,
    ) -> bool {
        *attenuation = Color::new(1.0, 1.0, 1.0);
        let refraction_ratio: f64 = if rec.front_face {
            1.0 / self.ir
        } else {
            self.ir
        };

        let unit_direction = r_in.direction().unit_vector();
        let cos_theta = f64::min(-unit_direction.dot(&rec.normal), 1.0);
        let sin_theta = f64::sqrt(1.0 - cos_theta * cos_theta);

        let cannot_refract = (refraction_ratio * sin_theta) > 1.0;

        let direction = if cannot_refract
            || Dielectric::reflectance(self, cos_theta, refraction_ratio) > random_f64()
        {
            reflect(&unit_direction, &rec.normal)
        } else {
            refract(&unit_direction, &rec.normal, refraction_ratio)
        };

        *scattered = Ray::new(rec.p, direction);
        true
    }
}

pub struct Light {
    emit: Color
}

impl Light {
    pub const fn new(emit: Color) -> Self {
        Light { emit }
    }
}

impl Material for Light {
    fn scatter(
        &self,
        _r_in: Ray,
        _rec: HitRecord,
        _attenuation: &mut Color,
        _scattered: &mut Ray,
    ) -> bool {
        false // Light sources dont scatter rays
    }

    fn emitted(&self) -> Color {
        self.emit
    }
}