use std::time::{Duration, Instant};

use common::emulator_setup;
use gb_emu_lib::connect::{EmulatorCoreTrait, RomFile};

mod common;

#[test]
fn cpu_instrs() -> Result<(), String> {
    run_blargg_test("cpu_instrs\n\n01:ok  02:ok  03:ok  04:ok  05:ok  06:ok  07:ok  08:ok  09:ok  10:ok  11:ok  \n\nPassed all tests", include_bytes!(
        "../../test-roms/blargg/cpu_instrs/cpu_instrs.gb"
    ),None)
}

#[test]
fn instr_timing() -> Result<(), String> {
    run_blargg_test(
        "instr_timing\n\n\nPassed",
        include_bytes!("../../test-roms/blargg/instr_timing/instr_timing.gb"),
        Some(vec!["Failed"]),
    )
}

#[test]
fn mem_timing() -> Result<(), String> {
    run_blargg_test(
        "mem_timing\n\n\nPassed",
        include_bytes!("../../test-roms/blargg/mem_timing/mem_timing.gb"),
        None,
    )
}

fn run_blargg_test<const N: usize>(
    correct_output: &str,
    rom: &[u8; N],
    extra_end: Option<Vec<&str>>,
) -> Result<(), String> {
    let mut emu = emulator_setup(RomFile::Raw(rom.to_vec()))?;

    let mut end_strings = extra_end.unwrap_or_default();
    end_strings.append(&mut vec![".", "tests"]);

    let mut chars = String::new();

    let began = Instant::now();
    let timeout = Duration::from_secs(60);

    'outer: loop {
        emu.core.run(100);
        while let Ok(v) = emu.serial_rx.try_recv() {
            chars.push(v as char);
            if end_strings.iter().any(|end| chars.ends_with(end)) {
                break 'outer;
            }
        }
        if began.elapsed() > timeout {
            return Err(format!("Test timed out: output was {chars}"));
        }
    }

    assert_eq!(correct_output, chars);

    Ok(())
}