mod camera;
mod components;
mod debug;
mod draw;
mod empty;
mod entity;
mod event;
mod heightmap;
mod light;
mod mesh;
mod physics;
mod player;
mod postprocess;
mod render;
mod shader;
mod snow;
mod snow_light;
mod space;
mod state;
mod systems;
mod terrain;
mod texture;
mod utility;
mod world;

use std::time::{Duration, Instant};

use glam::Vec3;
use render::Renderer;
use utility::input::InputState;
use world::{Transform, World};

use crate::camera::Camera;
use crate::components::CameraComponent;
use crate::debug::render_collider_debug;
use crate::entity::EntityHandle;
use crate::light::Lights;
use crate::physics::PhysicsManager;
use crate::player::Player;
use crate::space::Space;
use crate::systems::{
    camera_follow_system, camera_input_system, camera_noclip_system, physics_sync_system,
    player_input_system, render_system, rotate_system, spotlight_sync_system,
    start_camera_following, state_machine_physics_system, state_machine_system,
    stop_camera_following,
};
use crate::snow::{SnowConfig, SnowLayer};
use crate::terrain::{Terrain, TerrainConfig};
use crate::utility::time::Time;

fn main() -> Result<(), Box<dyn std::error::Error>>
{
    let sdl_context = sdl3::init()?;
    let video_subsystem = sdl_context.video()?;

    let window = video_subsystem
        .window("snow_trail", 1200, 900)
        .position_centered()
        .resizable()
        .vulkan()
        .build()?;
    let renderer = pollster::block_on(Renderer::new(&window, 2))?;
    render::init(renderer);

    let space = Space::load_space("meshes/terrain.gltf")?;
    let terrain_config = TerrainConfig::default();

    let player_spawn = space.player_spawn;
    let camera_spawn = space.camera_spawn_position();

    let mut world = World::new();

    let _player_entity = Player::spawn(&mut world, player_spawn);
    let _terrain_entity = Terrain::spawn(&mut world, space.mesh_data, &terrain_config)?;
    Lights::spawn_lights(&mut world, space.spotlights);

    render::set_terrain_data();

    let terrain_half_size = terrain_config.size / 2.0;
    render::init_snow_light_accumulation(
        glam::Vec2::new(-terrain_half_size.x, -terrain_half_size.y),
        glam::Vec2::new(terrain_half_size.x, terrain_half_size.y),
    );

    let snow_config = SnowConfig::default();
    let snow_layer = SnowLayer::load(&mut world, &snow_config)?;
    println!("Snow layer loaded successfully");

    render::set_snow_depth(&snow_layer.depth_texture_view);

    let mut noclip_mode = true;

    let camera_entity = Camera::spawn(&mut world, camera_spawn);
    if noclip_mode == false
    {
        start_camera_following(&mut world, camera_entity);
    }

    let mut event_pump = sdl_context.event_pump()?;
    let mut input_state = InputState::new();

    sdl_context.mouse().set_relative_mouse_mode(&window, true);

    Time::init();
    let mut last_frame = Instant::now();
    let target_fps = 60;
    let frame_duration = Duration::from_millis(1000 / target_fps);

    const FIXED_TIMESTEP: f32 = 1.0 / 60.0;
    let mut physics_accumulator = 0.0;

    'running: loop
    {
        let frame_start = Instant::now();
        let time = Time::get_time_elapsed();
        let delta = (frame_start - last_frame).as_secs_f32();
        last_frame = frame_start;

        for event in event_pump.poll_iter()
        {
            let mouse_capture_changed = input_state.handle_event(&event);

            if mouse_capture_changed
            {
                sdl_context
                    .mouse()
                    .set_relative_mouse_mode(&window, input_state.mouse_captured);
            }
        }

        if input_state.quit_requested
        {
            break 'running;
        }

        input_state.process_post_events();

        if input_state.noclip_just_pressed
        {
            noclip_mode = !noclip_mode;

            if noclip_mode
            {
                stop_camera_following(&mut world, camera_entity);
            }
            else
            {
                start_camera_following(&mut world, camera_entity);
            }
        }

        camera_input_system(&mut world, &input_state);

        if noclip_mode
        {
            camera_noclip_system(&mut world, &input_state, delta);
        }
        else
        {
            camera_follow_system(&mut world);
            player_input_system(&mut world, &input_state);
        }

        physics_accumulator += delta;

        while physics_accumulator >= FIXED_TIMESTEP
        {
            state_machine_physics_system(&mut world, FIXED_TIMESTEP);

            PhysicsManager::physics_step();

            physics_sync_system(&mut world);

            physics_accumulator -= FIXED_TIMESTEP;
        }

        state_machine_system(&mut world, delta);

        rotate_system(&mut world, delta);

        let spotlights = spotlight_sync_system(&world);
        render::update_spotlights(spotlights);

        let mut draw_calls = render_system(&world);
        draw_calls.extend(render_collider_debug());

        if let Some((camera_entity, camera_component)) = world.cameras.get_active()
        {
            if let Some(camera_transform) = world.transforms.get(camera_entity)
            {
                let view =
                    get_view_matrix(&world, camera_entity, camera_transform, camera_component);
                let projection = camera_component.projection_matrix();

                let player_pos = world
                    .player_tags
                    .all()
                    .first()
                    .and_then(|e| world.transforms.get(*e))
                    .map(|t| t.position)
                    .unwrap_or(Vec3::ZERO);

                render::render(
                    &view,
                    &projection,
                    camera_transform.position,
                    player_pos,
                    &draw_calls,
                    time,
                    delta,
                );
            }
        }

        input_state.clear_just_pressed();

        let frame_time = frame_start.elapsed();
        if frame_time < frame_duration
        {
            std::thread::sleep(frame_duration - frame_time);
        }
    }

    Ok(())
}

fn get_view_matrix(
    world: &World,
    camera_entity: EntityHandle,
    camera_transform: &Transform,
    camera_component: &CameraComponent,
) -> glam::Mat4
{
    if let Some(follow) = world.follows.get(camera_entity)
    {
        if let Some(target_transform) = world.transforms.get(follow.target)
        {
            return glam::Mat4::look_at_rh(
                camera_transform.position,
                target_transform.position,
                Vec3::Y,
            );
        }
    }

    let forward = camera_component.get_forward();
    let target = camera_transform.position + forward;
    glam::Mat4::look_at_rh(camera_transform.position, target, Vec3::Y)
}
