use winit::{
    event::*,
    event_loop::{ControlFlow, EventLoop},
    window::WindowBuilder,
};

mod camera;
mod commands;
mod handle;
mod mesh;
mod pool;
mod renderer;
mod scene;

use camera::*;
use handle::*;
use mesh::*;
use pool::*;
use renderer::*;
use scene::*;

fn load_model() -> (MeshData, TextureData) {
    use tobj::*;

    let model_data = include_bytes!("viking_room.obj").to_vec();
    let model_data = &mut model_data.as_slice();

    let load_options = LoadOptions {
        triangulate: true,
        single_index: true,
        ..Default::default()
    };

    let (models, _mats) = load_obj_buf(model_data, &load_options, |_| unimplemented!()).unwrap();

    let mut vertices = Vec::new();
    let mut indices = Vec::new();

    let m = models.first().unwrap();
    let index_base = vertices.len() as u32;
    for i in 0..m.mesh.positions.len() / 3 {
        let t = i * 3;
        vertices.push(Vertex {
            position: [
                m.mesh.positions[t],
                m.mesh.positions[t + 2],
                -m.mesh.positions[t + 1],
            ],
            normal: [
                m.mesh.normals[t],
                m.mesh.normals[t + 2],
                -m.mesh.normals[t + 1],
            ],
            tex_coords: [m.mesh.texcoords[i * 2], 1.0 - m.mesh.texcoords[i * 2 + 1]],
        });
    }

    indices.extend(m.mesh.indices.iter().map(|i| i + index_base));

    let albedo_data = include_bytes!("viking_room.png");
    let albedo = image::load_from_memory(albedo_data).unwrap();

    use image::GenericImageView;
    let dimensions = albedo.dimensions();

    let albedo_rgb = albedo.as_rgb8().unwrap().to_vec();
    let mut albedo_rgba = Vec::<u8>::new();
    for rgb in albedo_rgb.chunks(3) {
        albedo_rgba.extend_from_slice(rgb);
        albedo_rgba.push(0xff);
    }

    (
        MeshData { vertices, indices },
        TextureData {
            width: dimensions.0,
            height: dimensions.1,
            data: albedo_rgba,
        },
    )
}

trait WorldState {
    fn update(&mut self);
    fn render(&self) -> Vec<MeshInstance>;
}

struct Grid {
    meshes: Vec<MeshInstance>,
}

impl Grid {
    fn new(ren: &mut Renderer) -> Self {
        let (mesh_data, albedo_data) = load_model();
        let mesh = ren.mesh_pool.allocate(&ren.device, &mesh_data);
        let albedo = ren
            .texture_pool
            .allocate(&ren.device, &ren.queue, &albedo_data);
        let mut meshes = Vec::new();
        for x in -5..5 {
            for y in -5..5 {
                let translation = glam::Vec3::new(x as f32, 0.0, y as f32) * 3.0;
                let transform = glam::Mat4::from_translation(translation);
                meshes.push(MeshInstance {
                    mesh,
                    albedo,
                    transform,
                });
            }
        }

        Self { meshes }
    }
}

impl WorldState for Grid {
    fn update(&mut self) {}

    fn render(&self) -> Vec<MeshInstance> {
        self.meshes.clone()
    }
}

struct Planet {
    speed: f32,
    offset: f32,
    radius: f32,
    size: f32,
}

struct Planets {
    start: std::time::Instant,
    planets: Vec<Planet>,
    mesh: MeshHandle,
    albedo: TextureHandle,
}

impl Planets {
    fn new(ren: &mut Renderer) -> Self {
        let start = std::time::Instant::now();
        let (mesh_data, albedo_data) = load_model();
        let mesh = ren.mesh_pool.allocate(&ren.device, &mesh_data);
        let albedo = ren
            .texture_pool
            .allocate(&ren.device, &ren.queue, &albedo_data);

        let mut planets = Vec::new();
        for i in 0..10 {
            let i = i as f32;
            planets.push(Planet {
                speed: 1.618 * 1.5 / i,
                offset: 0.0,
                radius: i * 2.0,
                size: 0.5,
            });
        }

        Self {
            start,
            planets,
            mesh,
            albedo,
        }
    }
}

impl WorldState for Planets {
    fn update(&mut self) {}

    fn render(&self) -> Vec<MeshInstance> {
        let elapsed = self.start.elapsed().as_secs_f32();

        let mut meshes = Vec::new();
        for planet in self.planets.iter() {
            let translation = glam::Vec3::new(0.0, 0.0, planet.radius);
            let translation = glam::Mat4::from_translation(translation);
            let theta = planet.speed * elapsed + planet.offset;
            let rotation = glam::Mat4::from_rotation_y(theta);
            meshes.push(MeshInstance {
                mesh: self.mesh,
                albedo: self.albedo,
                transform: rotation * translation,
            });
        }

        meshes
    }
}

async fn make_window_renderer(window: &winit::window::Window) -> Renderer {
    let size = window.inner_size();

    let instance = wgpu::Instance::new(wgpu::Backends::all());
    let surface = unsafe { instance.create_surface(window) };
    let adapter = instance
        .request_adapter(&wgpu::RequestAdapterOptions {
            power_preference: wgpu::PowerPreference::HighPerformance,
            compatible_surface: Some(&surface),
            force_fallback_adapter: false,
        })
        .await
        .unwrap();

    let (device, queue) = adapter
        .request_device(
            &wgpu::DeviceDescriptor {
                features: wgpu::Features::empty(),
                limits: wgpu::Limits::default(),
                label: None,
            },
            None,
        )
        .await
        .unwrap();

    let config = wgpu::SurfaceConfiguration {
        usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
        format: surface.get_preferred_format(&adapter).unwrap(),
        width: size.width,
        height: size.height,
        present_mode: wgpu::PresentMode::Fifo,
    };
    surface.configure(&device, &config);

    Renderer::new(size, surface, device, queue, config)
}

fn main() {
    let event_loop = EventLoop::new();
    let window = WindowBuilder::new().build(&event_loop).unwrap();
    let mut camera = Flycam::new(10.0, 0.002);
    let mut is_grabbed = false;
    let mut ren = pollster::block_on(make_window_renderer(&window));
    // let mut state: Box<dyn WorldState> = Box::new(Planets::new(&mut ren));
    let mut state: Box<dyn WorldState> = Box::new(Grid::new(&mut ren));

    let lights = vec![
        PointLight {
            center: glam::Vec3A::new(0.0, 5.0, 0.0),
            intensity: glam::Vec3A::new(100.0, 100.0, 100.0),
        },
        PointLight {
            center: glam::Vec3A::new(-7.0, 5.0, 7.0),
            intensity: glam::Vec3A::new(100.0, 0.0, 0.0),
        },
        PointLight {
            center: glam::Vec3A::new(7.0, 5.0, 7.0),
            intensity: glam::Vec3A::new(0.0, 100.0, 0.0),
        },
        PointLight {
            center: glam::Vec3A::new(0.0, 5.0, -7.0),
            intensity: glam::Vec3A::new(0.0, 0.0, 100.0),
        },
    ];

    event_loop.run(move |event, _, control_flow| match event {
        Event::RedrawRequested(_) => match ren.render(&camera, &state.render(), &lights) {
            Ok(_) => {}
            Err(wgpu::SurfaceError::Lost) => ren.resize(ren.size),
            Err(wgpu::SurfaceError::OutOfMemory) => *control_flow = ControlFlow::Exit,
            Err(e) => println!("error: {:?}", e),
        },
        Event::MainEventsCleared => {
            camera.update();
            state.update();
            window.request_redraw();
        }
        Event::DeviceEvent { ref event, .. } => match event {
            DeviceEvent::MouseMotion { delta } => {
                if is_grabbed {
                    camera.process_mouse(delta.0, delta.1);
                }
            }
            _ => {}
        },
        Event::WindowEvent {
            ref event,
            window_id,
        } if window_id == window.id() => match event {
            WindowEvent::KeyboardInput {
                input:
                    KeyboardInput {
                        virtual_keycode: Some(key),
                        state,
                        ..
                    },
                ..
            } => {
                if *state == ElementState::Pressed && *key == VirtualKeyCode::Escape {
                    if is_grabbed {
                        window.set_cursor_grab(false).unwrap();
                        window.set_cursor_visible(true);
                        is_grabbed = false;
                    }
                } else {
                    camera.process_keyboard(*key, *state);
                }
            }
            WindowEvent::MouseInput {
                button: MouseButton::Left,
                state: ElementState::Pressed,
                ..
            } => {
                if !is_grabbed {
                    window.set_cursor_grab(true).unwrap();
                    window.set_cursor_visible(false);
                    is_grabbed = true;
                }
            }
            WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
            WindowEvent::Resized(physical_size) => {
                ren.resize(*physical_size);
                camera.resize(physical_size.width, physical_size.height);
            }
            WindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
                ren.resize(**new_inner_size);
                camera.resize(new_inner_size.width, new_inner_size.height);
            }
            _ => {}
        },
        _ => {}
    });
}
