use std::ops::Range;
use syntect::{
    highlighting::{
        FontStyle, HighlightState, Highlighter, RangedHighlightIterator, Style, ThemeSet,
    },
    parsing::{ParseState, ScopeStack, SyntaxSet},
};
use tui::text::{Span, Spans};

struct SyntaxLine {
    items: Vec<(Style, usize, Range<usize>)>,
}

pub struct SyntaxText {
    text: String,
    lines: Vec<SyntaxLine>,
}

impl SyntaxText {
    pub fn new(text: String) -> Self {
        let syntax_set: SyntaxSet = SyntaxSet::load_defaults_nonewlines();
        let theme_set: ThemeSet = ThemeSet::load_defaults();

        let mut state = ParseState::new(syntax_set.find_syntax_by_extension("sql").unwrap());
        let highlighter = Highlighter::new(&theme_set.themes["base16-eighties.dark"]);
        let mut syntax_lines: Vec<SyntaxLine> = Vec::new();
        let mut highlight_state = HighlightState::new(&highlighter, ScopeStack::new());

        for (number, line) in text.lines().enumerate() {
            let ops = state.parse_line(line, &syntax_set);
            let iter =
                RangedHighlightIterator::new(&mut highlight_state, &ops[..], line, &highlighter);

            syntax_lines.push(SyntaxLine {
                items: iter
                    .map(|(style, _, range)| (style, number, range))
                    .collect(),
            });
        }

        Self {
            text,
            lines: syntax_lines,
        }
    }

    pub fn convert(&self) -> tui::text::Text<'_> {
        let mut result_lines: Vec<Spans> = Vec::with_capacity(self.lines.len());

        for (syntax_line, line_content) in self.lines.iter().zip(self.text.lines()) {
            let mut line_span = Spans(Vec::with_capacity(syntax_line.items.len()));

            for (style, _, range) in &syntax_line.items {
                let item_content = &line_content[range.clone()];
                let item_style = syntact_style_to_tui(style);

                line_span.0.push(Span::styled(item_content, item_style));
            }

            result_lines.push(line_span);
        }

        result_lines.into()
    }
}

impl<'a> From<&'a SyntaxText> for tui::text::Text<'a> {
    fn from(v: &'a SyntaxText) -> Self {
        let mut result_lines: Vec<Spans> = Vec::with_capacity(v.lines.len());

        for (syntax_line, line_content) in v.lines.iter().zip(v.text.lines()) {
            let mut line_span = Spans(Vec::with_capacity(syntax_line.items.len()));

            for (style, _, range) in &syntax_line.items {
                let item_content = &line_content[range.clone()];
                let item_style = syntact_style_to_tui(style);

                line_span.0.push(Span::styled(item_content, item_style));
            }

            result_lines.push(line_span);
        }

        result_lines.into()
    }
}

fn syntact_style_to_tui(style: &Style) -> tui::style::Style {
    let mut res = tui::style::Style::default().fg(tui::style::Color::Rgb(
        style.foreground.r,
        style.foreground.g,
        style.foreground.b,
    ));

    if style.font_style.contains(FontStyle::BOLD) {
        res = res.add_modifier(tui::style::Modifier::BOLD);
    }
    if style.font_style.contains(FontStyle::ITALIC) {
        res = res.add_modifier(tui::style::Modifier::ITALIC);
    }
    if style.font_style.contains(FontStyle::UNDERLINE) {
        res = res.add_modifier(tui::style::Modifier::UNDERLINED);
    }

    res
}
