use crossterm::event::{Event, poll, read};
/// Disable raw terminal mode. Call at application shutdown.
pub use crossterm::terminal::disable_raw_mode;
/// Enable raw terminal mode. Call at application start-up.
pub use crossterm::terminal::enable_raw_mode;
use std::time::Duration;
use uuidmap::Table;

/// Re-export portable key code enum from crossterm.
pub use crossterm::event::KeyCode;

/// A single key input event with key code and order.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct KeyInputEvent {
    /// The captured key code.
    pub code: KeyCode,
    /// Order in which the event occurred during a frame.
    pub order: u64,
}

/// Handle a raw terminal event: if it's a key, insert it into the table.
pub fn handle_event(
    table: &mut Table<KeyInputEvent>,
    counter: &mut u64,
    event: Event,
) -> Result<(), Box<dyn std::error::Error>> {
    if let Event::Key(key_event) = event {
        let order = *counter;
        *counter += 1;
        // store only the KeyCode for pressed keys
        table.add(KeyInputEvent {
            code: key_event.code,
            order,
        });
    }
    Ok(())
}

/// System that captures raw key input events and writes them to a Table.
pub fn input_system(table: &mut Table<KeyInputEvent>) -> Result<(), Box<dyn std::error::Error>> {
    // reset for new frame
    table.clear();
    let mut counter = 0;
    // capture pending key event
    if poll(Duration::from_millis(0))? {
        let event = read()?;
        handle_event(table, &mut counter, event)?;
    }
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;
    use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers};
    use world_dispatcher::{DispatcherBuilder, World};

    #[test]
    fn unmapped_key_handling() {
        let mut table: Table<KeyInputEvent> = Default::default();
        let mut counter = 0;
        let event = Event::Resize(80, 24);
        handle_event(&mut table, &mut counter, event).unwrap();
        assert_eq!(counter, 0);
        assert_eq!(table.iter().count(), 0);
    }

    #[test]
    fn key_events_order_and_insertion() {
        let mut table: Table<KeyInputEvent> = Default::default();
        let mut counter = 0;
        let key1 = KeyEvent::new(KeyCode::Char('x'), KeyModifiers::NONE);
        handle_event(&mut table, &mut counter, Event::Key(key1.clone())).unwrap();
        let key2 = KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE);
        handle_event(&mut table, &mut counter, Event::Key(key2.clone())).unwrap();
        assert_eq!(counter, 2);
        let mut events: Vec<_> = table.iter().map(|(_, evt)| evt.clone()).collect();
        events.sort_by_key(|evt| evt.order);
        assert_eq!(events[0].order, 0);
        assert_eq!(events[1].order, 1);
        assert_eq!(events[0].code, key1.code);
        assert_eq!(events[1].code, key2.code);
    }

    #[test]
    fn integration_test_handle_events_system() {
        #[derive(Default)]
        struct Events(Vec<Event>);

        fn handle_events_system(
            events: &Events,
            table: &mut Table<KeyInputEvent>,
            counter: &mut u64,
        ) -> world_dispatcher::SystemResult {
            table.clear();
            *counter = 0;
            for event in &events.0 {
                crate::handle_event(table, counter, event.clone()).unwrap();
            }
            Ok(())
        }

        let mut world = World::default();
        *world.get_mut_or_default::<Events>() = Events(vec![
            Event::Key(KeyEvent::new(KeyCode::Char('a'), KeyModifiers::NONE)),
            Event::Key(KeyEvent::new(KeyCode::Char('b'), KeyModifiers::SHIFT)),
            Event::Resize(10, 10),
            Event::Key(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE)),
        ]);

        let mut dispatcher = DispatcherBuilder::default()
            .add(handle_events_system)
            .build(&mut world);

        dispatcher.run_seq(&world).unwrap();

        let table_ref = world.get::<Table<KeyInputEvent>>().unwrap();
        let mut events: Vec<_> = table_ref.iter().map(|(_, evt)| evt.clone()).collect();
        events.sort_by_key(|evt| evt.order);
        assert_eq!(events.len(), 3);
        assert_eq!(events[0].order, 0);
        assert_eq!(events[1].order, 1);
        assert_eq!(events[2].order, 2);
        assert_eq!(events[0].code, KeyCode::Char('a'));
        assert_eq!(events[1].code, KeyCode::Char('b'));
        assert_eq!(events[2].code, KeyCode::Enter);
    }
}
