
use itertools::Itertools;
use std::{ops::RangeInclusive, usize};

pub enum MaskPresets {
    Char(char),
    AnyCase,
    LowerCase,
    UpperCase,
    AllHumanReadable,
    Numbers,
    NumberRange(RangeInclusive<u8>),
    SpecialChars,
    WhiteSpace,
    LineTermination,
    NoChar,
}

#[derive(Debug, Clone)]
pub struct TransitionMask {
    mask: u128,
}

impl From<MaskPresets> for TransitionMask {
    fn from(value: MaskPresets) -> Self {
        let mut n: u128 = 0;

        let mut set_range = |r: RangeInclusive<i32>| {
            r.for_each(|i| n |= 1 << i);
        };

        match value {
            MaskPresets::NoChar => {}
            MaskPresets::AnyCase => {
                set_range(0x41..=0x5a); // UpperCase
                set_range(0x61..=0x7a); // LowerCase
            },
            MaskPresets::NumberRange(r) => {
                let start = (*r.start() as i32) + 0x30;
                let end = (*r.end() as i32) + 0x30;
                set_range(start..=end);
            }
            MaskPresets::LowerCase => {
                set_range(0x61..=0x7a); // LowerCase
            }
            MaskPresets::UpperCase => {
                set_range(0x41..=0x5a); // UpperCase
            }
            MaskPresets::AllHumanReadable => {
                set_range(0x20..=0x7e); // All Ascii Chars + Space + Tab
                n |= 1 << 0x9; // Tab
            }
            MaskPresets::Numbers => {
                set_range(0x30..=0x39); // Numbers
            }
            MaskPresets::SpecialChars => {
                set_range(0x21..=0x2F); // ! to /
                set_range(0x3A..=0x40); // : to @
                set_range(0x5B..=0x0D); // [ to `
                set_range(0x7B..=0x7E); // { to ~
            }
            MaskPresets::WhiteSpace => {
                n |= 1 << 0x9; // Tab
                n |= 1 << 0x20; // Spaces
            }
            MaskPresets::LineTermination => {
                n |= 1 << 0x0a; // Line Feed
                n |= 1 << 0x0d; // Carriage Return
            }
            MaskPresets::Char(c) => {
                if c.is_ascii() {
                    n |= 1 << (c as u32)
                }
            }
        }

        TransitionMask { mask: n }
    }
}

impl TransitionMask {
        pub fn new() -> Self {
            TransitionMask { mask: 0 }
        }

        pub fn or(&self, other: Self) -> TransitionMask {
            TransitionMask {
                mask: other.mask | self.mask,
            }
        }

        pub fn and(&self, other: Self) -> TransitionMask {
            TransitionMask {
                mask: other.mask & self.mask,
            }
        }

        pub fn inv(&self) -> TransitionMask {
            TransitionMask { mask: !self.mask }
        }

        pub fn get_bit(&self, i: usize) -> bool {
            (self.mask & (1 << i)) > 0
        }

        pub fn set_bit(&mut self, i: usize) {
            self.mask |= 1 << i;
        }

        pub fn to_string(&self) -> String {
            let mut res: Vec<String> = Vec::new();
            let mut strike: Option<usize> = None;

            fn num_to_str(i: usize) -> String {
                if (0x21..=0x7E).contains(&i) {
                    format!("'{}'", ((i as u8) as char).to_string())
                } else {
                    match i {
                        0x20 => "SP".to_string(),
                        0x0D => "LF".to_string(),
                        0x0A => "CR".to_string(),
                        0x09 => "TAB".to_string(),
                        _ => format!("{:02x}", i),
                    }
                }
            }

            for (p, n) in (0..128).tuple_windows() {
                match (strike, self.get_bit(p), self.get_bit(n)) {
                    (None, true, true) => {
                        // First two
                        strike = Some(p);
                    }
                    (None, true, false) => {
                        // First, only one
                        res.push(num_to_str(p));
                    }
                    (None, false, true) => {
                        // Strik Start
                        strike = Some(n)
                    }
                    (None, false, false) => {
                        // No Strike
                    }
                    (Some(_), true, true) => {
                        // Stike ongoing
                    }
                    (Some(s), true, false) => {
                        // Strik end
                        if s == p {
                            // Single Number
                            res.push(num_to_str(p));
                        } else {
                            // Range
                            res.push(format!("{}-{}", num_to_str(s), num_to_str(p)));
                        }

                        strike = None;
                    }
                    (Some(_), false, true) => {
                        // This should no happend
                        strike = Some(n);
                    }
                    (Some(_), false, false) => {
                        // Should also no happend
                        strike = None;
                    }
                }
            }

            match strike {
                None => (),
                Some(s) => {
                    if s == 127 {
                        res.push(num_to_str(s));
                    } else {
                        res.push(format!("{}-{}", num_to_str(s), num_to_str(127)));
                    }
                }
            };

            res.join(",")
        }
    }


mod test {
    use super::TransitionMask;

    #[test]
    fn transition_mask_to_string() {
        let mut mask1 = TransitionMask::new();

        mask1.set_bit(0);

        (10..=16).for_each(|i| {
            mask1.set_bit(i);
        });

        (50..=51).for_each(|i| {
            mask1.set_bit(i);
        });

        mask1.set_bit(60);
        mask1.set_bit(62);
        mask1.set_bit(64);

        (70..=73).for_each(|i| {
            mask1.set_bit(i);
        });

        (126..=127).for_each(|i| {
            mask1.set_bit(i);
        });

        println!("{}", mask1.to_string());

        assert_eq!(
            mask1.to_string(),
            "00,CR-10,'2'-'3','<','>','@','F'-'I','~'-7f"
        );
    }

}