extern crate glium;

use glium::{DisplayBuild, Display, Frame, Program, Surface};
use glium::glutin::{Event,WindowBuilder};
use glium::texture::{RawImage2d,Texture2d};

use therm_model::graph::SceneGraph;

const VERTEX_SHADER: &'static str = include_str!("../data/shaders/vertex.glsl");
const FRAGMENT_SHADER: &'static str = include_str!("../data/shaders/fragment.glsl");

pub struct Graphics<'a> {
    disp: Display,
    rt: f32,
    light: [f32;3],
    graph: SceneGraph<'a>,
    program: Program,
    tex: Texture2d,
}

fn nudge(g: &mut SceneGraph, dx:f32, dy:f32, dz:f32) {
    match g {
        &mut SceneGraph::Translate(ref mut x, ref mut y, ref mut z, _) => {
            *x += dx;
            *y += dy;
            *z += dz;
        },
        _ => {},
    }
}


impl<'a> Graphics<'a> {
    pub fn new(sg: SceneGraph<'a>, t: RawImage2d<(u16,u16,u16,u16)>) -> Self {
        let disp = WindowBuilder::new()
            .with_title(format!("utmyen"))
            .with_depth_buffer(24)
            .build_glium()
            .unwrap();
        let program = Program::from_source(
            &disp,
            VERTEX_SHADER,
            FRAGMENT_SHADER,
            None).unwrap();
        let tex = Texture2d::with_format(
            &disp,
            t,
            glium::texture::UncompressedFloatFormat::U16U16U16U16,
            glium::texture::MipmapsOption::NoMipmap).unwrap();
        Graphics {
            disp: disp,
            rt: 0.0,
            light: [-1.0, 0.4, 0.9f32],
            graph: sg,
            program: program,
            tex: tex,
        }
    }

    pub fn run(&mut self) {
        loop {
            let mut tgt = self.disp.draw();
            if ! (Target { frame: &mut tgt }).step(self) {
                return;
            }
            tgt.finish().unwrap();

            for ev in self.disp.poll_events() {
                match ev {
                    Event::Closed => return,
                    Event::KeyboardInput(_, n, _) =>
                        match n {
                            9  => return,
                            24 => self.rt -= 0.1,
                            25 => nudge(&mut self.graph,0.0,0.0,-DX),
                            26 => self.rt += 0.1,
                            38 => nudge(&mut self.graph,DX,0.0,0.0),
                            39 => nudge(&mut self.graph,0.0,0.0,DX),
                            40 => nudge(&mut self.graph,-DX,0.0,0.0),
                            44 => nudge(&mut self.graph,0.0,-DX,0.0),
                            45 => nudge(&mut self.graph,0.0,DX,0.0),
                            _  => {},
                        },
                    _ => {},
                }
            }
        }
    }
}

const DX: f32 = 0.01;

struct Target<'a> {
    frame: &'a mut Frame,
}

impl<'a> Target<'a> {
    fn step(&mut self, g: &mut Graphics) -> bool {

        let params = glium::DrawParameters {
            depth: glium::Depth {
                test: glium::draw_parameters::DepthTest::IfLess,
                write: true,
                .. Default::default()
            },
            .. Default::default()
        };

        self.frame.clear_color_and_depth((0.0, 0.0, 0.0, 1.0), 1.0);

        let mytex = glium::uniforms::Sampler::new(&g.tex)
            .minify_filter(glium::uniforms::MinifySamplerFilter::Nearest)
            .magnify_filter(glium::uniforms::MagnifySamplerFilter::Nearest);

        let per = self.perspective();
        g.graph.traverse(&mut |model, matrix| {
            self.frame.draw(&model.get_vbuf(&g.disp),
                            &model.get_ibuf(&g.disp),
                            &g.program,
                            &uniform! {
                                perspective: per,
                                u_light: g.light,
                                matrix: *matrix,
                                tx: mytex,
                            },
                            &params).unwrap();
        });

        return true;
    }

    fn perspective(&self) -> [[f32;4];4] {
        let (width, height) = self.frame.get_dimensions();
        let aspect_ratio = height as f32 / width as f32;

        let fov: f32 = 3.141592 / 3.0;
        let zfar = 1024.0;
        let znear = 0.1;

        let f = 1.0 / (fov / 2.0).tan();

        [
            [f *   aspect_ratio   ,    0.0,              0.0              ,   0.0],
            [         0.0         ,     f ,              0.0              ,   0.0],
            [         0.0         ,    0.0,  (zfar+znear)/(zfar-znear)    ,   1.0],
            [         0.0         ,    0.0, -(2.0*zfar*znear)/(zfar-znear),   0.0],
        ]
    }
}
