//! Unabhängige Review-Proben: zwei behobene Vertragslücken, Timer und Mindestmaße.
//! Ausführung über den temporären Cargo-Treiber aus review.md.
use tb_frontend::forms::{self, FormCatalog, ObjectClass};
use tb_runtime::{
    host::{CaptureHost, Host},
    screen::TextScreen,
};
use tb_ui::forms::{FormsModel, PropertyValue as V};
use tb_vm::interp::{RunEvent, Vm};

fn model(class: ObjectClass) -> FormsModel {
    let mut c = FormCatalog::default();
    c.add("Form1", ObjectClass::Form, None, false);
    c.add("List1", class, Some("Form1"), false);
    let mut m = FormsModel::new(c.objects, 80, 25);
    m.set_initial(0, prop(ObjectClass::Form, "WIDTH"), V::Integer(40))
        .unwrap();
    m.set_initial(0, prop(ObjectClass::Form, "HEIGHT"), V::Integer(12))
        .unwrap();
    m
}
fn prop(class: ObjectClass, name: &str) -> u16 {
    forms::property(class, name).unwrap().0
}

#[test]
fn sorted_nach_befuellen_ordnet_liste_und_erhaelt_auswahl() {
    let mut results = vec![];
    for class in [ObjectClass::ListBox, ObjectClass::ComboBox] {
        let mut m = model(class);
        m.add_item((1, None), "b".into(), None).unwrap();
        m.add_item((1, None), "a".into(), None).unwrap();
        m.set(1, prop(class, "LISTINDEX"), V::Integer(0)).unwrap();
        m.set(1, prop(class, "SORTED"), V::Boolean(true)).unwrap();
        m.add_item((1, None), "c".into(), None).unwrap();
        let list: Vec<_> = (0..3)
            .map(|i| m.get_indexed(1, prop(class, "LIST"), i).unwrap())
            .collect();
        println!(
            "{class:?}: {list:?}; Auswahl={:?}",
            m.get(1, prop(class, "LISTINDEX")).unwrap()
        );
        results.push((list, m.get(1, prop(class, "LISTINDEX")).unwrap()));
    }
    for (list, index) in results {
        assert_eq!(
            list,
            vec![
                V::String("a".into()),
                V::String("b".into()),
                V::String("c".into())
            ]
        );
        assert_eq!(index, V::Integer(1));
    }
}

#[test]
fn combo_simple_zeigt_seine_listeneintraege() {
    let class = ObjectClass::ComboBox;
    let mut m = model(class);
    for (name, value) in [
        ("STYLE", V::Integer(1)),
        ("WIDTH", V::Integer(15)),
        ("HEIGHT", V::Integer(5)),
        ("LEFT", V::Integer(2)),
        ("TOP", V::Integer(2)),
        ("TEXT", V::String("frei".into())),
    ] {
        m.set_initial(1, prop(class, name), value).unwrap();
    }
    m.add_item((1, None), "apfel".into(), None).unwrap();
    m.add_item((1, None), "birne".into(), None).unwrap();
    m.show(0, false).unwrap();
    let mut screen = TextScreen::new();
    m.render(&mut screen);
    let text = tb_runtime::snapshot::text(&screen);
    println!("{text}");
    assert!(
        text.contains("frei"),
        "Kontrolle: das Control muss sichtbar sein"
    );
    assert!(text.contains("apfel") && text.contains("birne"));
}

#[test]
fn timer_array_wird_beim_laden_an_aktueller_hostzeit_aktiviert() {
    let mut c = FormCatalog::default();
    c.add("Form1", ObjectClass::Form, None, false);
    c.add("Timer1", ObjectClass::Timer, Some("Form1"), true);
    let src="DIM SHARED s$\nForm1.Show\nTimer1(0).Interval=100\nSTOP\nLOAD Timer1(1)\nTimer1(0).Interval=0\nSTOP\nDOEVENTS\nEND\nSUB Timer1_Timer(Index AS INTEGER)\nSHARED s$\ns$=s$+LTRIM$(STR$(Index))\nEND SUB";
    let mut vm = Vm::new(tb_vm::compile_source_with_forms("FORM1", src, &c).unwrap());
    let mut h = CaptureHost::default();
    assert!(matches!(vm.run(&mut h), RunEvent::Stopped { .. }));
    h.uhr_setzen(1000);
    assert!(matches!(vm.run(&mut h), RunEvent::Stopped { .. }));
    assert_eq!(vm.forms.next_deadline(), Some(1100));
    h.uhr_setzen(1100);
    assert_eq!(vm.run(&mut h), RunEvent::Ended);
    assert_eq!(h.jetzt_ms(), 1100);
}

#[test]
fn listen_bleiben_auch_bei_minimalmassen_im_controlrechteck() {
    let mut failures = vec![];
    for (class, width, height) in [
        (ObjectClass::ComboBox, 15, 5),
        (ObjectClass::ListBox, 6, 4),
        (ObjectClass::ComboBox, 1, 5),
        (ObjectClass::ListBox, 1, 4),
        (ObjectClass::ListBox, 6, 1),
    ] {
        let mut m = model(class);
        for (name, value) in [
            ("WIDTH", width),
            ("HEIGHT", height),
            ("LEFT", 2),
            ("TOP", 2),
        ] {
            m.set_initial(1, prop(class, name), V::Integer(value))
                .unwrap();
        }
        if class == ObjectClass::ComboBox {
            m.set_initial(1, prop(class, "STYLE"), V::Integer(1))
                .unwrap();
        }
        m.add_item((1, None), "apfel".into(), None).unwrap();
        m.set(1, prop(class, "VISIBLE"), V::Boolean(false)).unwrap();
        m.show(0, false).unwrap();
        let mut screen = TextScreen::new();
        m.render(&mut screen);
        let before = tb_runtime::snapshot::text(&screen);
        m.set(1, prop(class, "VISIBLE"), V::Boolean(true)).unwrap();
        m.render(&mut screen);
        let after = tb_runtime::snapshot::text(&screen);
        let mut outside = vec![];
        for (row, (a, b)) in before.lines().zip(after.lines()).enumerate() {
            for (col, (a, b)) in a.chars().zip(b.chars()).enumerate() {
                if a != b
                    && !(row >= 2
                        && row < 2 + height as usize
                        && col >= 2
                        && col < 2 + width as usize)
                {
                    outside.push((row + 1, col + 1, a, b));
                }
            }
        }
        if !outside.is_empty() {
            failures.push(format!("{class:?} {width}x{height}: {outside:?}"));
        }
    }
    assert!(failures.is_empty(), "{}", failures.join("\n"));
}
