// SPDX-FileCopyrightText: 2023 CELESTIFYX Team
// SPDX-License-Identifier: GPL-3.0-or-later

use regex::{
    Captures,
    Regex
};

use crate::token::{
    Token,
    TokenType
};

use lazy_static::lazy_static;

lazy_static! {
    static ref COLOR_PATTERN: Regex = Regex::new(r"\{(\d)}").unwrap();
}

pub struct Tokenizer {
    line: String,
    pos: usize,
    color_pattern: &'static Regex
}

impl Tokenizer {
    pub fn new(line: String) -> Self {
        Self {
            line,
            pos: 0,
            color_pattern: &COLOR_PATTERN
        }
    }

    pub fn clone_tokenizer(&self) -> Self {
        Self {
            line: self.line.clone(),
            pos: self.pos,
            color_pattern: self.color_pattern
        }
    }

    pub fn true_length(&self) -> usize {
        let mut last_non_space: usize = 0;
        let mut total: usize = 0;
        let mut clone: Tokenizer = self.clone_tokenizer();

        while let Some(tok) = (&mut clone).next() {
            if tok.has_zero_width() {
                continue;
            }

            total += 1;

            if !tok.is_space() {
                last_non_space = total;
            }
        }

        last_non_space
    }

    pub fn leading_spaces(&self) -> usize {
        let mut count: usize = 0;
        let mut clone: Tokenizer = self.clone_tokenizer();

        while let Some(tok) = (&mut clone).next() {
            if tok.is_solid() {
                break;
            }

            if tok.is_space() {
                count += 1;
            }
        }

        count
    }

    pub fn truncate(&self, start: usize, end: usize) -> Vec<Token> {
        assert!(start <= end, "Start position must be <= end position");
        let width: usize = end - start;

        let mut remaining_start: usize = start;
        let mut result: Vec<Token> = Vec::new();
        let mut clone: Tokenizer = self.clone_tokenizer();
        let mut remaining_width: usize = width;

        while let Some(tok) = (&mut clone).next() {
            if tok.has_zero_width() {
                (&mut result).push(tok);
                continue;
            }

            if remaining_start > 0 {
                remaining_start -= 1;
                continue;
            }

            if remaining_width == 0 {
                break;
            }

            remaining_width -= 1;
            (&mut result).push(tok);
        }

        result
    }

    pub fn next(&mut self) -> Option<Token> {
        if self.pos >= self.line.len() {
            return None;
        }

        if let Some(mat) = self.color_pattern.find_at(&self.line, self.pos) {
            if mat.start() == self.pos {
                let captures: Captures = self.color_pattern.captures_at(&self.line, self.pos)?;
                let digit: String = captures.get(1)?.as_str().to_string();

                self.pos = mat.end();
                return Some(Token::new(TokenType::Color, Some(digit)));
            }
        }

        let ch: char = self.line[self.pos..].chars().next()?;
        self.pos += ch.len_utf8();

        match ch {
            ' '  => Some(Token::new(TokenType::Space, None)),
            '\n' => Some(Token::new(TokenType::Newline, None)),

            _    => Some(Token::new(TokenType::Char, Some(ch.to_string())))
        }
    }
}

impl Iterator for Tokenizer {
    type Item = Token;

    fn next(&mut self) -> Option<Self::Item> {
        Tokenizer::next(self)
    }
}
