mod abilities;
mod ai;
mod aim;
mod backpack;
mod camera;
mod cash;
mod cash_heal;
mod character;
mod control;
mod cutscene;
mod debug;
mod gates;
mod head;
mod head_drop;
mod heads;
mod heads_database;
mod heal_effect;
mod hitpoints;
mod keys;
mod loading_assets;
mod loading_map;
mod movables;
mod npc;
mod physics_layers;
mod platforms;
mod player;
mod sounds;
mod tb_entities;
mod utils;
mod water;

use avian3d::prelude::*;
use bevy::{
    audio::{PlaybackMode, Volume},
    core_pipeline::tonemapping::Tonemapping,
    prelude::*,
    render::view::ColorGrading,
};
use bevy_common_assets::ron::RonAssetPlugin;
use bevy_sprite3d::Sprite3dPlugin;
use bevy_steamworks::{Client, FriendFlags, SteamworksEvent, SteamworksPlugin};
use bevy_trenchbroom::prelude::*;
use bevy_ui_gradients::UiGradientsPlugin;
use camera::MainCamera;
use heads_database::HeadDatabaseAsset;
use loading_assets::AudioAssets;
use std::io::{Read, Write};
use utils::{billboards, sprite_3d_animation, squish_animation, trail};

#[derive(Resource, Reflect, Debug)]
#[reflect(Resource)]
struct DebugVisuals {
    pub unlit: bool,
    pub tonemapping: Tonemapping,
    pub exposure: f32,
    pub shadows: bool,
    pub cam_follow: bool,
}

#[derive(States, Default, Clone, Eq, PartialEq, Debug, Hash)]
enum GameState {
    #[default]
    AssetLoading,
    MapLoading,
    Playing,
}

fn main() {
    let mut app = App::new();

    app.register_type::<DebugVisuals>();
    app.insert_resource(DebugVisuals {
        unlit: false,
        tonemapping: Tonemapping::None,
        exposure: 1.,
        shadows: true,
        cam_follow: true,
    });

    app.add_plugins(
        DefaultPlugins
            .set(WindowPlugin {
                primary_window: Some(Window {
                    title: "HEDZ Reloaded".into(),
                    // resolution: (1024., 768.).into(),
                    ..default()
                }),
                ..default()
            })
            .set(bevy::log::LogPlugin {
                filter: "info".into(),
                level: bevy::log::Level::INFO,
                // provide custom log layer to receive logging events
                custom_layer: bevy_debug_log::log_capture_layer,
            }),
    );

    let app_id = 1603000;
    // should only be done in production builds
    #[cfg(not(debug_assertions))]
    if steamworks::restart_app_if_necessary(app_id.into()) {
        info!("Restarting app via steam");
        return;
    }

    match SteamworksPlugin::init_app(app_id) {
        Ok(plugin) => {
            app.add_plugins(plugin);
        }
        Err(e) => {
            warn!("steam init error: {e:?}");
        }
    };

    app.add_plugins(
        bevy_debug_log::LogViewerPlugin::default()
            .auto_open_threshold(bevy::log::tracing::level_filters::LevelFilter::OFF),
    );
    app.add_plugins(PhysicsPlugins::default());
    app.add_plugins(Sprite3dPlugin);
    app.add_plugins(TrenchBroomPlugins(
        TrenchBroomConfig::new("hedz").icon(None),
    ));
    app.add_plugins(UiGradientsPlugin);
    app.add_plugins(RonAssetPlugin::<HeadDatabaseAsset>::new(&["headsdb.ron"]));

    #[cfg(feature = "dbg")]
    {
        app.add_plugins(bevy_inspector_egui::bevy_egui::EguiPlugin {
            enable_multipass_for_primary_context: true,
        });
        app.add_plugins(bevy_inspector_egui::quick::WorldInspectorPlugin::new());
        app.add_plugins(PhysicsDebugPlugin::default());

        // app.add_plugins(bevy::pbr::wireframe::WireframePlugin)
        //     .insert_resource(bevy::pbr::wireframe::WireframeConfig {
        //         global: true,
        //         default_color: bevy::color::palettes::css::WHITE.into(),
        //     });
    }

    app.add_plugins(ai::plugin);
    app.add_plugins(character::plugin);
    app.add_plugins(cash::plugin);
    app.add_plugins(player::plugin);
    app.add_plugins(gates::plugin);
    app.add_plugins(platforms::plugin);
    app.add_plugins(movables::plugin);
    app.add_plugins(billboards::plugin);
    app.add_plugins(aim::plugin);
    app.add_plugins(npc::plugin);
    app.add_plugins(keys::plugin);
    app.add_plugins(squish_animation::plugin);
    app.add_plugins(cutscene::plugin);
    app.add_plugins(control::plugin);
    app.add_plugins(sounds::plugin);
    app.add_plugins(camera::plugin);
    app.add_plugins(backpack::plugin);
    app.add_plugins(loading_assets::LoadingPlugin);
    app.add_plugins(loading_map::plugin);
    app.add_plugins(sprite_3d_animation::plugin);
    app.add_plugins(abilities::plugin);
    app.add_plugins(heads::plugin);
    app.add_plugins(hitpoints::plugin);
    app.add_plugins(cash_heal::plugin);
    app.add_plugins(debug::plugin);
    app.add_plugins(utils::observers::plugin);
    app.add_plugins(water::plugin);
    app.add_plugins(head_drop::plugin);
    app.add_plugins(trail::plugin);
    app.add_plugins(heal_effect::plugin);
    app.add_plugins(tb_entities::plugin);

    app.init_state::<GameState>();

    app.insert_resource(AmbientLight {
        color: Color::WHITE,
        brightness: 400.,
        ..Default::default()
    });
    app.insert_resource(ClearColor(Color::BLACK));
    //TODO: let user control this
    app.insert_resource(GlobalVolume::new(Volume::Linear(0.4)));

    app.add_systems(
        Startup,
        (
            write_trenchbroom_config,
            (steam_system, steam_events)
                .chain()
                .run_if(resource_exists::<Client>),
        ),
    );
    app.add_systems(OnEnter(GameState::Playing), music);
    app.add_systems(Update, (set_materials_unlit, set_tonemapping, set_shadows));

    app.run();
}

fn steam_events(mut events: EventReader<SteamworksEvent>) {
    for e in events.read() {
        info!("steam ev: {:?}", e);
    }
}

fn steam_system(steam_client: Res<Client>) {
    steam_client.matchmaking().request_lobby_list(|list| {
        let Ok(list) = list else { return };

        info!("lobby list: [{}]", list.len());
        for (i, l) in list.iter().enumerate() {
            info!("lobby [{i}]: {:?}", l);
        }
    });

    steam_client
        .matchmaking()
        .create_lobby(
            steamworks::LobbyType::FriendsOnly,
            4,
            |result| match result {
                Ok(lobby_id) => {
                    info!("Created lobby with ID: {:?}", lobby_id);
                }
                Err(e) => error!("Failed to create lobby: {}", e),
            },
        );

    for friend in steam_client.friends().get_friends(FriendFlags::IMMEDIATE) {
        info!(
            "Steam Friend: {:?} - {}({:?})",
            friend.id(),
            friend.name(),
            friend.state()
        );
    }

    steam_client
        .remote_storage()
        .set_cloud_enabled_for_app(true);
    let f = steam_client.remote_storage().file("hedz_data.dat");
    if f.exists() {
        let mut buf = String::new();
        if let Err(e) = f.read().read_to_string(&mut buf) {
            error!("File read error: {}", e);
        } else {
            info!("File content: {}", buf);
        }
    } else {
        info!("File does not exist");

        if let Err(e) = f.write().write_all(String::from("hello world").as_bytes()) {
            error!("steam cloud error: {}", e);
        } else {
            info!("steam cloud saved");
        }
    }
}

fn music(assets: Res<AudioAssets>, mut commands: Commands) {
    commands.spawn((
        Name::new("sfx-music"),
        AudioPlayer::new(assets.music.clone()),
        PlaybackSettings {
            mode: PlaybackMode::Loop,
            volume: Volume::Linear(0.6),
            ..default()
        },
    ));

    commands.spawn((
        Name::new("sfx-ambient"),
        AudioPlayer::new(assets.ambient.clone()),
        PlaybackSettings {
            mode: PlaybackMode::Loop,
            volume: Volume::Linear(0.8),
            ..default()
        },
    ));
}

fn write_trenchbroom_config(server: Res<TrenchBroomServer>, type_registry: Res<AppTypeRegistry>) {
    if let Err(e) = server
        .config
        .write_game_config("trenchbroom/hedz", &type_registry.read())
    {
        warn!("Failed to write trenchbroom config: {}", e);
    }
}

fn set_tonemapping(
    mut cams: Query<(&mut Tonemapping, &mut ColorGrading), With<MainCamera>>,
    visuals: Res<DebugVisuals>,
) {
    for (mut tm, mut color) in cams.iter_mut() {
        *tm = visuals.tonemapping;
        color.global.exposure = visuals.exposure;
    }
}

fn set_materials_unlit(
    mut materials: ResMut<Assets<StandardMaterial>>,
    visuals: Res<DebugVisuals>,
) {
    if !materials.is_changed() {
        return;
    }

    for (_, material) in materials.iter_mut() {
        material.unlit = visuals.unlit;
    }
}

fn set_shadows(mut lights: Query<&mut DirectionalLight>, visuals: Res<DebugVisuals>) {
    for mut l in lights.iter_mut() {
        l.shadows_enabled = visuals.shadows;
    }
}
