use crate::{diagnostic::span::Span, err::CompileError, ir::err::IRError, lexer::err::FrontendError};

pub mod span;

pub struct Diagnositics {
    diagnostics: Vec<Diagnostic>,
}
pub enum DiagnosticLevel {
    Error,
    Warning,
    Info,
}
pub struct Diagnostic {
    level: DiagnosticLevel,
    message: String,
    span: Span
}
impl Diagnositics {
    pub fn new() -> Self {
        Self { diagnostics: vec![] }
    }
    pub fn add(&mut self, diagnostic: Diagnostic) {
        self.diagnostics.push(diagnostic);
    }
    pub fn add_from_error(&mut self, error: impl Into<CompileError>, span: Span) {
        self.diagnostics.push(Diagnostic {
            level: DiagnosticLevel::Error,
            message: Into::<CompileError>::into(error).to_string(),
            span,
        });
    }
    pub fn add_from_frontend_error(&mut self, error: impl Into<FrontendError>, span: Span) {
        self.diagnostics.push(Diagnostic {
            level: DiagnosticLevel::Error,
            message: Into::<FrontendError>::into(error).to_string(),
            span,
        });
    }
    pub fn add_from_ir_error(&mut self, error: IRError, span: Span) {
        self.diagnostics.push(Diagnostic {
            level: DiagnosticLevel::Error,
            message: error.to_string(),
            span,
        });
    }
    pub fn is_empty(&self) -> bool {
        self.diagnostics.is_empty()
    }
    pub fn print(&self, name: &str, source: &str) {
        use codespan_reporting::diagnostic::Diagnostic as CodespanDiagnostic;
        use codespan_reporting::files::SimpleFile;
        use codespan_reporting::diagnostic::{Severity, Label};
        use std::io::IsTerminal;

        use codespan_reporting::term::{self, termcolor::{ColorChoice, StandardStream}};
        let mut choice = ColorChoice::Auto;
        if !std::io::stdin().is_terminal() {
            choice = ColorChoice::Never;
        }
        let stdout = StandardStream::stdout(choice);
        let source_file = SimpleFile::new(name, source);
        let output_config = codespan_reporting::term::Config::default();
        for diagnostic in &self.diagnostics {
            let output_level = match diagnostic.level {
                DiagnosticLevel::Error => Severity::Error,
                DiagnosticLevel::Warning => Severity::Warning,
                DiagnosticLevel::Info => Severity::Note,
            };
            let output_diagnostic = CodespanDiagnostic::new(output_level)
                .with_message(&diagnostic.message)
                .with_label(
                    Label::primary((), diagnostic.span.start..diagnostic.span.end)
                );
            term::emit_to_write_style(&mut stdout.lock(), &output_config, &source_file, &output_diagnostic);
            
        }
            
    }
}
#[cfg(test)]
mod tests {
    #[test]
    fn test_diagnostics() {
        use crate::diagnostic::{Diagnostic, DiagnosticLevel, Diagnositics};
        let mut diagnostics = Diagnositics::new();
        diagnostics.add(Diagnostic {
            level: DiagnosticLevel::Error,
            message: "test error".to_string(),
            span: crate::diagnostic::span::Span { start: 0, end: 3 },
        });
        diagnostics.add(Diagnostic {
            level: DiagnosticLevel::Error,
            message: "test error".to_string(),
            span: crate::diagnostic::span::Span { start: 16, end: 22 },
        });
        diagnostics.print("main.c", 
r#"int main(){
    return 1;
}"#);
    }
}
