//! Null sentinel constants and SIMD-accelerated null-detection helpers for q IPC types.
//!
//! In q's IPC protocol each fixed-width primitive has a dedicated sentinel value
//! that represents a missing (null) element.  These constants are consumed by
//! both the Arrow projection layer and any serialisation code that needs to
//! round-trip q nullability semantics.
//!
//! Each `validity_*` function returns `None` when the slice contains no nulls
//! (the fast path: callers can skip building a null buffer entirely) or
//! `Some(Vec<bool>)` where `true` means the element is valid.  The null check
//! uses `portable_simd` for throughput; the validity-vector build falls back to
//! a scalar loop because nulls are the uncommon case.

use std::simd::prelude::*;

/// Null sentinel for q short (i16).
pub const Q_NULL_SHORT: i16 = i16::MIN;

/// Null sentinel for q int (i32).
pub const Q_NULL_INT: i32 = i32::MIN;

/// Null sentinel for q long (i64).
pub const Q_NULL_LONG: i64 = i64::MIN;

/// Null sentinel for q timestamp (i64 nanoseconds since 2000.01.01).
pub const Q_NULL_TIMESTAMP: i64 = i64::MIN;

/// Null sentinel for q month (i32 months since 2000.01).
pub const Q_NULL_MONTH: i32 = i32::MIN;

/// Null sentinel for q date (i32 days since 2000.01.01).
pub const Q_NULL_DATE: i32 = i32::MIN;

/// Null sentinel for q timespan (i64 nanoseconds).
pub const Q_NULL_TIMESPAN: i64 = i64::MIN;

/// Null sentinel for q minute (i32 minutes).
pub const Q_NULL_MINUTE: i32 = i32::MIN;

/// Null sentinel for q second (i32 seconds).
pub const Q_NULL_SECOND: i32 = i32::MIN;

/// Null sentinel for q time (i32 milliseconds).
pub const Q_NULL_TIME: i32 = i32::MIN;

/// Byte value used to encode a null boolean in the raw q IPC wire format.
/// `0` = false, `1` = true, `2` = null.
pub const Q_NULL_BOOLEAN_WIRE: u8 = 2;

// ---------------------------------------------------------------------------
// Infinity sentinel constants
// ---------------------------------------------------------------------------

/// Positive infinity sentinel for q short (i16).
pub const Q_INF_SHORT: i16 = i16::MAX;
/// Negative infinity sentinel for q short (i16).
pub const Q_NINF_SHORT: i16 = i16::MIN + 1;

/// Positive infinity sentinel for q int (i32).
pub const Q_INF_INT: i32 = i32::MAX;
/// Negative infinity sentinel for q int (i32).
pub const Q_NINF_INT: i32 = i32::MIN + 1;

/// Positive infinity sentinel for q long (i64).
pub const Q_INF_LONG: i64 = i64::MAX;
/// Negative infinity sentinel for q long (i64).
pub const Q_NINF_LONG: i64 = i64::MIN + 1;

/// Positive infinity sentinel for q real (f32).
pub const Q_INF_REAL: f32 = f32::INFINITY;
/// Negative infinity sentinel for q real (f32).
pub const Q_NINF_REAL: f32 = f32::NEG_INFINITY;

/// Positive infinity sentinel for q float (f64).
pub const Q_INF_FLOAT: f64 = f64::INFINITY;
/// Negative infinity sentinel for q float (f64).
pub const Q_NINF_FLOAT: f64 = f64::NEG_INFINITY;

/// Positive infinity sentinel for q timestamp (i64 nanoseconds).
pub const Q_INF_TIMESTAMP: i64 = i64::MAX;
/// Negative infinity sentinel for q timestamp (i64 nanoseconds).
pub const Q_NINF_TIMESTAMP: i64 = i64::MIN + 1;

/// Positive infinity sentinel for q timespan (i64 nanoseconds).
pub const Q_INF_TIMESPAN: i64 = i64::MAX;
/// Negative infinity sentinel for q timespan (i64 nanoseconds).
pub const Q_NINF_TIMESPAN: i64 = i64::MIN + 1;

/// Positive infinity sentinel for q date (i32 days).
pub const Q_INF_DATE: i32 = i32::MAX;
/// Negative infinity sentinel for q date (i32 days).
pub const Q_NINF_DATE: i32 = i32::MIN + 1;

/// Positive infinity sentinel for q month (i32 months).
pub const Q_INF_MONTH: i32 = i32::MAX;
/// Negative infinity sentinel for q month (i32 months).
pub const Q_NINF_MONTH: i32 = i32::MIN + 1;

/// Positive infinity sentinel for q minute (i32 minutes).
pub const Q_INF_MINUTE: i32 = i32::MAX;
/// Negative infinity sentinel for q minute (i32 minutes).
pub const Q_NINF_MINUTE: i32 = i32::MIN + 1;

/// Positive infinity sentinel for q second (i32 seconds).
pub const Q_INF_SECOND: i32 = i32::MAX;
/// Negative infinity sentinel for q second (i32 seconds).
pub const Q_NINF_SECOND: i32 = i32::MIN + 1;

/// Positive infinity sentinel for q time (i32 milliseconds).
pub const Q_INF_TIME: i32 = i32::MAX;
/// Negative infinity sentinel for q time (i32 milliseconds).
pub const Q_NINF_TIME: i32 = i32::MIN + 1;

// ---------------------------------------------------------------------------
// SIMD null-detection helpers
// ---------------------------------------------------------------------------

/// Returns a validity vector for a `&[i16]` slice using [`Q_NULL_SHORT`] as
/// the sentinel.  Returns `None` when no nulls are present.
#[inline]
pub fn validity_i16(values: &[i16]) -> Option<Vec<bool>> {
    const N: usize = 32;
    let sentinel = Simd::<i16, N>::splat(Q_NULL_SHORT);
    let n_aligned = (values.len() / N) * N;

    let has_null = values[..n_aligned]
        .chunks_exact(N)
        .any(|c| Simd::<i16, N>::from_slice(c).simd_eq(sentinel).any())
        || values[n_aligned..].iter().any(|&v| v == Q_NULL_SHORT);

    if !has_null {
        return None;
    }
    Some(values.iter().map(|&v| v != Q_NULL_SHORT).collect())
}

/// Returns a validity vector for a `&[i32]` slice using the supplied sentinel.
/// Returns `None` when no nulls are present.
#[inline]
pub fn validity_i32(values: &[i32], sentinel: i32) -> Option<Vec<bool>> {
    const N: usize = 16;
    let sentinel_v = Simd::<i32, N>::splat(sentinel);
    let n_aligned = (values.len() / N) * N;

    let has_null = values[..n_aligned]
        .chunks_exact(N)
        .any(|c| Simd::<i32, N>::from_slice(c).simd_eq(sentinel_v).any())
        || values[n_aligned..].iter().any(|&v| v == sentinel);

    if !has_null {
        return None;
    }
    Some(values.iter().map(|&v| v != sentinel).collect())
}

/// Returns a validity vector for a `&[i64]` slice using the supplied sentinel.
/// Returns `None` when no nulls are present.
#[inline]
pub fn validity_i64(values: &[i64], sentinel: i64) -> Option<Vec<bool>> {
    const N: usize = 8;
    let sentinel_v = Simd::<i64, N>::splat(sentinel);
    let n_aligned = (values.len() / N) * N;

    let has_null = values[..n_aligned]
        .chunks_exact(N)
        .any(|c| Simd::<i64, N>::from_slice(c).simd_eq(sentinel_v).any())
        || values[n_aligned..].iter().any(|&v| v == sentinel);

    if !has_null {
        return None;
    }
    Some(values.iter().map(|&v| v != sentinel).collect())
}

/// Returns a validity vector for a `&[f32]` slice where `NaN` encodes null.
/// Returns `None` when no nulls are present.
#[inline]
pub fn validity_f32(values: &[f32]) -> Option<Vec<bool>> {
    const N: usize = 16;
    let n_aligned = (values.len() / N) * N;

    // NaN is the only value not equal to itself.
    let has_null = values[..n_aligned].chunks_exact(N).any(|c| {
        let v = Simd::<f32, N>::from_slice(c);
        v.simd_ne(v).any()
    }) || values[n_aligned..].iter().any(|v| v.is_nan());

    if !has_null {
        return None;
    }
    Some(values.iter().map(|v| !v.is_nan()).collect())
}

/// Returns a validity vector for a `&[f64]` slice where `NaN` encodes null.
/// Returns `None` when no nulls are present.
#[inline]
pub fn validity_f64(values: &[f64]) -> Option<Vec<bool>> {
    const N: usize = 8;
    let n_aligned = (values.len() / N) * N;

    let has_null = values[..n_aligned].chunks_exact(N).any(|c| {
        let v = Simd::<f64, N>::from_slice(c);
        v.simd_ne(v).any()
    }) || values[n_aligned..].iter().any(|v| v.is_nan());

    if !has_null {
        return None;
    }
    Some(values.iter().map(|v| !v.is_nan()).collect())
}

#[cfg(test)]
mod tests {
    use super::*;

    // validity_i16
    #[test]
    fn i16_no_nulls() {
        assert_eq!(validity_i16(&[1, 2, 3, 4, 5]), None);
    }

    #[test]
    fn i16_with_null() {
        assert_eq!(
            validity_i16(&[1, Q_NULL_SHORT, 3]),
            Some(vec![true, false, true])
        );
    }

    #[test]
    fn i16_all_nulls() {
        assert_eq!(validity_i16(&[Q_NULL_SHORT; 4]), Some(vec![false; 4]));
    }

    #[test]
    fn i16_empty() {
        assert_eq!(validity_i16(&[]), None);
    }

    #[test]
    fn i16_single_null() {
        assert_eq!(validity_i16(&[Q_NULL_SHORT]), Some(vec![false]));
    }

    #[test]
    fn i16_null_in_remainder() {
        let mut data: Vec<i16> = (1..=9).collect();
        data[8] = Q_NULL_SHORT;
        let v = validity_i16(&data).unwrap();
        assert!(!v[8]);
        assert!(v[0]);
    }

    // validity_i32
    #[test]
    fn i32_no_nulls() {
        assert_eq!(validity_i32(&[1, 2, 3], Q_NULL_INT), None);
    }

    #[test]
    fn i32_with_null() {
        assert_eq!(
            validity_i32(&[1, Q_NULL_INT, 3], Q_NULL_INT),
            Some(vec![true, false, true])
        );
    }

    #[test]
    fn i32_empty() {
        assert_eq!(validity_i32(&[], Q_NULL_INT), None);
    }

    #[test]
    fn i32_all_nulls() {
        assert_eq!(
            validity_i32(&[Q_NULL_INT; 3], Q_NULL_INT),
            Some(vec![false; 3])
        );
    }

    #[test]
    fn i32_null_in_remainder() {
        let mut data: Vec<i32> = (1..=10).collect();
        data[9] = Q_NULL_INT;
        assert!(!validity_i32(&data, Q_NULL_INT).unwrap()[9]);
    }

    // validity_i64
    #[test]
    fn i64_no_nulls() {
        assert_eq!(validity_i64(&[1, 2, 3], Q_NULL_LONG), None);
    }

    #[test]
    fn i64_with_null() {
        assert_eq!(
            validity_i64(&[1, Q_NULL_LONG, 3], Q_NULL_LONG),
            Some(vec![true, false, true])
        );
    }

    #[test]
    fn i64_empty() {
        assert_eq!(validity_i64(&[], Q_NULL_LONG), None);
    }

    #[test]
    fn i64_timestamp_sentinel() {
        assert_eq!(
            validity_i64(&[100, Q_NULL_TIMESTAMP, 300], Q_NULL_TIMESTAMP),
            Some(vec![true, false, true])
        );
    }

    // validity_f32
    #[test]
    fn f32_no_nulls() {
        assert_eq!(validity_f32(&[1.0, 2.0, 3.0]), None);
    }

    #[test]
    fn f32_with_nan() {
        assert_eq!(
            validity_f32(&[1.0, f32::NAN, 3.0]),
            Some(vec![true, false, true])
        );
    }

    #[test]
    fn f32_empty() {
        assert_eq!(validity_f32(&[]), None);
    }

    #[test]
    fn f32_infinity_is_not_null() {
        assert_eq!(validity_f32(&[f32::INFINITY, f32::NEG_INFINITY, 1.0]), None);
    }

    // validity_f64
    #[test]
    fn f64_no_nulls() {
        assert_eq!(validity_f64(&[1.0, 2.0, 3.0]), None);
    }

    #[test]
    fn f64_with_nan() {
        assert_eq!(
            validity_f64(&[1.0, f64::NAN, 3.0]),
            Some(vec![true, false, true])
        );
    }

    #[test]
    fn f64_empty() {
        assert_eq!(validity_f64(&[]), None);
    }

    #[test]
    fn f64_infinity_is_not_null() {
        assert_eq!(validity_f64(&[f64::INFINITY, f64::NEG_INFINITY, 1.0]), None);
    }

    #[test]
    fn f64_large_aligned_with_null() {
        let mut data = vec![1.0; 8];
        data[7] = f64::NAN;
        let v = validity_f64(&data).unwrap();
        assert!(v[0]);
        assert!(!v[7]);
    }
}
