#![allow(clippy::type_complexity, clippy::too_many_arguments)]
#![feature(iter_array_chunks)] // used in ray.rs
#![feature(iter_intersperse)] // used in debug.rs
#![feature(async_closure)] // Loading tweakfiles

mod audio;
mod credits;
mod debug;
mod display3d;
mod game;
mod hit3d;
mod intro;
mod loading;
mod menu;
mod prelude;
mod tutorial;
mod tweak;
mod ui;

use crate::prelude::*;

fn main() {
    if std::env::var_os("CARGO_MANIFEST_DIR").is_none() {
        std::env::set_var("CARGO_MANIFEST_DIR", ".");
    }

    let mut app = App::new();
    app.init_state::<GameState>();
    app.init_state::<DisplayState>();
    app.add_systems(
        Update,
        (
            debug_state::<DisplayState>.run_if(resource_changed::<State<DisplayState>>),
            debug_state::<GameState>.run_if(resource_changed::<State<GameState>>),
            debug_state::<tutorial::TutorialState>
                .run_if(resource_changed::<State<tutorial::TutorialState>>),
        ),
    );
    app.add_systems(Startup, set_window_icon);
    app.add_plugins(
        DefaultPlugins
            .set(ImagePlugin::default_nearest())
            .set(WindowPlugin {
                primary_window: Some(Window {
                    title: "Martian Chess".into(),
                    ..default()
                }),
                ..default()
            }),
    );

    app.add_plugins(credits::CreditsPlugin);
    app.add_plugins(debug::DebugPlugin);
    app.add_plugins(display3d::Display3dPlugin);
    app.add_plugins(game::GamePlugin);
    app.add_plugins(loading::LoadingPlugin);
    app.add_plugins(menu::MenuPlugin);
    app.add_plugins(audio::AudioPlugin);
    app.add_plugins(ui::UiPlugin);
    app.add_plugins(tweak::TweakPlugin);
    app.add_plugins(intro::IntroPlugin);
    app.add_plugins(tutorial::TutorialPlugin);
    app.run();
}

#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default, States, Component)]
pub enum GameState {
    #[default]
    Loading,
    Credits,
    Intro,
    Title,
    Play,
    Endgame,
    Restart,
    Quit,
}

#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default, States, Component)]
pub(crate) enum DisplayState {
    #[default]
    Display3d,
}

/// System for printing the current state
///
/// Only runs when state is modified.
fn debug_state<S: States>(state: Res<State<S>>) {
    info!("State change {:?}", *state);
}

pub(crate) fn manage_state_entities<Marker>() -> impl FnMut(
    Query<
        (&Marker, &mut Visibility),
        (
            With<Marker>,
            Without<game::Captured>,
            Without<game::ValidMove>,
        ),
    >,
    Res<State<Marker>>,
)
where
    Marker: Component + States + PartialEq + std::fmt::Debug,
{
    move |mut entities: Query<
        (&Marker, &mut Visibility),
        (
            With<Marker>,
            Without<game::Captured>,
            Without<game::ValidMove>,
        ),
    >,
          curr_state: Res<State<Marker>>| {
        info!("Activating state {:?}", *curr_state);
        entities.iter_mut().for_each(|(mark, mut visibility)| {
            *visibility = if mark == curr_state.get() {
                Visibility::Inherited
            } else {
                Visibility::Hidden
            };
        });
    }
}

pub(crate) fn any_component_changed<C: Component>(
) -> impl Fn(Query<Entity, Changed<C>>) -> bool + Clone {
    |q: Query<Entity, Changed<C>>| -> bool { !q.is_empty() }
}

pub(crate) fn any_component_added<C: Component>() -> impl Fn(Query<Entity, Added<C>>) -> bool + Clone
{
    |q: Query<Entity, Added<C>>| -> bool { !q.is_empty() }
}

pub(crate) fn _any_component_added_or_changed<C: Component>(
    q: Query<Entity, Or<(Added<C>, Changed<C>)>>,
) -> bool {
    !q.is_empty()
}

fn set_window_icon(
    // we have to use `NonSend` here
    windows: NonSend<WinitWindows>,
) {
    // here we use the `image` crate to load our icon data from a png file
    // this is not a very bevy-native solution, but it will do
    let (icon_rgba, icon_width, icon_height) = {
        let image = image::open("assets/images/3DIcon.png")
            .expect("Failed to open icon path")
            .into_rgba8();
        let (width, height) = image.dimensions();
        let rgba = image.into_raw();
        (rgba, width, height)
    };
    let icon = Icon::from_rgba(icon_rgba, icon_width, icon_height).unwrap();

    // do it for all windows
    for window in windows.windows.values() {
        window.set_window_icon(Some(icon.clone()));
    }
}

pub(crate) fn just_pressed<T>(button: T) -> impl FnMut(Res<ButtonInput<T>>) -> bool + Clone
where
    T: Copy + Eq + Hash + Send + Sync + 'static,
{
    Box::new(move |buttons: Res<ButtonInput<T>>| -> bool { buttons.just_pressed(button) })
}
