use crossbeam::channel::{Receiver, Sender};
use cursive::backends::puppet::observed::ObservedScreen;
use cursive::backends::puppet::Backend;
use cursive::event::{Event, Key};
use cursive::view::Nameable;
use cursive::views::TextView;
use cursive::Vec2;
use cursive_tabs::{Align, Placement, TabPanel, TabView};
use insta::assert_display_snapshot;

fn setup_test_environment<F>(cb: F) -> (Receiver<ObservedScreen>, Sender<Option<Event>>)
where
    F: FnOnce(&mut cursive::Cursive),
{
    let backend = Backend::init(Some(Vec2::new(80, 24)));
    let frames = backend.stream();
    let input = backend.input();
    let mut siv = cursive::Cursive::new().into_runner(backend);
    cb(&mut siv);
    input
        .send(Some(Event::Refresh))
        .expect("Refresh not accepted, backend not valid");
    siv.step();
    (frames, input)
}

struct TestCursive {
    siv: cursive::CursiveRunner<cursive::Cursive>,
    frames: Receiver<ObservedScreen>,
    input: Sender<Option<Event>>,
}

impl TestCursive {
    fn new<F>(cb: F) -> Self
    where
        F: FnOnce(&mut cursive::Cursive),
    {
        let backend = Backend::init(Some(Vec2::new(80, 24)));
        let frames = backend.stream();
        let input = backend.input();
        let mut siv = cursive::Cursive::new().into_runner(backend);
        cb(&mut siv);
        input
            .send(Some(Event::Refresh))
            .expect("Refresh not accepted, backend not valid");
        siv.step();
        Self { siv, frames, input }
    }
    fn _call_on<F>(&mut self, cb: F)
    where
        F: FnOnce(&mut cursive::Cursive),
    {
        cb(&mut self.siv);
    }

    fn input(&mut self, event: Event) {
        self.input
            .send(Some(event))
            .expect("Refresh not accepted, backend could not react");
        self.step();
    }

    fn step(&mut self) {
        self.input
            .send(Some(Event::Refresh))
            .expect("Refresh not accepted, backend could not react");
        self.siv.step();
    }

    fn last_screen(&mut self) -> ObservedScreen {
        self.frames.try_iter().last().unwrap()
    }
}

#[test]
fn test_puppet_screen() {
    let (frames, _) = setup_test_environment(|siv: &mut cursive::Cursive| {
        siv.add_fullscreen_layer(TextView::new(
            "This is a smoke test for the puppet cursive backend.",
        ))
    });
    assert_display_snapshot!(frames.try_iter().last().unwrap())
}

#[test]
fn end2end_add_at() {
    let (frames, _) = setup_test_environment(|siv: &mut cursive::Cursive| {
        let tabs = TabView::new()
            .with_tab_at(TextView::new("Third").with_name("0"), 0)
            .with_tab_at(TextView::new("First").with_name("1"), 0)
            .with_tab_at(TextView::new("Second").with_name("2"), 1);
        siv.add_layer(tabs);
    });
    assert_display_snapshot!(frames.try_iter().last().unwrap());
}

#[test]
fn end2end_add_at_action_change_tab() {
    let mut tsiv = TestCursive::new(|siv: &mut cursive::Cursive| {
        let tabs = TabView::new()
            .with_tab_at(TextView::new("Third").with_name("0"), 0)
            .with_tab_at(TextView::new("First").with_name("1"), 0)
            .with_tab_at(TextView::new("Second").with_name("2"), 1);
        siv.add_layer(tabs);
    });
    tsiv.input(Event::Key(Key::Up));
    assert_display_snapshot!(tsiv.last_screen());
}

#[test]
fn end2end_add_at_panel() {
    let (frames, _) = setup_test_environment(|siv: &mut cursive::Cursive| {
        let tabs = TabPanel::new()
            .with_tab(TextView::new("Pshhhh").with_name("Stonks"))
            .with_tab_at(TextView::new("Fooooo").with_name("So"), 0)
            .with_tab_at(TextView::new("Ahhhhh").with_name("Much"), 1)
            .with_bar_alignment(Align::Center);
        siv.add_layer(tabs);
    });
    assert_display_snapshot!(frames.try_iter().last().unwrap());
}

#[test]
fn end2end_panel_smoke() {
    let (frames, _) = setup_test_environment(|siv: &mut cursive::Cursive| {
        let tabs = TabPanel::new()
            .with_tab(TextView::new("Pshhhh").with_name("Stronk test"))
            .with_active_tab("Stronk test")
            .unwrap_or_else(|_| panic!("Setting active tab has failed"))
            .with_bar_alignment(Align::Center);
        siv.add_layer(tabs);
    });
    assert_display_snapshot!(frames.try_iter().last().unwrap());
}

#[test]
fn end2end_remove_active() {
    let (frames, _) = setup_test_environment(|siv: &mut cursive::Cursive| {
        let mut tabs = TabView::new()
            .with_tab(TextView::new("First").with_name("0"))
            .with_tab(TextView::new("Second").with_name("1"));
        tabs.remove_tab("1").expect("Removal of active tab failed");
        siv.add_layer(tabs);
    });
    assert_display_snapshot!(frames.try_iter().last().unwrap());
}

#[test]
fn end2end_remove_inactive() {
    let (frames, _) = setup_test_environment(|siv: &mut cursive::Cursive| {
        let mut tabs = TabView::new()
            .with_tab(TextView::new("First").with_name("0"))
            .with_tab(TextView::new("Second").with_name("1"));
        tabs.remove_tab("0").expect("Removal failed.");
        siv.add_layer(tabs);
    });
    assert_display_snapshot!(frames.try_iter().last().unwrap());
}

#[test]
fn end2end_swap() {
    let (frames, _) = setup_test_environment(|siv: &mut cursive::Cursive| {
        let mut tabs = TabPanel::new()
            .with_tab(TextView::new("Pshhhh").with_name("Stonks"))
            .with_tab_at(TextView::new("Fooooo").with_name("So"), 0)
            .with_tab_at(TextView::new("Ahhhhh").with_name("Much"), 1)
            .with_bar_alignment(Align::Center);
        tabs.swap_tabs("So", "Stonks");
        siv.add_layer(tabs);
    });
    assert_display_snapshot!(frames.try_iter().last().unwrap());
}

#[test]
fn end2end_switch() {
    let (frames, _) = setup_test_environment(|siv: &mut cursive::Cursive| {
        let tabs = TabView::new()
            .with_tab(TextView::new("First").with_name("0"))
            .with_tab(TextView::new("Second").with_name("1"))
            .with_active_tab("0")
            .unwrap_or_else(|_| panic!("Setting active tab has failed"));
        siv.add_layer(tabs);
    });
    assert_display_snapshot!(frames.try_iter().last().unwrap());
}

#[test]
fn end2end_vertical_left() {
    let (frames, _) = setup_test_environment(|siv: &mut cursive::Cursive| {
        let tabs = TabPanel::new()
            .with_tab(TextView::new("Pshhhh").with_name("Stronk test"))
            .with_tab(TextView::new("Pshhhh").with_name("Stronker test"))
            .with_active_tab("Stronk test")
            .unwrap_or_else(|_| panic!("Setting active tab has failed"))
            .with_bar_alignment(Align::Center)
            .with_bar_placement(Placement::VerticalLeft);
        siv.add_layer(tabs);
    });
    assert_display_snapshot!(frames.try_iter().last().unwrap());
}

#[test]
fn end2end_vertical_left_with_action_change_tab() {
    let mut tsiv = TestCursive::new(|siv: &mut cursive::Cursive| {
        let tabs = TabPanel::new()
            .with_tab(TextView::new("Pshhhh").with_name("Stronk test"))
            .with_tab(TextView::new("Pshhhh").with_name("Stronker test"))
            .with_active_tab("Stronk test")
            .unwrap_or_else(|_| panic!("Setting active tab has failed"))
            .with_bar_alignment(Align::Center)
            .with_bar_placement(Placement::VerticalLeft);
        siv.add_layer(tabs);
    });
    tsiv.input(Event::Key(Key::Up));
    assert_display_snapshot!(tsiv.last_screen());
}

#[test]
fn end2end_vertical_right() {
    let (frames, _) = setup_test_environment(|siv: &mut cursive::Cursive| {
        let tabs = TabPanel::new()
            .with_tab(TextView::new("Pshhhh").with_name("Stronk test"))
            .with_active_tab("Stronk test")
            .unwrap_or_else(|_| panic!("Setting active tab has failed"))
            .with_bar_alignment(Align::Center)
            .with_bar_placement(Placement::VerticalRight);
        siv.add_layer(tabs);
    });
    assert_display_snapshot!(frames.try_iter().last().unwrap());
}
