use crate::core::geometry::Lerp;
use core::sync::atomic::{AtomicU64, Ordering as SyncOrdering};
use num_traits::{Num, PrimInt};
use std::hash::Hash;
use std::ops::{Add, Mul};
use std::sync::{Arc, Mutex};

use crate::core::image::DeviceImage;
use crate::core::light::LightTrait;
use crate::core::shape::Shape;
use crate::core::texture::GPUFloatTexture;
use crate::lights::*;
use crate::spectra::{DenselySampledSpectrum, RGBColorSpace};
use crate::utils::Ptr;

pub type Float = f32;

// #[derive(Copy, Clone, Debug)]
// pub struct Host;
//
// #[derive(Copy, Clone, Debug)]
// pub struct Device;
//
// pub trait Backend: Copy + Clone + 'static {
//     type ShapeRef: Copy + Clone;
//     type TextureRef: Copy + Clone;
//     type ImageRef: Copy + Clone;
//     type DenseSpectrumRef = Ptr<DenselySampledSpectrum>;
//     type ColorSpaceRef = Ptr<RGBColorSpace>;
//     type DiffuseLight: LightTrait;
//     type PointLight: LightTrait;
//     type UniformInfiniteLight: LightTrait;
//     type PortalInfiniteLight: LightTrait;
//     type ImageInfiniteLight: LightTrait;
//     type SpotLight: LightTrait;
// }
//
// impl Backend for Device {
//     type ShapeRef = Ptr<Shape>;
//     type TextureRef = Ptr<GPUFloatTexture>;
//     type ColorSpaceRef = Ptr<RGBColorSpace>;
//     type DenseSpectrumRef = Ptr<DenselySampledSpectrum>;
//     type ImageRef = Ptr<DeviceImage>;
//     type DiffuseLight = Ptr<DiffuseAreaLight<Device>>;
//     type PointLight = Ptr<PointLight<Device>>;
//     type UniformInfiniteLight = Ptr<UniformInfiniteLight<Device>>;
//     type PortalInfiniteLight = Ptr<PortalInfiniteLight<Device>>;
//     type ImageInfiniteLight = Ptr<ImageInfiniteLight<Device>>;
//     type SpotLight = Ptr<SpotLight<Device>>;
// }
//
#[cfg(not(feature = "use_f64"))]
pub type FloatBits = u32;

#[cfg(feature = "use_f64")]
pub type FloatBits = u64;

pub trait FloatBitOps: Copy {
    type Bits;

    fn to_bits_val(self) -> Self::Bits;
    fn exponent_val(self) -> i32;
    fn significand_val(self) -> Self::Bits;
    fn sign_bit_val(self) -> Self::Bits;
    fn from_bits_val(bits: Self::Bits) -> Self;
}

impl FloatBitOps for f32 {
    type Bits = u32;

    #[inline(always)]
    fn to_bits_val(self) -> u32 {
        self.to_bits()
    }

    // Shift 23, Mask 8 bits (0xFF), Bias 127
    #[inline(always)]
    fn exponent_val(self) -> i32 {
        let bits = self.to_bits();
        ((bits >> 23) & 0xFF) as i32 - 127
    }

    // Mask bottom 23 bits
    #[inline(always)]
    fn significand_val(self) -> u32 {
        self.to_bits() & ((1 << 23) - 1)
    }

    // Mask top bit (31)
    #[inline(always)]
    fn sign_bit_val(self) -> u32 {
        self.to_bits() & 0x8000_0000
    }

    #[inline(always)]
    fn from_bits_val(bits: u32) -> Self {
        Self::from_bits(bits)
    }
}

impl FloatBitOps for f64 {
    type Bits = u64;

    #[inline(always)]
    fn to_bits_val(self) -> u64 {
        self.to_bits()
    }

    // Shift 52, Mask 11 bits (0x7FF), Bias 1023
    #[inline(always)]
    fn exponent_val(self) -> i32 {
        let bits = self.to_bits();
        ((bits >> 52) & 0x7FF) as i32 - 1023
    }

    // Mask bottom 52 bits
    #[inline(always)]
    fn significand_val(self) -> u64 {
        self.to_bits() & ((1u64 << 52) - 1)
    }

    // Mask top bit (63)
    #[inline(always)]
    fn sign_bit_val(self) -> u64 {
        self.to_bits() & 0x8000_0000_0000_0000
    }

    #[inline(always)]
    fn from_bits_val(bits: u64) -> Self {
        Self::from_bits(bits)
    }
}

pub const MACHINE_EPSILON: Float = Float::EPSILON * 0.5;
pub const SHADOW_EPSILON: Float = 0.0001;
pub const ONE_MINUS_EPSILON: Float = 0.99999994;
pub const PI: Float = std::f32::consts::PI;
pub const INV_PI: Float = 0.318_309_886_183_790_671_54;
pub const INV_2_PI: Float = 0.159_154_943_091_895_335_77;
pub const INV_4_PI: Float = 0.079_577_471_545_947_667_88;
pub const PI_OVER_2: Float = 1.570_796_326_794_896_619_23;
pub const PI_OVER_4: Float = 0.785_398_163_397_448_309_61;
pub const SQRT_2: Float = 1.414_213_562_373_095_048_80;

#[inline]
pub fn gamma(n: i32) -> Float {
    n as Float * MACHINE_EPSILON / (1. - n as Float * MACHINE_EPSILON)
}

pub static RARE_EVENT_TOTAL_CALLS: AtomicU64 = AtomicU64::new(0);
pub static RARE_EVENT_CONDITION_MET: AtomicU64 = AtomicU64::new(0);

#[macro_export]
macro_rules! check_rare {
    ($frequency_threshold:expr, $condition:expr) => {
        use core::sync::atomic::{AtomicU64, Ordering as SyncOrdering};
        const CHECK_INTERVAL: u64 = 4096;

        let total_calls = RARE_EVENT_TOTAL_CALLS.fetch_add(1, SyncOrdering::Relaxed);

        if $condition {
            RARE_EVENT_CONDITION_MET.fetch_add(1, SyncOrdering::Relaxed);
        }

        if (total_calls + 1) % CHECK_INTERVAL == 0 {
            let met_count = RARE_EVENT_CONDITION_MET.load(SyncOrdering::Relaxed);
            if met_count > 0 {
                let frequency = met_count as f64 / (total_calls + 1) as f64;
                if frequency > $frequency_threshold {
                    panic!(
                        "Rare event occurred with frequency {} which is > threshold {}",
                        frequency, $frequency_threshold
                    );
                }
            }
        }
    };
}
