use avian3d::PhysicsPlugins;
use avian3d::prelude::*;
use bevy::core_pipeline::tonemapping::Tonemapping;
use bevy::ecs::component::ComponentId;
use bevy::ecs::world::DeferredWorld;
use bevy::math::*;
use bevy::prelude::*;
use bevy::render::view::ColorGrading;
use bevy_flycam::prelude::*;
use bevy_trenchbroom::prelude::*;

mod alien;

#[derive(SolidClass, Component, Reflect)]
#[reflect(Component)]
#[geometry(GeometryProvider::new().convex_collider().smooth_by_default_angle().render())]
pub struct Worldspawn;

#[derive(SolidClass, Component, Reflect, Default)]
#[reflect(Component)]
#[geometry(GeometryProvider::new().convex_collider().smooth_by_default_angle().render())]
pub struct NamedEntity {
    pub name: String,
}

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

#[derive(PointClass, Component, Reflect, Default)]
#[reflect(Component)]
#[require(Transform)]
#[component(on_add = Self::on_add)]
#[model({ "path": "models/spawn.glb" })]
pub struct SpawnPoint {}

impl SpawnPoint {
    fn on_add(mut world: DeferredWorld, entity: Entity, _id: ComponentId) {
        let Some(asset_server) = world.get_resource::<AssetServer>() else {
            return;
        };

        let mesh = asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/spawn.glb"));

        world.commands().entity(entity).insert((
            Name::new("spawn"),
            SceneRoot(mesh),
            RigidBody::Static,
            ColliderConstructorHierarchy::new(ColliderConstructor::TrimeshFromMesh),
        ));
    }
}

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

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

    app.add_plugins(DefaultPlugins.set(ImagePlugin {
        default_sampler: repeating_image_sampler(false),
    }));

    app.add_plugins(PhysicsPlugins::default());

    // bevy_flycam setup so we can get a closer look at the scene, mainly for debugging
    app.add_plugins(PlayerPlugin);

    app.add_plugins(alien::plugin);

    app.insert_resource(MovementSettings {
        sensitivity: 0.00005,
        speed: 12.,
    });
    app.insert_resource(AmbientLight {
        color: Color::WHITE,
        brightness: 400.,
    });
    app.add_plugins(bevy_inspector_egui::quick::WorldInspectorPlugin::default());

    app.add_plugins(TrenchBroomPlugin(TrenchBroomConfig::new("hedz")));

    app.add_systems(Startup, (write_trenchbroom_config, music));
    app.add_systems(PostStartup, setup_scene);
    app.add_systems(
        Update,
        (set_materials_unlit, set_tonemapping, set_shadows, spawn_box),
    );

    app.run();
}

fn setup_scene(mut commands: Commands, asset_server: Res<AssetServer>) {
    commands.spawn(SceneRoot(asset_server.load("maps/map1.map#Scene")));

    commands.spawn((
        DirectionalLight {
            illuminance: light_consts::lux::OVERCAST_DAY,
            shadows_enabled: true,
            ..default()
        },
        Transform {
            translation: Vec3::new(0.0, 2.0, 0.0),
            rotation: Quat::from_rotation_x(-1.7),
            ..default()
        },
    ));

    commands.spawn((
        SceneRoot(
            asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/angry_demonstrator.glb")),
        ),
        Transform::from_xyz(0.0, 5.0, 0.0),
    ));
}

fn spawn_box(
    mut commands: Commands,
    mut meshes: ResMut<Assets<Mesh>>,
    mut materials: ResMut<Assets<StandardMaterial>>,
    keys: Res<ButtonInput<KeyCode>>,
) {
    if keys.just_pressed(KeyCode::Enter) {
        commands.spawn((
            RigidBody::Dynamic,
            Collider::cuboid(5.0, 5.0, 5.0),
            AngularVelocity(Vec3::new(2.5, 3.5, 1.5)),
            Mesh3d(meshes.add(Cuboid::from_length(5.0))),
            MeshMaterial3d(materials.add(Color::srgb_u8(124, 144, 255))),
            Transform::from_xyz(0.0, 50.0, 0.0),
        ));
    }
}

fn music(asset_server: Res<AssetServer>, mut commands: Commands) {
    commands.spawn(AudioPlayer::new(asset_server.load("sfx/music/02.ogg")));
    commands.spawn(AudioPlayer::new(
        asset_server.load("sfx/heads/angry_demonstrator.ogg"),
    ));
}

fn write_trenchbroom_config(server: Res<TrenchBroomServer>) {
    server.config.write_folder("trenchbroom/hedz").unwrap()
}

fn set_tonemapping(
    mut cams: Query<(&mut Tonemapping, &mut ColorGrading), With<Camera>>,
    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;
    }
}
