use minify_html_common::gen::codepoints::Lookup;

pub mod bang;
pub mod comment;
pub mod content;
pub mod doctype;
pub mod element;
pub mod instruction;
pub mod script;
pub mod style;
#[cfg(test)]
mod tests;
pub mod textarea;
pub mod title;

#[derive(Default, Clone, Debug)]
pub struct ParseOpts {
    pub treat_brace_as_opaque: bool,
    pub treat_chevron_percent_as_opaque: bool,
}

pub struct Code<'c> {
    code: &'c [u8],
    next: usize,
    pub(crate) opts: ParseOpts,

    pub seen_html_open: bool,
    pub seen_head_open: bool,
    pub seen_head_close: bool,
    pub seen_body_open: bool,
}

#[derive(Copy, Clone)]
pub struct Checkpoint(usize);

impl<'c> Code<'c> {
    pub fn new_with_opts(code: &[u8], opts: ParseOpts) -> Code<'_> {
        Code {
            code,
            next: 0,
            opts,
            seen_html_open: false,
            seen_head_open: false,
            seen_head_close: false,
            seen_body_open: false,
        }
    }

    pub fn new(code: &[u8]) -> Code<'_> {
        Code::new_with_opts(code, ParseOpts::default())
    }

    pub fn as_slice(&self) -> &[u8] {
        &self.code[self.next..]
    }

    pub fn take_checkpoint(&self) -> Checkpoint {
        Checkpoint(self.next)
    }

    pub fn restore_checkpoint(&mut self, cp: Checkpoint) {
        self.next = cp.0;
    }

    pub fn at_end(&self) -> bool {
        debug_assert!(self.next <= self.code.len());
        self.next == self.code.len()
    }

    pub fn shift_if_next(&mut self, c: u8) -> bool {
        if self.code.get(self.next).filter(|&&n| n == c).is_some() {
            self.next += 1;
            true
        } else {
            false
        }
    }

    pub fn shift_if_next_seq_case_insensitive(&mut self, seq: &[u8]) -> bool {
        if self
            .code
            .get(self.next..self.next + seq.len())
            .filter(|n| n.eq_ignore_ascii_case(seq))
            .is_some()
        {
            self.next += seq.len();
            true
        } else {
            false
        }
    }

    pub fn shift_if_next_in_lookup(&mut self, lookup: &'static Lookup) -> Option<u8> {
        let c = self.code.get(self.next).filter(|&&n| lookup[n]).copied();
        if c.is_some() {
            self.next += 1;
        };
        c
    }

    pub fn shift_if_next_not_in_lookup(&mut self, lookup: &'static Lookup) -> Option<u8> {
        let c = self.code.get(self.next).filter(|&&n| !lookup[n]).copied();
        if c.is_some() {
            self.next += 1;
        };
        c
    }

    pub fn shift(&mut self, n: usize) {
        self.next += n;
    }

    pub fn slice_and_shift(&mut self, n: usize) -> &[u8] {
        let str = &self.code[self.next..self.next + n];
        self.next += n;
        str
    }

    pub fn copy_and_shift(&mut self, n: usize) -> Vec<u8> {
        self.slice_and_shift(n).to_vec()
    }

    pub fn copy_and_shift_while_in_lookup(&mut self, lookup: &'static Lookup) -> Vec<u8> {
        let mut len = 0;
        loop {
            match self.code.get(self.next + len) {
                Some(&c) if lookup[c] => len += 1,
                _ => break,
            };
        }
        self.copy_and_shift(len)
    }

    pub fn slice_and_shift_while_not_in_lookup(&mut self, lookup: &'static Lookup) -> &[u8] {
        let mut len = 0;
        loop {
            match self.code.get(self.next + len) {
                Some(&c) if !lookup[c] => len += 1,
                _ => break,
            };
        }
        self.slice_and_shift(len)
    }

    // Returns the last character matched.
    pub fn shift_while_in_lookup(&mut self, lookup: &'static Lookup) -> Option<u8> {
        let mut last: Option<u8> = None;
        loop {
            match self.code.get(self.next) {
                Some(&c) if lookup[c] => {
                    self.next += 1;
                    last = Some(c);
                }
                _ => break,
            };
        }
        last
    }

    pub fn rem(&self) -> usize {
        self.code.len() - self.next
    }
}
