mod device;
mod model;
mod scripting;
mod gameobject;

#[macro_use]
extern crate glium;
extern crate nalgebra_glm as glm;

use model::Model;

use scripting::{ ScriptLang, Lua };

use gameobject::{ Spatial, Mesh, Node, GameObject };

fn main() {
    use glium::{glutin, Surface};
    
    let events_loop = glutin::event_loop::EventLoop::new();
    let wb = glutin::window::WindowBuilder::new()
        .with_title("Couch Game Engine");
    let cb = glutin::ContextBuilder::new()
	.with_depth_buffer(24);

    let display = glium::Display::new(wb, cb, &events_loop).unwrap();

    let vertex_shader_src = include_str!("flat.vert");
    let fragment_shader_src = include_str!("flat.frag");
    let program = glium::Program::from_source(&display, vertex_shader_src, fragment_shader_src, None).unwrap();

    let mut device_manager = crate::device::DeviceManager::new();

    let mut root = Node::new("_root_".to_string(), GameObject::Null);
    
    let mut cube = Mesh::new(Model::new(&display, "Box.glb"));
    cube.set_transform(gameobject::Transform {
	position: glm::vec3(0.0, 0.0, -10.0),
	rotation: glm::vec3(0.0, 15.0_f32.to_radians(), 0.0),
	scale: glm::vec3(1.0, 1.0, 1.0),
    });
    root.add_child(Node::new("cube".to_string(), GameObject::Mesh(Box::new(cube))));
	
    let mut script_lang = Lua::new();
    script_lang.init();

    let mut last_draw = std::time::Instant::now();

    events_loop.run(move |ev, _, control_flow| {
        let next_frame_time = std::time::Instant::now() +
            std::time::Duration::from_nanos(16_666_667);
        *control_flow = glutin::event_loop::ControlFlow::WaitUntil(next_frame_time);
	match ev {
	    glutin::event::Event::WindowEvent { event, .. } => {
		if event == glutin::event::WindowEvent::CloseRequested {
                    *control_flow = glutin::event_loop::ControlFlow::Exit;
		}
	    },
	    glutin::event::Event::DeviceEvent { event, .. } => {
		device_manager.handle(event);
	    },
	    glutin::event::Event::MainEventsCleared => {
		let now = std::time::Instant::now();
		let delta = (now - last_draw).as_secs_f32();
		last_draw = now;

		script_lang.update(delta);

		let mut target = display.draw();
		let params = glium::DrawParameters {
		    depth: glium::Depth {
			test: glium::DepthTest::IfLess,
			write: true,
			.. Default::default()
		    },
		    backface_culling: glium::draw_parameters::BackfaceCullingMode::CullClockwise,
		    .. Default::default()
		};
		target.clear_color_and_depth((0.0, 0.0, 0.0, 1.0), 1.0);
		
		let cube: &mut Node = root.find_node("cube").unwrap();
		if let GameObject::Mesh(cube) = &mut cube.gameobject {
		    let mut transform = cube.get_transform();
		    if device_manager.is_pressed(&device::Key::W) {
			transform.position.z += 1.0 * delta;
		    } else if device_manager.is_pressed(&device::Key::S) {
			transform.position.z -= 1.0 * delta;
		    }
		    if device_manager.is_pressed(&device::Key::A) {
			transform.rotation.x += 1.0 * delta;
		    } else if device_manager.is_pressed(&device::Key::D) {
			transform.rotation.x -= 1.0 * delta;
		    }
		    cube.set_transform(transform);
		    let mvp = glm::perspective::<f32>(4.0/3.0, 45.0_f32.to_radians(), 0.2, 100.0);
		    let mvp = glm::translate::<f32>(&mvp, &cube.get_transform().position);
		    let mvp = glm::rotate_x(&mvp, cube.get_transform().rotation.x);

		    
		    
		    let uniforms = uniform! {
			MVP: *mvp.as_ref(),
		    };
		    cube.model.draw(&mut target, &program, &uniforms, &params);
		}
		target.finish().unwrap();
	    }
	    _ => (),
        };
    });
}
