mod aabb;
mod camera;
mod chunk;
mod geometry;
mod quad;
mod render_context;
mod state;
mod text_renderer;
mod texture;
mod time;
mod vertex;
mod view;
mod world;

use std::time::{Duration, Instant};
use wgpu::SwapChainError;
use winit::{
    dpi::{PhysicalSize, Size},
    event::{ElementState, Event, KeyboardInput, MouseButton, VirtualKeyCode, WindowEvent},
    event_loop::{ControlFlow, EventLoop},
    window::WindowBuilder,
};

use crate::state::State;

fn main() {
    env_logger::init();
    let event_loop = EventLoop::new();
    let window = WindowBuilder::new()
        .with_title("minecrab")
        .with_inner_size(Size::Physical(PhysicalSize {
            width: 1280,
            height: 720,
        }))
        .build(&event_loop)
        .unwrap();

    let mut state = futures::executor::block_on(State::new(&window));

    let mut frames = 0;
    let mut frame_instant = Instant::now();
    let mut elapsed = Duration::from_secs(0);

    let mut frametime_min = Duration::from_secs(1000);
    let mut frametime_max = Duration::from_secs(0);

    let mut last_render_time = Instant::now();
    let mut triangle_count = 0;

    event_loop.run(move |event, _, control_flow| {
        match event {
            Event::DeviceEvent { ref event, .. } => state.device_event(event),
            Event::WindowEvent {
                ref event,
                window_id,
            } if window_id == window.id() => match event {
                WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
                WindowEvent::KeyboardInput {
                    input:
                        KeyboardInput {
                            state: ElementState::Pressed,
                            virtual_keycode: Some(VirtualKeyCode::Escape),
                            ..
                        },
                    ..
                } => {
                    let _ = window.set_cursor_grab(false);
                    window.set_cursor_visible(true);
                    state.mouse_grabbed = false;
                }
                WindowEvent::Resized(physical_size) => {
                    state.resize(*physical_size);
                }
                WindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
                    state.resize(**new_inner_size);
                }
                WindowEvent::MouseInput {
                    state: mouse_state,
                    button,
                    ..
                } => {
                    if !state.mouse_grabbed && *button == MouseButton::Left && *mouse_state == ElementState::Pressed {
                        let _ = window.set_cursor_grab(true);
                        window.set_cursor_visible(false);
                        state.mouse_grabbed = true;
                    } else {
                        state.window_event(event);
                    }
                }
                WindowEvent::Focused(false) => {
                    let _ = window.set_cursor_grab(false);
                    window.set_cursor_visible(true);
                    state.mouse_grabbed = false;
                }
                event => { state.window_event(event); }
            },
            Event::RedrawRequested(_) => {
                let frame_elapsed = frame_instant.elapsed();
                frame_instant = Instant::now();

                frametime_min = frametime_min.min(frame_elapsed);
                frametime_max = frametime_max.max(frame_elapsed);
                elapsed += frame_elapsed;

                frames += 1;
                if elapsed.as_secs() >= 1 {
                    let frametime = elapsed / frames;
                    let fps = 1_000_000 / frametime.as_micros();
                    let fps_max = 1_000_000 / frametime_min.as_micros();
                    let fps_min = 1_000_000 / frametime_max.as_micros();

                    println!(
                        "{:>4} frames | frametime avg={:>5.2}ms min={:>5.2}ms max={:>5.2}ms | fps avg={:>5} min={:>5} max={:>5} | {:>8} tris",
                        frames, frametime.as_secs_f32() * 1000.0, frametime_min.as_secs_f32() * 1000.0, frametime_max.as_secs_f32() * 1000.0, fps, fps_min, fps_max, triangle_count,
                    );

                    elapsed = Duration::from_secs(0);
                    frames = 0;
                    frametime_min = Duration::from_secs(1000);
                    frametime_max = Duration::from_secs(0);
                }

                let now = Instant::now();
                let dt = now - last_render_time;
                last_render_time = now;
                state.update(dt);

                match state.render() {
                    Err(root_cause) =>
                        match root_cause.downcast_ref::<SwapChainError>() {
                            // Recreate the swap_chain if lost
                            Some(wgpu::SwapChainError::Lost) => state.resize(state.window_size),
                            // The system is out of memory, we should probably quit
                            Some(wgpu::SwapChainError::OutOfMemory) => *control_flow = ControlFlow::Exit,
                            // All other errors (Outdated, Timeout) should be resolved by the next frame
                            Some(_) | None => eprintln!("{:?}", root_cause),
                        }

                    Ok(v) => triangle_count = v
                }
            }
            Event::MainEventsCleared => {
                // RedrawRequested will only trigger once, unless we manually
                // request it.
                window.request_redraw();
            }
            _ => {}
        }
    });
}