use std::convert::From;
use std::fmt;
use std::num::ParseFloatError;
use std::ops::Add;
use std::ops::Div;
use std::ops::Index;
use std::ops::Mul;
use std::ops::Neg;
use std::ops::Sub;
use std::str;

use serde_derive::Deserialize;
use serde_derive::Serialize;

#[derive(Default, Debug, Deserialize, Serialize, PartialEq, Copy, Clone)]
pub struct Vec3 {
    pub x: f32,
    pub y: f32,
    pub z: f32,
}

pub fn cross(v1: Vec3, v2: Vec3) -> Vec3 {
    Vec3 {
        x: v1.y * v2.z - v1.z * v2.y,
        y: v1.z * v2.x - v1.x * v2.z,
        z: v1.x * v2.y - v1.y * v2.x,
    }
}

pub fn dot(v1: Vec3, v2: Vec3) -> f32 {
    v1.x * v2.x + v1.y * v2.y + v1.z * v2.z
}

impl Vec3 {
    pub fn new(x: f32, y: f32, z: f32) -> Vec3 {
        Vec3 { x, y, z }
    }

    pub fn min(self) -> f32 {
        self.x.min(self.y).min(self.z)
    }

    pub fn max(self) -> f32 {
        self.x.max(self.y).max(self.z)
    }

    pub fn length(self) -> f32 {
        self.squared_length().sqrt()
    }

    pub fn squared_length(self) -> f32 {
        self.x * self.x + self.y * self.y + self.z * self.z
    }

    pub fn unit_vector(self) -> Vec3 {
        self / self.length()
    }

    pub fn make_unit_vector(&mut self) {
        *self = self.unit_vector();
    }
}

impl From<[f32; 3]> for Vec3 {
    fn from(v: [f32; 3]) -> Self {
        Vec3 {
            x: v[0],
            y: v[1],
            z: v[2],
        }
    }
}

impl fmt::Display for Vec3 {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{} {} {}", self.x, self.y, self.z)
    }
}

impl str::FromStr for Vec3 {
    type Err = ParseFloatError;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let coords: Vec<&str> = s.split(' ').collect();
        Ok(Vec3 {
            x: coords[0].parse::<f32>()?,
            y: coords[1].parse::<f32>()?,
            z: coords[2].parse::<f32>()?,
        })
    }
}

impl Add<f32> for Vec3 {
    type Output = Vec3;

    fn add(self, r: f32) -> Vec3 {
        Vec3 {
            x: self.x + r,
            y: self.y + r,
            z: self.z + r,
        }
    }
}

impl Add for Vec3 {
    type Output = Vec3;

    fn add(self, r: Vec3) -> Vec3 {
        Vec3 {
            x: self.x + r.x,
            y: self.y + r.y,
            z: self.z + r.z,
        }
    }
}

impl Div<Vec3> for f32 {
    type Output = Vec3;

    fn div(self, r: Vec3) -> Vec3 {
        Vec3 {
            x: self / r.x,
            y: self / r.y,
            z: self / r.z,
        }
    }
}

impl Div<f32> for Vec3 {
    type Output = Vec3;

    fn div(self, r: f32) -> Vec3 {
        Vec3 {
            x: self.x / r,
            y: self.y / r,
            z: self.z / r,
        }
    }
}

impl Index<usize> for Vec3 {
    type Output = f32;
    fn index(&self, idx: usize) -> &f32 {
        match idx {
            0 => &self.x,
            1 => &self.y,
            2 => &self.z,
            _ => panic!("idx {} out of range for vec3", idx),
        }
    }
}

impl Mul for Vec3 {
    type Output = Vec3;

    fn mul(self, r: Vec3) -> Vec3 {
        Vec3 {
            x: self.x * r.x,
            y: self.y * r.y,
            z: self.z * r.z,
        }
    }
}

impl Mul<Vec3> for f32 {
    type Output = Vec3;

    fn mul(self, v: Vec3) -> Vec3 {
        Vec3 {
            x: v.x * self,
            y: v.y * self,
            z: v.z * self,
        }
    }
}

impl Mul<f32> for Vec3 {
    type Output = Vec3;

    fn mul(self, r: f32) -> Vec3 {
        Vec3 {
            x: self.x * r,
            y: self.y * r,
            z: self.z * r,
        }
    }
}

impl Neg for Vec3 {
    type Output = Vec3;

    fn neg(self) -> Vec3 {
        -1. * self
    }
}

impl Sub for Vec3 {
    type Output = Vec3;

    fn sub(self, r: Vec3) -> Vec3 {
        Vec3 {
            x: self.x - r.x,
            y: self.y - r.y,
            z: self.z - r.z,
        }
    }
}

#[cfg(test)]
mod tests {
    use std::f32::consts::PI;
    use std::str::FromStr;

    use super::*;

    #[test]
    fn vec_add() {
        let v0 = Vec3::new(1., 2., 3.);
        let v1 = Vec3::new(1., 1., 1.);
        assert_eq!(v0 + v1, Vec3::new(2., 3., 4.));
    }

    #[test]
    fn vec_div() {
        let v0 = Vec3::new(1., 2., 4.);
        assert_eq!(v0 / 2., Vec3::new(0.5, 1., 2.));
    }

    #[test]
    fn vec_mul() {
        let v0 = Vec3::new(1., 2., 4.);
        assert_eq!(v0 * 0.5, Vec3::new(0.5, 1., 2.));
        assert_eq!(v0 * v0, Vec3::new(1., 4., 16.));
    }

    #[test]
    fn vec_sub() {
        let v0 = Vec3::new(1., 2., 3.);
        let v1 = Vec3::new(1., 1., 1.);
        assert_eq!(v0 - v1, Vec3::new(0., 1., 2.));
    }

    #[test]
    fn vec_idx() {
        let v0 = Vec3::new(1., 2., 3.);
        assert_eq!(v0[2], 3.);
    }

    #[test]
    fn vec_display() {
        let v0 = Vec3::new(1., 2., 3.);
        assert_eq!(format!("{}", v0), "1 2 3".to_owned());
    }

    #[test]
    fn vec_from_str() {
        assert_eq!(Vec3::from_str("1. 2. 3.").unwrap(), Vec3::new(1., 2., 3.));
    }

    #[test]
    fn vec_str_roundtrip() {
        let v = Vec3::from_str("1 2 3").unwrap();
        let s = format!("{}", v);
        assert_eq!(v, Vec3::from_str(&s).unwrap());
    }

    #[test]
    fn vec_dot() {
        let v0 = Vec3::new(1., 0., 0.);
        let v1 = Vec3::new(-1., 0., 0.);
        assert_eq!(dot(v0, v1), v0.length() * v1.length() * PI.cos());
    }

    #[test]
    fn vec_cross() {
        let v0 = Vec3::new(1., 0., 0.);
        let v1 = Vec3::new(0., 1., 0.);
        assert_eq!(cross(v0, v1), Vec3::new(0., 0., 1.));
    }
}
