mod gl {
    pub use gl::types::*;
    pub use gl::*;
}

#[derive(Debug,PartialEq)]
pub struct OBJError(String);

#[derive(Debug,PartialEq)]
pub struct Vertex {
    pub x: f32,
    pub y: f32,
    pub z: f32,
    pub w: f32,
}

#[derive(Debug,PartialEq)]
pub struct UV {
    pub u: f32,
    pub v: f32,
}

#[derive(Debug,PartialEq)]
pub struct Normal {
    pub x: f32,
    pub y: f32,
    pub z: f32,
}

#[derive(Debug,PartialEq)]
pub struct Point {
    vertex: usize,
    uv: Option<usize>,
    normal: Option<usize>,
}

impl Point {
    fn parse(s: &str) -> Result<Point, OBJError> {
        let err = || OBJError(format!("Invalid point: {}", s));
        let mut chunks = s.split("/");

        let vertex = chunks.next().ok_or(err())?.parse().map_err(|_| err())?;
        let uv = match chunks.next() {
            Some("") => None,
            Some(n) => Some(n.parse().map_err(|_| err())?),
            None => None,
        };

        let normal = match chunks.next() {
            Some("") => None,
            Some(n) => Some(n.parse().map_err(|_| err())?),
            None => None,
        };

        Ok(Point { vertex, uv, normal, })
    }
}

#[derive(Debug,PartialEq)]
pub struct Face(Vec<Point>);

#[derive(Debug,PartialEq)]
pub struct OBJFile {
    pub vertices: Vec<Vertex>,
    pub texcoords: Vec<UV>,
    pub normals: Vec<Normal>,
    pub faces: Vec<Face>,
}

impl OBJFile {
    pub fn parse(s: &str) -> Result<OBJFile, OBJError> {
        let mut vertices = Vec::new();
        let mut texcoords = Vec::new();
        let mut normals = Vec::new();
        let mut faces = Vec::new();

        for line in s.lines() {
            let err = || OBJError(format!("Invalid line: {}", line));
            let mut chunks = line.split_whitespace();
            match chunks.next() {
                Some("v") => vertices.push(Vertex {
                    x: chunks.next().ok_or(err())?.parse().map_err(|_| err())?,
                    y: chunks.next().ok_or(err())?.parse().map_err(|_| err())?,
                    z: chunks.next().ok_or(err())?.parse().map_err(|_| err())?,
                    w: 1.0,
                }),

                Some("vt") => texcoords.push(UV {
                    u: chunks.next().ok_or(err())?.parse().map_err(|_| err())?,
                    v: chunks.next().ok_or(err())?.parse().map_err(|_| err())?,
                }),

                Some("vn") => normals.push(Normal {
                    x: chunks.next().ok_or(err())?.parse().map_err(|_| err())?,
                    y: chunks.next().ok_or(err())?.parse().map_err(|_| err())?,
                    z: chunks.next().ok_or(err())?.parse().map_err(|_| err())?,
                }),

                Some("f") =>
                    match chunks.map(Point::parse).collect() {
                        Ok(ps) => faces.push(Face(ps)),
                        Err(e) => return Err(e),
                    }

                _ => (),
            }
        }

        Ok(OBJFile { vertices, texcoords, normals, faces })
    }

    pub fn to_bare_object(&self) -> Result<Vec<gl::GLfloat>, OBJError> {
        let mut rs = Vec::new();
        for &Face(ref f) in self.faces.iter() {
            if f.len() != 3 {
                return Err(OBJError(format!("Face length not, uh, three")));
            }

            for pt in f {
                let vertex: &Vertex = self
                    .vertices.get(pt.vertex - 1)
                    .ok_or(OBJError(format!("Face length not, uh, three")))?;
                rs.push(vertex.x);
                rs.push(vertex.y);
                rs.push(vertex.z);
            }
        }

        Ok(rs)
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn just_a_vertex() {
        assert_eq!(
            OBJFile::parse("v 1.0 2.0 3.0\n").unwrap(),
            OBJFile {
                vertices: vec![ Vertex { x: 1.0, y: 2.0, z: 3.0, w: 1.0 } ],
                texcoords: vec![],
                normals: vec![],
                faces: vec![],
            },
        );
    }

    #[test]
    fn just_a_uv() {
        assert_eq!(
            OBJFile::parse("vt 1.0 0.5\n").unwrap(),
            OBJFile {
                vertices: vec![],
                texcoords: vec![ UV { u: 1.0, v: 0.5 } ],
                normals: vec![],
                faces: vec![],
            },
        );
    }

    #[test]
    fn simple_tri() {
        assert_eq!(
            OBJFile::parse("v 0 0 0
                            v 0 1 0
                            v 1 0 0
                            f 1 2 3").unwrap(),
            OBJFile {
                vertices: vec![
                    Vertex { x: 0.0, y: 0.0, z: 0.0, w: 1.0 },
                    Vertex { x: 0.0, y: 1.0, z: 0.0, w: 1.0 },
                    Vertex { x: 1.0, y: 0.0, z: 0.0, w: 1.0 },
                ],
                texcoords: vec![],
                normals: vec![],
                faces: vec![ Face(vec![
                    Point { vertex: 1, uv: None, normal: None },
                    Point { vertex: 2, uv: None, normal: None },
                    Point { vertex: 3, uv: None, normal: None },
                ]) ],
            },
        );
    }

    #[test]
    fn simple_tri_with_uvs() {
        assert_eq!(
            OBJFile::parse("v 0 0 0
                            v 0 1 0
                            v 1 0 0
                            vt 0 0
                            vt 1 1
                            vt 1 0
                            f 1/1 2/2 3/3\n").unwrap(),

            OBJFile {
                vertices: vec![
                    Vertex { x: 0.0, y: 0.0, z: 0.0, w: 1.0 },
                    Vertex { x: 0.0, y: 1.0, z: 0.0, w: 1.0 },
                    Vertex { x: 1.0, y: 0.0, z: 0.0, w: 1.0 },
                ],
                texcoords: vec![
                    UV { u: 0.0, v: 0.0 },
                    UV { u: 1.0, v: 1.0 },
                    UV { u: 1.0, v: 0.0 },
                ],
                normals: vec![],
                faces: vec![ Face(vec![
                    Point { vertex: 1, uv: Some(1), normal: None },
                    Point { vertex: 2, uv: Some(2), normal: None },
                    Point { vertex: 3, uv: Some(3), normal: None },
                ]) ],
            },
        );
    }

}
