use core::ops::RangeInclusive;

use super::FormatError;

pub struct ScanCharSet {
    mask: [u64; 4],
    invert: bool,
}

impl ScanCharSet {
    pub fn new() -> Self {
        Self {
            mask: [0; 4],
            invert: false,
        }
    }

    pub fn from_format_iter<'a, I: Iterator<Item = &'a u8>>(
        mut it: I,
    ) -> Result<(I, Self), FormatError> {
        let mut maybe_close_bracket = true;
        let mut hyphen = false;
        let mut start: Option<u8> = None;
        let mut set = ScanCharSet::new();

        for &ch in it.by_ref() {
            if ch == b'^' && maybe_close_bracket {
                // "[^...]"
                set.set_invert();
                continue;
            }

            if ch == b']' {
                if !maybe_close_bracket {
                    if hyphen {
                        // "[...-]"
                        set.insert(b'-');
                    }
                    break;
                } else {
                    debug_assert!(!hyphen);
                    // "[]...]" or "[^]...]"
                    maybe_close_bracket = false;
                    set.insert(b']');
                    continue;
                }
            }

            maybe_close_bracket = false;

            if ch == b'-' {
                if hyphen {
                    // "--"?
                    return Err(FormatError);
                }

                hyphen = true;
                continue;
            }

            // Any other character
            if hyphen {
                let Some(start) = start.take() else {
                    // "-x" without start?
                    return Err(FormatError);
                };

                let same_category = (start.is_ascii_digit() && ch.is_ascii_digit())
                    || (start.is_ascii_uppercase() && ch.is_ascii_uppercase())
                    || (start.is_ascii_lowercase() && ch.is_ascii_lowercase());

                if same_category && ch > start {
                    hyphen = false;
                    set.insert_range(start..=ch);
                } else {
                    return Err(FormatError);
                }
            } else if let Some(_start) = start.replace(ch) {
                // "c" without range
                set.insert(ch);
            }
        }

        Ok((it, set))
    }

    pub fn set_invert(&mut self) {
        self.invert = true;
    }

    pub fn insert(&mut self, ch: u8) {
        let index = (ch / 64) as usize;
        let bit = 1 << (ch % 64);
        self.mask[index] |= bit;
    }

    pub fn insert_range(&mut self, range: RangeInclusive<u8>) {
        for ch in range {
            self.insert(ch);
        }
    }

    pub fn contains(&self, ch: u8) -> bool {
        let index = (ch / 64) as usize;
        let bit = 1 << (ch % 64);
        self.mask[index] & bit == bit
    }

    pub fn check(&self, ch: u8) -> bool {
        self.invert ^ self.contains(ch)
    }
}
