// SPDX-FileCopyrightText: 2024 Aurélien Gâteau <mail@agateau.com>
//
// SPDX-License-Identifier: Apache-2.0

use nu_ansi_term::Color::{Blue, Cyan, DarkGray, Green, Magenta, Yellow};
use nu_ansi_term::Style;

use crate::parse::{RegexNode, RegexTree, Token};

struct ColorTheme {
    symbol: Style,
    escaped: Style,
    group_name: Style,
    group_line: Style,
    quantifier: Style,
    alternative: Style,
}

impl Default for ColorTheme {
    fn default() -> Self {
        ColorTheme {
            symbol: Green.bold(),
            escaped: Yellow.into(),
            group_name: Blue.into(),
            group_line: DarkGray.into(),
            quantifier: Cyan.into(),
            alternative: Magenta.bold(),
        }
    }
}

struct Printer<'a> {
    theme: ColorTheme,
    tree: &'a RegexTree,
}

/// Used by print_text() to decide how to colorize individual, non-escaped characters
type StyleFn = fn(&ColorTheme, char, usize, usize) -> Option<Style>;

fn get_style_in_literal(theme: &ColorTheme, ch: char, _idx: usize, _len: usize) -> Option<Style> {
    if ch == '.' {
        Some(theme.symbol)
    } else {
        None
    }
}

fn get_style_in_set(theme: &ColorTheme, ch: char, idx: usize, len: usize) -> Option<Style> {
    match ch {
        // '^' at start negates the set
        '^' if idx == 0 => Some(theme.symbol),
        // '-' not at start or end indicates a range
        '-' if idx > 0 && idx < len - 1 => Some(theme.symbol),
        _ => None,
    }
}

impl<'a> Printer<'a> {
    fn new(theme: ColorTheme, tree: &'a RegexTree) -> Self {
        Printer { theme, tree }
    }

    fn print_indent(&self, depth: i64) {
        for _ in 0..depth {
            print!("{} ", self.theme.group_line.paint("┆"));
        }
    }

    fn print_text(&self, text: &str, style_fn: StyleFn) {
        let mut idx = 0;
        let len = text.len();

        while idx < len {
            let ch = text.chars().nth(idx).unwrap();
            match ch {
                '\\' => {
                    print!("{}", self.theme.escaped.paint(&text[idx..idx + 2]));
                    idx += 1;
                }
                _ => match style_fn(&self.theme, ch, idx, len) {
                    Some(color) => print!("{}", color.paint(format!("{}", ch))),
                    None => print!("{}", ch),
                },
            }
            idx += 1;
        }
    }

    fn print_node_as_tree(&self, node: &RegexNode, depth: i64) {
        self.print_indent(depth);
        match node.get_value().unwrap() {
            Token::Group { header, quantifier } => {
                println!(
                    "{}{}",
                    self.theme.symbol.paint("("),
                    self.theme.group_name.paint(header)
                );

                self.print_children_as_tree(node, depth + 1);

                self.print_indent(depth);
                println!(
                    "{}{}",
                    self.theme.symbol.paint(")"),
                    self.theme.quantifier.paint(&quantifier)
                );
            }
            Token::Literal { text, quantifier } => {
                self.print_text(&text, get_style_in_literal);
                println!("{}", self.theme.quantifier.paint(&quantifier));
            }
            Token::Set { text, quantifier } => {
                print!("{}", self.theme.symbol.paint("["));
                self.print_text(&text, get_style_in_set);
                println!(
                    "{}{}",
                    self.theme.symbol.paint("]"),
                    self.theme.quantifier.paint(&quantifier)
                );
            }
            Token::Alternative => {
                println!("{}", self.theme.alternative.paint("|"));
            }
            Token::Edge { symbol } => {
                println!("{}", self.theme.symbol.paint(symbol));
            }
        }
    }

    fn print_children_as_tree(&self, parent: &RegexNode, depth: i64) {
        for id in parent.get_children_ids() {
            let child = self.tree.get_node_by_id(&id).unwrap();
            self.print_node_as_tree(&child, depth);
        }
    }

    fn print_node(&self, node: &RegexNode) {
        match node.get_value().unwrap() {
            Token::Group { header, quantifier } => {
                print!(
                    "{}{}",
                    self.theme.symbol.paint("("),
                    self.theme.group_name.paint(header)
                );

                self.print_children(node);
                print!(
                    "{}{}",
                    self.theme.symbol.paint(")"),
                    self.theme.quantifier.paint(&quantifier)
                );
            }
            Token::Literal { text, quantifier } => {
                self.print_text(&text, get_style_in_literal);
                print!("{}", self.theme.quantifier.paint(&quantifier));
            }
            Token::Set { text, quantifier } => {
                print!("{}", self.theme.symbol.paint("["));
                self.print_text(&text, get_style_in_set);
                print!(
                    "{}{}",
                    self.theme.symbol.paint("]"),
                    self.theme.quantifier.paint(&quantifier)
                );
            }
            Token::Alternative => {
                print!("{}", self.theme.alternative.paint("|"));
            }
            Token::Edge { symbol } => {
                print!("{}", self.theme.symbol.paint(symbol));
            }
        }
    }

    fn print_children(&self, parent: &RegexNode) {
        for id in parent.get_children_ids() {
            let child = self.tree.get_node_by_id(&id).unwrap();
            self.print_node(&child);
        }
    }
}

pub fn print_tree(tree: &RegexTree) {
    let theme = ColorTheme::default();
    let printer = Printer::new(theme, tree);
    printer.print_children_as_tree(&tree.get_root_node().unwrap(), 0);
}

pub fn print(tree: &RegexTree) {
    let theme = ColorTheme::default();
    let printer = Printer::new(theme, tree);
    printer.print_children(&tree.get_root_node().unwrap());
    println!();
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::parse::parse_regex;
    use yare::parameterized;

    #[parameterized(
        badly_parsed_escape = { "\\s*" }
    )]
    fn smoke_test(regex: &str) {
        let tree = parse_regex(&regex).unwrap();
        print_tree(&tree);
    }
}
