use crate::core::pbrt::Float;
use crate::utils::math::{next_float_down, next_float_up};
use core::ops::{Add, Div, Mul, Neg, Sub};
use num_traits::Zero;

#[repr(C)]
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct Interval {
    pub low: Float,
    pub high: Float,
}

impl Interval {
    pub fn new(v: Float) -> Self {
        Self { low: v, high: v }
    }

    pub fn new_from_endpoints(v1: Float, v2: Float) -> Self {
        if v1 < v2 {
            Self { low: v1, high: v2 }
        } else {
            Self { low: v2, high: v1 }
        }
    }

    pub fn new_from_value_and_error(val: Float, err: Float) -> Self {
        Self {
            low: val - err,
            high: val + err,
        }
    }

    pub fn width(&self) -> Float {
        self.high - self.low
    }

    pub fn midpoint(&self) -> Float {
        (self.low + self.high) / 2.0
    }

    pub fn contains(&self, v: Float) -> bool {
        v >= self.low && v <= self.high
    }

    pub fn is_empty(&self) -> bool {
        self.low > self.high
    }

    pub fn abs(&self) -> Self {
        if self.low >= 0.0 {
            return *self;
        }

        if self.high < 0.0 {
            return -(*self);
        }

        Self {
            low: 0.0,
            high: next_float_up((-self.low).max(self.high)),
        }
    }
}

impl Default for Interval {
    fn default() -> Self {
        Self {
            low: Float::INFINITY,
            high: Float::NEG_INFINITY,
        }
    }
}

impl Add for Interval {
    type Output = Self;
    fn add(self, rhs: Self) -> Self::Output {
        Self {
            low: next_float_down(self.low + rhs.low),
            high: next_float_up(self.high + rhs.high),
        }
    }
}

impl Add<Interval> for Float {
    type Output = Interval;
    fn add(self, rhs: Interval) -> Self::Output {
        Interval::new(self) + rhs
    }
}

impl Sub<Interval> for Float {
    type Output = Interval;
    fn sub(self, rhs: Interval) -> Self::Output {
        Interval::new(self) - rhs
    }
}

impl Sub for Interval {
    type Output = Self;
    fn sub(self, rhs: Self) -> Self::Output {
        Self {
            low: next_float_down(self.low - rhs.high),
            high: next_float_up(self.high - rhs.low),
        }
    }
}

impl Mul for Interval {
    type Output = Self;
    fn mul(self, rhs: Self) -> Self::Output {
        let prods = [
            self.low * rhs.low,
            self.high * rhs.low,
            self.low * rhs.high,
            self.high * rhs.high,
        ];

        Self {
            low: next_float_down(prods[0].min(prods[1]).min(prods[2]).min(prods[3])),
            high: next_float_up(prods[0].max(prods[1]).max(prods[2]).max(prods[3])),
        }
    }
}

impl Mul<Float> for Interval {
    type Output = Self;
    fn mul(self, rhs: Float) -> Self::Output {
        if rhs > 0.0 {
            Self {
                low: next_float_down(self.low * rhs),
                high: next_float_up(self.high * rhs),
            }
        } else {
            Self {
                low: next_float_down(self.high * rhs),
                high: next_float_up(self.low * rhs),
            }
        }
    }
}

impl Mul<Interval> for Float {
    type Output = Interval;
    fn mul(self, rhs: Interval) -> Self::Output {
        rhs * self
    }
}

impl Div for Interval {
    type Output = Self;
    fn div(self, rhs: Self) -> Self::Output {
        if rhs.contains(0.0) {
            return Interval {
                low: Float::NEG_INFINITY,
                high: Float::INFINITY,
            };
        }

        self * Interval::new_from_endpoints(1.0 / rhs.low, 1.0 / rhs.high)
    }
}

impl Div<Float> for Interval {
    type Output = Self;
    fn div(self, rhs: Float) -> Self::Output {
        if rhs > 0.0 {
            Self {
                low: next_float_down(self.low / rhs),
                high: next_float_up(self.high / rhs),
            }
        } else {
            Self {
                low: next_float_down(self.high / rhs),
                high: next_float_up(self.low / rhs),
            }
        }
    }
}

impl Neg for Interval {
    type Output = Self;
    fn neg(self) -> Self::Output {
        Self {
            low: next_float_down(-self.high),
            high: next_float_up(-self.low),
        }
    }
}

impl Zero for Interval {
    fn zero() -> Self {
        Self::new(0.0)
    }
    fn is_zero(&self) -> bool {
        self.low == 0.0 && self.high == 0.0
    }
}

impl From<Interval> for Float {
    fn from(interval: Interval) -> Self {
        interval.midpoint()
    }
}
