use log::error;

const EOL_CHARS: [u8; 2] = [b'\r', b'\n'];
const QUOTE_CHARS: [u8; 2] = [b'\'', b'"'];
const CONS_CHARS: [u8; 3] = [b'(', b'.', b')'];
const SPACE_CHARS: [u8; 2] = [b' ', b'\t'];

pub const START_CONS: Token = Token {
    bytes: &[b'('],
    index: 0,
};
pub const DOT_CONS: Token = Token {
    bytes: &[b'.'],
    index: 0,
};
pub const END_CONS: Token = Token {
    bytes: &[b')'],
    index: 0,
};
#[derive(Debug)]
pub struct Token<'a> {
    pub bytes: &'a [u8],
    pub index: usize,
}
impl PartialEq for Token<'_> {
    fn eq(&self, other: &Self) -> bool {
        self.bytes == other.bytes
    }
}
impl Eq for Token<'_> {}
pub struct Reader<'a> {
    stream: &'a [u8],
    pub index: usize,
}
impl<'a> Reader<'a> {
    pub fn new(stream: &'a [u8]) -> Self {
        Self { stream, index: 0 }
    }
    pub fn consume_whitespace(&mut self) {
        loop {
            for c in &self.stream[self.index..] {
                if SPACE_CHARS.contains(c) {
                    self.index += 1;
                } else {
                    break;
                }
            }
            if self.index >= self.stream.len() || self.stream[self.index] != b';' {
                break;
            }
            for c in &self.stream[self.index..] {
                if EOL_CHARS.contains(c) {
                    self.index += 1;
                } else {
                    break;
                }
            }
        }
    }
    pub fn consume_until_whitespace(&mut self) {
        for c in &self.stream[self.index..] {
            match c {
                b' ' | b'\t' | b')' => {
                    break;
                }
                _ => {
                    self.index += 1;
                }
            }
        }
    }
}
impl<'a> Iterator for Reader<'a> {
    type Item = Token<'a>;
    fn next(&mut self) -> Option<Self::Item> {
        self.consume_whitespace();
        if self.stream.len() <= self.index {
            None
        } else {
            let chr = &self.stream[self.index];
            if CONS_CHARS.contains(chr) {
                let token = Token {
                    bytes: &self.stream[self.index..self.index + 1],
                    index: self.index,
                };
                self.index += 1;
                Some(token)
            } else if QUOTE_CHARS.contains(chr) {
                let start = self.index;
                let mut bs = false;
                self.index += 1;
                loop {
                    if self.stream.len() <= self.index {
                        error!("ERROR: Unterminated String at {}, ", start);
                        return None;
                    } else if bs {
                        bs = false;
                        self.index += 1;
                    } else if self.stream[self.index] == b'\\' {
                        bs = true;
                    } else if self.stream[self.index] == *chr {
                        self.index += 1;
                        return Some(Token {
                            bytes: &self.stream[start..self.index],
                            index: start,
                        });
                    } else {
                        self.index += 1;
                    }
                }
            } else {
                let start = self.index;
                self.consume_until_whitespace();
                Some(Token {
                    bytes: &self.stream[start..self.index],
                    index: start,
                })
            }
        }
    }
}
