use fine::compiler::{compile, Function, Module};
use fine::parser::SyntaxTree;
use fine::semantics::{check, Error, Semantics, Type};
use fine::tokens::Lines;
use fine::vm::{eval_export_fn, Context};
use pretty_assertions::assert_eq;
use std::fmt::Write as _;

fn rebase_section(source_path: &str, section: &str, value: &str) {
    let contents = std::fs::read_to_string(source_path)
        .expect(&format!("unable to read input file {}", source_path));

    let mut result = String::new();
    let mut lines = contents.lines();

    // Search for the section.
    let mut found_section = false;
    let marker = format!("// @{section}:");
    while let Some(line) = lines.next() {
        result.push_str(line);
        result.push_str("\n");

        if line == marker {
            found_section = true;
            break;
        }
    }
    if !found_section {
        panic!(
            "unable to locate the {section} section in {source_path}. Is there a line that starts with '// @{section}:'?"
        );
    }

    // We've found the section we care about, replace all the lines we care
    // about with the actual lines.
    let mut replaced_output = false;
    while let Some(line) = lines.next() {
        if line.starts_with("// | ") {
            // Skip copying lines here, because we're skipping
            // the existing concrete syntax tree.
        } else {
            // OK we're out of concrete syntax tree; copy in the
            // new CST. (We do this inline so we don't lose
            // `line`.)
            for expected_line in value.lines() {
                result.push_str("// | ");
                result.push_str(expected_line);
                result.push_str("\n");
            }

            // (Make sure not to drop this line.)
            result.push_str(line);
            result.push_str("\n");

            replaced_output = true;
            break;
        }
    }
    if !replaced_output {
        panic!(
            "didn't actually replace the output section in {}",
            source_path
        );
    }

    // Now just copy the rest of the lines.
    while let Some(line) = lines.next() {
        result.push_str(line);
        result.push_str("\n");
    }

    // ... and re-write the file.
    std::fs::write(source_path, result).expect("unable to write the new file!");
}

fn should_rebase() -> bool {
    let rebase = std::env::var("FINE_TEST_REBASE")
        .unwrap_or(String::new())
        .to_lowercase();
    match rebase.as_str() {
        "1" | "true" | "yes" | "y" => true,
        _ => false,
    }
}

fn assert_concrete(tree: &SyntaxTree, expected: &str, source_path: &str) {
    let dump = tree.dump(false);
    if dump != expected {
        if should_rebase() {
            rebase_section(source_path, "concrete", &dump)
        } else {
            assert_eq!(expected, dump, "concrete syntax trees did not match (set FINE_TEST_REBASE=1 to auto-rebase if the diff is expected)")
        }
    }
}

macro_rules! semantic_panic {
    ($semantics:expr, $tr:expr, $($t:tt)*) => {{
        let message = format!($($t)*);
        eprintln!("{message}!");
        $semantics.dump_compiler_state($tr);
        panic!("{message}");
    }};
}

macro_rules! semantic_assert {
    ($semantics:expr, $tr:expr, $pred:expr, $($t:tt)*) => {{
        if !$pred {
            let message = format!($($t)*);
            eprintln!("{message}!");
            $semantics.dump_compiler_state($tr);
            panic!("{message}");
        }
    }};
}

macro_rules! semantic_assert_eq {
    ($semantics:expr, $tr:expr, $left:expr, $right:expr, $($t:tt)*) => {{
        let ll = $left;
        let rr = $right;
        if ll != rr {
            let message = format!($($t)*);
            eprintln!("{message}!");
            $semantics.dump_compiler_state($tr);
            assert_eq!(ll, rr, "{}", message);
        }
    }};
}

fn assert_type_at(
    tree: &SyntaxTree,
    lines: &Lines,
    pos: usize,
    expected: &str,
    _source_path: &str,
) {
    let semantics = Semantics::new(tree, lines);
    let tree_ref = match tree.find_tree_at(pos) {
        Some(t) => t,
        None => semantic_panic!(
            &semantics,
            None,
            "Unable to find the subtee at position {pos}"
        ),
    };

    let tree_type = semantics.type_of(tree_ref);
    let actual = format!("{}", tree_type);
    semantic_assert_eq!(
        &semantics,
        Some(tree_ref),
        expected,
        actual,
        "The type of the tree at position {pos} was incorrect"
    );
}

fn assert_type_error_at(
    tree: &SyntaxTree,
    lines: &Lines,
    pos: usize,
    expected: &str,
    _source_path: &str,
) {
    let semantics = Semantics::new(tree, lines);
    let tree_ref = match tree.find_tree_at(pos) {
        Some(t) => t,
        None => semantic_panic!(
            &semantics,
            None,
            "Unable to find the subtee at position {pos}"
        ),
    };

    let tree_type = semantics.type_of(tree_ref);
    semantic_assert!(
        &semantics,
        Some(tree_ref),
        matches!(tree_type, Type::Error),
        "The type of the {:?} tree at position {pos} was '{tree_type:?}', not an error",
        tree[tree_ref].kind
    );

    let errors = semantics.snapshot_errors();
    semantic_assert!(
        &semantics,
        Some(tree_ref),
        errors.iter().any(|e| e.message == expected),
        "Unable to find the expected error message '{expected}'"
    );
}

fn dump_function(out: &mut String, function: &Function) -> std::fmt::Result {
    writeln!(
        out,
        "function {} ({} args, {} locals):",
        function.name(),
        function.args(),
        function.locals()
    )?;

    let strings = function.strings();
    writeln!(out, "  strings ({}):", strings.len())?;
    for (i, s) in strings.iter().enumerate() {
        writeln!(out, "    {}: {}", i, s)?; // TODO: ESCAPE
    }

    let code = function.instructions();
    writeln!(out, "  code ({}):", code.len())?;
    for (i, inst) in code.iter().enumerate() {
        writeln!(out, "    {}: {:?}", i, inst)?;
    }

    Ok(())
}

fn dump_module(out: &mut String, module: &Module) -> std::fmt::Result {
    for function in module.functions() {
        dump_function(out, function)?;
    }

    Ok(())
}

fn assert_compiles_to(tree: &SyntaxTree, lines: &Lines, expected: &str, source_path: &str) {
    let semantics = Semantics::new(tree, lines);
    let module = compile(&semantics);

    let mut actual = String::new();
    dump_module(&mut actual, &module).expect("no dumping?");

    if expected != actual {
        if should_rebase() {
            rebase_section(source_path, "compiles-to", &actual)
        } else {
            semantic_assert_eq!(
                &semantics,
                None,
                expected,
                actual,
                "did not compile as expected (set FINE_TEST_REBASE=1 to auto-rebase if the diff is expected)"
            )
        }
    }
}

fn assert_no_errors(tree: &SyntaxTree, lines: &Lines) {
    let semantics = Semantics::new(tree, lines);
    check(&semantics);

    let expected_errors: Vec<Error> = Vec::new();
    let errors = semantics.snapshot_errors();
    semantic_assert_eq!(
        &semantics,
        None,
        expected_errors,
        errors,
        "expected no errors"
    );
}

fn assert_eval_ok(tree: &SyntaxTree, lines: &Lines, expected: &str) {
    let semantics = Semantics::new(tree, lines);

    let module = compile(&semantics);
    let mut context = Context::new(module.clone());
    context.init().expect("Unable to initialize module");

    match eval_export_fn(&mut context, "test", &[]) {
        Ok(v) => {
            let actual = format!("{:?}", v);
            semantic_assert_eq!(
                &semantics,
                None,
                expected,
                &actual,
                "wrong return from test function"
            );
        }
        Err(e) => {
            semantics.dump_compiler_state(None);

            let mut actual = String::new();
            let _ = dump_module(&mut actual, &module);

            eprintln!("{actual}");

            eprintln!("Backtrace:");
            for frame in e.stack.iter() {
                let func = frame.func();
                eprint!("  {} (", func.name());
                for arg in frame.args().iter() {
                    eprint!("{:?},", arg);
                }
                eprintln!(") @ {}", frame.pc());
            }
            eprintln!();

            panic!("error occurred while running: {:?}", e.code);
        }
    }
}

fn assert_errors(tree: &SyntaxTree, lines: &Lines, expected_errors: Vec<&str>) {
    let semantics = Semantics::new(tree, lines);
    check(&semantics);

    let errors: Vec<String> = semantics
        .snapshot_errors()
        .iter()
        .map(|e| format!("{}", e))
        .collect();

    semantic_assert_eq!(
        &semantics,
        None,
        expected_errors,
        errors,
        "expected error messages to match"
    );
}

fn assert_check_error(tree: &SyntaxTree, lines: &Lines, expected: &str) {
    let semantics = Semantics::new(tree, lines);
    check(&semantics);

    let errors = semantics.snapshot_errors();
    semantic_assert!(
        &semantics,
        None,
        errors.iter().any(|e| e.message == expected),
        "Unable to find the expected error message '{expected}'"
    );
}

include!(concat!(env!("OUT_DIR"), "/generated_tests.rs"));
