use std::collections::HashMap;

use ecs::extension::Collector as ExtensionCollector;
use ecs::phase::{Phase, PRE_UPDATE as PRE_UPDATE_PHASE, START as START_PHASE};
use ecs::relationship::{ChildOf, Relationship};
use ecs::sole::Single;
use ecs::{static_entity, Sole};

use crate::vector::Vec2;
use crate::window::{Window, UPDATE_PHASE as WINDOW_UPDATE_PHASE};

mod reexports
{
    pub use crate::window::{Key, KeyState};
}

pub use reexports::*;

static_entity!(
    SET_PREV_KEY_STATE_PHASE,
    (
        Phase,
        <Relationship<ChildOf, Phase>>::new(*WINDOW_UPDATE_PHASE)
    )
);

#[derive(Debug, Sole)]
pub struct Keys
{
    map: HashMap<Key, KeyData>,
    pending: Vec<(Key, KeyState)>,
}

impl Keys
{
    #[must_use]
    pub fn new() -> Self
    {
        Self {
            map: Key::KEYS
                .iter()
                .map(|key| {
                    (
                        *key,
                        KeyData {
                            state: KeyState::Released,
                            prev_tick_state: KeyState::Released,
                        },
                    )
                })
                .collect(),
            pending: Vec::with_capacity(Key::KEYS.len()),
        }
    }

    #[must_use]
    pub fn get_key_state(&self, key: Key) -> KeyState
    {
        let Some(key_data) = self.map.get(&key) else {
            unreachable!();
        };

        key_data.state
    }

    #[must_use]
    pub fn get_prev_key_state(&self, key: Key) -> KeyState
    {
        let Some(key_data) = self.map.get(&key) else {
            unreachable!();
        };

        key_data.prev_tick_state
    }

    pub fn set_key_state(&mut self, key: Key, new_key_state: KeyState)
    {
        let Some(key_data) = self.map.get_mut(&key) else {
            unreachable!();
        };

        key_data.state = new_key_state;
    }

    #[must_use]
    pub fn is_anything_pressed(&self) -> bool
    {
        self.map
            .values()
            .any(|key_data| matches!(key_data.state, KeyState::Pressed))
    }
}

impl Default for Keys
{
    fn default() -> Self
    {
        Self::new()
    }
}

#[derive(Debug, Default, Clone, Sole)]
pub struct Cursor
{
    pub position: Vec2<f64>,
    pub has_moved: bool,
}

#[derive(Debug, Clone, Sole)]
pub struct CursorFlags
{
    /// This flag is set in two situations:
    /// A: The window has just started
    /// B: The window has gained focus again after losing focus.
    ///
    /// This flag only lasts a single tick then it is cleared (at the beginning of the
    /// next tick).
    pub is_first_move: CursorFlag,
}

impl Default for CursorFlags
{
    fn default() -> Self
    {
        Self {
            is_first_move: CursorFlag { flag: true, ..Default::default() },
        }
    }
}

#[derive(Debug, Default, Clone)]
pub struct CursorFlag
{
    pub flag: bool,
    pub pending_clear: bool,
}

impl CursorFlag
{
    pub fn clear(&mut self)
    {
        self.flag = false;
        self.pending_clear = false;
    }
}

/// Input extension.
#[derive(Debug, Default)]
pub struct Extension {}

impl ecs::extension::Extension for Extension
{
    fn collect(self, mut collector: ExtensionCollector<'_>)
    {
        collector.add_system(*START_PHASE, initialize);
        collector.add_system(*PRE_UPDATE_PHASE, maybe_clear_cursor_is_first_move);
        collector.add_system(*SET_PREV_KEY_STATE_PHASE, set_pending_key_states);

        collector.add_sole(Keys::default()).ok();
        collector.add_sole(Cursor::default()).ok();
        collector.add_sole(CursorFlags::default()).ok();
    }
}

fn initialize(
    keys: Single<Keys>,
    cursor: Single<Cursor>,
    cursor_flags: Single<CursorFlags>,
    window: Single<Window>,
)
{
    let keys_weak_ref = keys.to_weak_ref();

    window.set_key_callback(move |key, _scancode, key_state, _modifiers| {
        let keys_ref = keys_weak_ref.access().expect("No world");

        let mut keys = keys_ref.to_single();

        keys.pending.push((key, key_state));
    });

    let cursor_weak_ref = cursor.to_weak_ref();

    window.set_cursor_pos_callback(move |cursor_position| {
        let cursor_ref = cursor_weak_ref.access().expect("No world");

        let mut cursor = cursor_ref.to_single();

        cursor.position = Vec2 {
            x: cursor_position.x,
            y: cursor_position.y,
        };

        cursor.has_moved = true;
    });

    let cursor_flags_weak_ref = cursor_flags.to_weak_ref();

    window.set_focus_callback(move |is_focused| {
        tracing::trace!("Window is focused: {is_focused}");

        let cursor_flags_ref = cursor_flags_weak_ref.access().expect("No world");

        cursor_flags_ref.to_single().is_first_move.flag = is_focused;
    });
}

fn maybe_clear_cursor_is_first_move(
    cursor: Single<Cursor>,
    mut cursor_flags: Single<CursorFlags>,
)
{
    if cursor_flags.is_first_move.pending_clear {
        tracing::trace!("Clearing is_first_move");

        // This flag was set for the whole previous tick so it can be cleared now
        cursor_flags.is_first_move.clear();

        return;
    }

    if cursor.has_moved && cursor_flags.is_first_move.flag {
        tracing::trace!("Setting flag to clear is_first_move next tick");

        // Make this system clear is_first_move the next time it runs
        cursor_flags.is_first_move.pending_clear = true;
    }
}

fn set_pending_key_states(mut keys: Single<Keys>)
{
    let Keys { map, pending } = &mut *keys;

    for key_data in map.values_mut() {
        key_data.prev_tick_state = key_data.state;
    }

    for (key, key_state) in pending {
        let Some(key_data) = map.get_mut(key) else {
            unreachable!();
        };

        key_data.state = *key_state;
    }
}

#[derive(Debug)]
struct KeyData
{
    state: KeyState,
    prev_tick_state: KeyState,
}