#![feature(result_option_inspect)]
#![feature(box_patterns)]
#![feature(default_free_fn)]
#![allow(non_snake_case)]

use std::{fmt::Display, path::Path, process::exit};

use ast::IDLModule;
use codegen::generate;
use codespan_reporting::{
    diagnostic::{Diagnostic, Label, Severity},
    files::SimpleFile,
    term::{
        emit,
        termcolor::{StandardStream, StandardStreamLock},
        Config,
    },
};
use lexer::{NumberSuffix, Token};
use parser::TokenIterator;

use crate::lexer::Spanned;

mod ast;
mod codegen;
mod lexer;
mod parser;

fn precheck<N: Display + Clone, S: AsRef<str>>(
    writer: &mut StandardStreamLock<'_>,
    config: &Config,
    file: &SimpleFile<N, S>,
) {
    let mut lexer = TokenIterator::new(file.source().as_ref());
    let mut diagnostics = vec![];
    let mut previous = lexer.peek().ok().map(|Spanned(a, b)| Spanned(a.clone(), b));

    while let Ok(Spanned(token, span)) = lexer.next() {
        let prev = Spanned(token.clone(), span.clone());
        match token {
            Token::Ident(lexer::Ident::Other(t)) if t == "Type" => {
                diagnostics.push(
                    Diagnostic::error()
                        .with_labels(vec![Label::primary((), span.0)])
                        .with_message("`Type` is not supported anymore.")
                        .with_notes(vec!["use `Alias` instead of `Type`".into()]),
                );
            }
            Token::Ident(lexer::Ident::Other(ident))
                if lexer
                    .peek()
                    .is_ok_and(|Spanned(a, _)| matches!(a, Token::LeftCurly))
                    && previous.is_some_and(|Spanned(ref a, _)| matches!(a, Token::Equals)) =>
            {
                diagnostics.push(
                    Diagnostic::error()
                        .with_message("Unknown expression")
                        .with_labels(vec![Label::primary((), span.0.clone())])
                        .with_notes(vec![
                            format!("add `Make` before the structure name to create a Make expression that will construct the `{ident}` structure"
                                ),
                        ]),
                );
            }
            Token::Ident(lexer::Ident::Other(ident))
                if NumberSuffix::ALL_SUFFIXES.contains(&ident.to_lowercase().as_str()) =>
            {
                diagnostics.push(
                    Diagnostic::warning()
                        .with_message("Potentially invalid use of an uppercased number type")
                        .with_labels(vec![Label::primary((), span.0)])
                        .with_notes(vec![
                            format!("Replace {ident} with {}", ident.to_lowercase()),
                            "Code generation might fail".into(),
                        ]),
                );
            }
            _ => {}
        }
        previous = Some(prev);
    }

    if !diagnostics.is_empty() {
        let mut was_fatal = false;
        for diagnostic in diagnostics {
            if let Severity::Error | Severity::Bug = &diagnostic.severity {
                was_fatal = true;
            }

            emit(writer, config, file, &diagnostic).unwrap();
        }

        if was_fatal {
            exit(1);
        }
    }
}

fn main() {
    let mut args = std::env::args();
    args.next().unwrap();

    let mut ast: Option<IDLModule> = None;

    if let Some(file) = args.next() {
        let path = Path::new(&file);
        let codespan_file = codespan_reporting::files::SimpleFile::new(
            &file,
            std::fs::read_to_string(path).unwrap(),
        );
        let writer = StandardStream::stdout(codespan_reporting::term::termcolor::ColorChoice::Auto);
        let config = Config {
            tab_width: 2,
            ..Default::default()
        };

        precheck(&mut writer.lock(), &config, &codespan_file);

        match parser::parse(codespan_file.source()) {
            Ok(ast_) => {
                println!("{:#?}", ast_);
                ast = Some(ast_);
            }
            Err(e) => {
                let msg = e.to_string();
                let label = match e {
                    parser::ParserError::UnexpectedEOF => Label::primary(
                        (),
                        (codespan_file.source().len() - 1)..codespan_file.source().len(),
                    )
                    .with_message("Unexpected end of file here"),
                    parser::ParserError::Unexpected(expected, Spanned(got, span)) => {
                        Label::primary((), span.0)
                            .with_message(format!("Unexpected `{got}`, expected {expected}"))
                    }
                    parser::ParserError::PleaseStopParsingUse => unsafe {
                        std::hint::unreachable_unchecked()
                    },
                };
                let diagnostic = codespan_reporting::diagnostic::Diagnostic::error()
                    .with_message(msg)
                    .with_labels(vec![label]);
                codespan_reporting::term::emit(
                    &mut writer.lock(),
                    &config,
                    &codespan_file,
                    &diagnostic,
                )
                .unwrap();
            }
        }
    } else {
        eprintln!("No file given. Aborting.");
    }

    let rust = generate(ast.unwrap());
    println!("{}", rust);
}

#[macro_export]
macro_rules! unwrap_match {
    ($x:expr, $m:pat => $a:expr) => {
        match $x {
            $m => $a,
            _ => unreachable!(),
        }
    };
}