use super::{Logic, SignalValue};

/// Parse a VCD binary string like "b0101" or "b01xz" into a SignalValue.
pub fn parse_binary_str(s: &str) -> SignalValue {
    let s = s.strip_prefix('b').or_else(|| s.strip_prefix('B')).unwrap_or(s);
    if s.is_empty() {
        return SignalValue::Unknown;
    }

    let has_xz = s.chars().any(|c| c == 'x' || c == 'X' || c == 'z' || c == 'Z');
    if has_xz {
        let bits: Vec<Logic> = s
            .chars()
            .map(|c| match c {
                '0' => Logic::Zero,
                '1' => Logic::One,
                'x' | 'X' => Logic::X,
                'z' | 'Z' => Logic::Z,
                _ => Logic::X,
            })
            .collect();
        SignalValue::FourState(bits)
    } else {
        let mut val: u64 = 0;
        for c in s.chars() {
            val = (val << 1) | if c == '1' { 1 } else { 0 };
        }
        SignalValue::Scalar(val)
    }
}

/// Extract bits [high:low] from val.
pub fn bit_slice(val: u64, high: u32, low: u32) -> u64 {
    if high < low {
        return 0;
    }
    let width = high - low + 1;
    if width >= 64 {
        return val >> low;
    }
    let mask = (1u64 << width) - 1;
    (val >> low) & mask
}

/// Sign-extend a value of given bit width to i64.
pub fn sign_extend(val: u64, width: u32) -> i64 {
    if width == 0 || width > 64 {
        return val as i64;
    }
    if width == 64 {
        return val as i64;
    }
    let sign_bit = 1u64 << (width - 1);
    if val & sign_bit != 0 {
        let mask = !((1u64 << width) - 1);
        (val | mask) as i64
    } else {
        val as i64
    }
}

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

    #[test]
    fn test_parse_binary() {
        assert_eq!(parse_binary_str("b0101"), SignalValue::Scalar(5));
        assert_eq!(parse_binary_str("b0000"), SignalValue::Scalar(0));
        assert_eq!(parse_binary_str("b1111"), SignalValue::Scalar(15));
    }

    #[test]
    fn test_bit_slice() {
        assert_eq!(bit_slice(0b11010, 4, 1), 0b1101);
        assert_eq!(bit_slice(0xFF, 3, 0), 0xF);
    }

    #[test]
    fn test_sign_extend() {
        assert_eq!(sign_extend(0b1111, 4), -1i64);
        assert_eq!(sign_extend(0b0111, 4), 7i64);
    }
}
