use std::collections::VecDeque;
use tb_runtime::{
    host::{Ereignis, Host},
    screen::TextScreen,
    snapshot,
    value::Value,
};
use tb_vm::interp::{PollResult, RunEvent, Vm};
#[derive(Default)]
struct HostStub {
    now: u64,
    clocks: usize,
    input: VecDeque<Ereignis>,
}
impl Host for HostStub {
    fn present(&mut self, _: &TextScreen) {}
    fn next_event(&mut self, blocking: bool) -> Option<Ereignis> {
        assert!(!blocking);
        self.input.pop_front()
    }
    fn warten(&mut self, _: Option<u64>) -> Option<Ereignis> {
        panic!("poll blocked")
    }
    fn jetzt_ms(&mut self) -> u64 {
        self.clocks += 1;
        self.now
    }
}
fn vm(source: &str) -> Vm {
    Vm::new(tb_vm::compile_source("TEST", source).unwrap())
}
fn text(vm: &Vm, name: &str) -> String {
    match vm.inspect(name) {
        Some(Value::Str(s)) => s.to_string(),
        v => panic!("{v:?}"),
    }
}
fn input(host: &mut HostStub, chars: &str) {
    host.input
        .extend(chars.chars().map(|c| Ereignis::Taste(c.to_string(), 0)));
}
fn poll(vm: &mut Vm, host: &mut HostStub) -> PollResult {
    vm.poll(host, 200)
}
fn end(vm: &mut Vm, host: &mut HostStub) {
    for _ in 0..1000 {
        match poll(vm, host) {
            PollResult::Event(RunEvent::Ended) => return,
            PollResult::Yield | PollResult::Waiting { .. } => {}
            other => panic!("{other:?}"),
        }
    }
    panic!("did not end");
}
fn pause(vm: &mut Vm, host: &mut HostStub) {
    vm.rt.abbruch = true;
    assert!(matches!(
        poll(vm, host),
        PollResult::Event(RunEvent::Interrupted { .. })
    ));
    vm.rt.abbruch = false;
}
#[test]
fn finite_slices_preserve_events_without_clock_queries() {
    for source in ["DO\nLOOP", "10 GOTO 10"] {
        let mut vm = vm(source);
        vm.set_poll_interrupt(true);
        let mut host = HostStub::default();
        for _ in 0..10 {
            assert_eq!(poll(&mut vm, &mut host), PollResult::Yield);
        }
        assert_eq!(host.clocks, 0);
        pause(&mut vm, &mut host);
    }
    let mut vm = vm("STOP\nPRINT 42\nEND");
    let mut host = HostStub::default();
    assert!(matches!(
        poll(&mut vm, &mut host),
        PollResult::Event(RunEvent::Stopped { .. })
    ));
    end(&mut vm, &mut host);
    assert!(snapshot::text(&vm.rt.screen).contains("42"));
    assert_eq!(host.clocks, 0);
}
#[test]
fn partial_console_operations_keep_prompt_arguments_and_assignment() {
    for instruction in [
        "INPUT \"Name\"; s$",
        "LINE INPUT \"Name\"; s$",
        "s$=INPUT$(3)",
    ] {
        let mut vm = vm(&format!("{instruction}\nn%=n%+1\nEND"));
        let mut host = HostStub::default();
        input(&mut host, "a");
        assert!(matches!(
            poll(&mut vm, &mut host),
            PollResult::Waiting { .. }
        ));
        let before = snapshot::text(&vm.rt.screen);
        pause(&mut vm, &mut host);
        assert_eq!(snapshot::text(&vm.rt.screen), before);
        input(
            &mut host,
            if instruction.contains("INPUT$(") {
                "bc"
            } else {
                "bc\r"
            },
        );
        end(&mut vm, &mut host);
        assert_eq!(text(&vm, "s"), "abc");
        assert!(matches!(vm.inspect("n"), Some(Value::Int(1))));
        assert!(snapshot::text(&vm.rt.screen).matches("Name").count() <= 1);
    }
    let mut vm = vm("RANDOMIZE\nPRINT RND\nEND");
    let mut host = HostStub::default();
    input(&mut host, "12");
    assert!(matches!(
        poll(&mut vm, &mut host),
        PollResult::Waiting { .. }
    ));
    pause(&mut vm, &mut host);
    input(&mut host, "3\r");
    end(&mut vm, &mut host);
    assert_eq!(
        snapshot::text(&vm.rt.screen)
            .matches("Random Number Seed")
            .count(),
        1
    );
}
#[test]
fn sleep_deadline_survives_pause_and_does_not_pop_argument_twice() {
    let mut vm = vm("SLEEP 2\nn%=n%+1\nEND");
    let mut host = HostStub::default();
    assert_eq!(
        poll(&mut vm, &mut host),
        PollResult::Waiting {
            deadline: Some(2000)
        }
    );
    host.now = 1000;
    pause(&mut vm, &mut host);
    assert_eq!(
        poll(&mut vm, &mut host),
        PollResult::Waiting {
            deadline: Some(2000)
        }
    );
    host.now = 2000;
    end(&mut vm, &mut host);
    assert!(matches!(vm.inspect("n"), Some(Value::Int(1))));
}
#[test]
fn dialogs_keep_partial_text_focus_and_caller_arguments() {
    for (source, chars) in [
        ("s$=INPUTBOX$(\"Prompt\",\"Title\")\nEND", "ab"),
        ("n%=MSGBOX(\"Prompt\",256+1,\"Title\")\nEND", ""),
    ] {
        let mut vm = vm(source);
        let mut host = HostStub::default();
        input(&mut host, chars);
        for _ in 0..5 {
            assert!(matches!(
                poll(&mut vm, &mut host),
                PollResult::Waiting { .. }
            ));
        }
        pause(&mut vm, &mut host);
        input(&mut host, "\r");
        end(&mut vm, &mut host);
        if chars.is_empty() {
            assert!(matches!(vm.inspect("n"), Some(Value::Int(2))));
        } else {
            assert_eq!(text(&vm, "s"), "ab");
        }
    }
}
#[test]
fn modal_and_modeless_forms_resume_handlers_across_tiny_budgets() {
    use tb_frontend::forms::{FormCatalog, ObjectClass};
    let mut catalog = FormCatalog::default();
    catalog.add("Form1", ObjectClass::Form, None, false);
    catalog.add("Timer1", ObjectClass::Timer, Some("Form1"), false);
    for modal in [false, true] {
        let source = format!("Timer1.Interval=100\nTimer1.Enabled=-1\nForm1.Show {}\n{}\nSUB Timer1_Timer\nFOR i%=1 TO 100\nNEXT\nForm1.Hide\nEND SUB", if modal {"1"} else {"0"}, if modal {"END"} else {""});
        let mut vm = Vm::new(tb_vm::compile_source_with_forms("FORM1", &source, &catalog).unwrap());
        let mut host = HostStub::default();
        let result = poll(&mut vm, &mut host);
        if modal {
            assert!(matches!(result, PollResult::Waiting { .. }));
        } else {
            assert_eq!(result, PollResult::Event(RunEvent::Ended));
        }
        pause(&mut vm, &mut host);
        host.now = 100;
        for _ in 0..1000 {
            let result = if modal {
                vm.poll(&mut host, 3)
            } else {
                vm.poll_visible_forms(&mut host, 3)
            };
            if result == PollResult::Event(RunEvent::Ended) && !vm.forms.has_visible_forms() {
                break;
            }
        }
        assert!(!vm.forms.has_visible_forms());
    }
}
#[test]
fn pending_dialog_keeps_physical_resize_and_modeless_handler_finishes_after_hide() {
    let mut vm = vm("s$=INPUTBOX$(\"Prompt\")\nEND");
    let mut host = HostStub::default();
    assert!(matches!(
        poll(&mut vm, &mut host),
        PollResult::Waiting { .. }
    ));
    vm.rt.ereignis(Ereignis::Groesse {
        cols: 120,
        rows: 40,
    });
    input(&mut host, "\r");
    end(&mut vm, &mut host);
    assert_eq!((vm.rt.screen.cols(), vm.rt.screen.rows()), (120, 40));
    use tb_frontend::forms::{FormCatalog, ObjectClass};
    let mut catalog = FormCatalog::default();
    catalog.add("Form1", ObjectClass::Form, None, false);
    let module=tb_vm::compile_source_with_forms("FORM1","Form1.Show\nSUB Form_KeyPress(KeyAscii AS INTEGER)\nForm1.Hide\nPRINT \"after hide\"\nEND SUB",&catalog).unwrap();
    let mut vm = Vm::new(module);
    assert_eq!(poll(&mut vm, &mut host), PollResult::Event(RunEvent::Ended));
    input(&mut host, "a");
    for _ in 0..100 {
        if vm.poll_visible_forms(&mut host, 1) == PollResult::Event(RunEvent::Ended) {
            break;
        }
    }
    assert!(snapshot::text(&vm.rt.screen).contains("after hide"));
}
#[test]
fn common_dialog_external_call_keeps_byref_arguments_until_completion() {
    let mut vm=vm("DECLARE SUB FindText (s AS STRING, flags AS INTEGER, cancelled AS INTEGER)\ns$=\"before\"\nFindText s$, flags%, cancelled%\nEND");
    let mut host = HostStub::default();
    assert!(matches!(
        poll(&mut vm, &mut host),
        PollResult::Waiting { .. }
    ));
    pause(&mut vm, &mut host);
    input(&mut host, "\r");
    end(&mut vm, &mut host);
    assert_eq!(text(&vm, "s"), "before");
    assert!(matches!(vm.inspect("cancelled"), Some(Value::Int(0))));
}
