use crate::{Euler, Matrix};
use ffi;
use glib::translate::*;
use std::mem;

glib_wrapper! {
    #[derive(Debug, Hash)] // PartialOrd, Ord,
    pub struct Quaternion(Boxed<ffi::CoglQuaternion>);

    match fn {
        copy => |ptr| ffi::cogl_quaternion_copy(mut_override(ptr)),
        free => |ptr| ffi::cogl_quaternion_free(ptr),
        get_type => || ffi::cogl_quaternion_get_gtype(),
    }
}

impl Quaternion {
    ///
    /// ## `b`
    /// A `Quaternion`
    pub fn dot_product(&self, b: &Quaternion) -> f32 {
        unsafe { ffi::cogl_quaternion_dot_product(self.to_glib_none().0, b.to_glib_none().0) }
    }

    ///
    pub fn get_rotation_angle(&self) -> f32 {
        unsafe { ffi::cogl_quaternion_get_rotation_angle(self.to_glib_none().0) }
    }

    ///
    /// ## `vector3`
    /// an allocated 3-float array
    pub fn get_rotation_axis(&self) -> f32 {
        unsafe {
            let mut vector3 = mem::MaybeUninit::uninit();
            ffi::cogl_quaternion_get_rotation_axis(self.to_glib_none().0, vector3.as_mut_ptr());
            let vector3 = vector3.assume_init();
            vector3
        }
    }

    /// Initializes a quaternion that rotates `angle` degrees around the
    /// axis vector (`x`, `y`, `z`). The axis vector does not need to be
    /// normalized.
    ///
    /// ## `angle`
    /// The angle you want to rotate around the given axis
    /// ## `x`
    /// The x component of your axis vector about which you want to
    /// rotate.
    /// ## `y`
    /// The y component of your axis vector about which you want to
    /// rotate.
    /// ## `z`
    /// The z component of your axis vector about which you want to
    /// rotate.
    pub fn init(&mut self, angle: f32, x: f32, y: f32, z: f32) {
        unsafe {
            ffi::cogl_quaternion_init(self.to_glib_none_mut().0, angle, x, y, z);
        }
    }

    //TODO:
    // /// Initializes a quaternion that rotates `angle` degrees around the
    // /// given `axis` vector. The axis vector does not need to be
    // /// normalized.
    // ///
    // /// ## `angle`
    // /// The angle to rotate around `axis3f`
    // /// ## `axis3f`
    // /// your 3 component axis vector about which you want to rotate.
    // pub fn init_from_angle_vector(&mut self, angle: f32, axis3f: f32) {
    //     // unsafe {
    //     //     ffi::cogl_quaternion_init_from_angle_vector(
    //     //         self.to_glib_none_mut().0,
    //     //         angle,
    //     //         axis3f,
    //     //     );
    //     // }
    // }

    //TODO:
    // /// Initializes a [w (x, y,z)] quaternion directly from an array of 4
    // /// floats: [w,x,y,z].
    // ///
    // /// ## `array`
    // /// An array of 4 floats w,(x,y,z)
    // pub fn init_from_array(&mut self, array: f32) {
    //     // unsafe {
    //     //     ffi::cogl_quaternion_init_from_array(self.to_glib_none_mut().0, array);
    //     // }
    // }

    ///
    /// ## `euler`
    /// A `Euler` with which to initialize the quaternion
    pub fn init_from_euler(&mut self, euler: &Euler) {
        unsafe {
            ffi::cogl_quaternion_init_from_euler(self.to_glib_none_mut().0, euler.to_glib_none().0);
        }
    }

    /// Initializes a quaternion from a rotation matrix.
    /// ## `matrix`
    /// A rotation matrix with which to initialize the quaternion
    pub fn init_from_matrix(&mut self, matrix: &Matrix) {
        unsafe {
            ffi::cogl_quaternion_init_from_matrix(
                self.to_glib_none_mut().0,
                matrix.to_glib_none().0,
            );
        }
    }

    ///
    /// ## `src`
    /// A `Quaternion` with which to initialize `self`
    pub fn init_from_quaternion(&mut self, src: &mut Quaternion) {
        unsafe {
            ffi::cogl_quaternion_init_from_quaternion(
                self.to_glib_none_mut().0,
                src.to_glib_none_mut().0,
            );
        }
    }

    /// XXX: check which direction this rotates
    ///
    /// ## `angle`
    /// The angle to rotate around the x axis
    pub fn init_from_x_rotation(&mut self, angle: f32) {
        unsafe {
            ffi::cogl_quaternion_init_from_x_rotation(self.to_glib_none_mut().0, angle);
        }
    }

    ///
    /// ## `angle`
    /// The angle to rotate around the y axis
    pub fn init_from_y_rotation(&mut self, angle: f32) {
        unsafe {
            ffi::cogl_quaternion_init_from_y_rotation(self.to_glib_none_mut().0, angle);
        }
    }

    ///
    /// ## `angle`
    /// The angle to rotate around the z axis
    pub fn init_from_z_rotation(&mut self, angle: f32) {
        unsafe {
            ffi::cogl_quaternion_init_from_z_rotation(self.to_glib_none_mut().0, angle);
        }
    }

    /// Initializes the quaternion with the canonical quaternion identity
    /// [1 (0, 0, 0)] which represents no rotation. Multiplying a
    /// quaternion with this identity leaves the quaternion unchanged.
    ///
    /// You might also want to consider using
    /// `cogl_get_static_identity_quaternion`.
    ///
    pub fn init_identity(&mut self) {
        unsafe {
            ffi::cogl_quaternion_init_identity(self.to_glib_none_mut().0);
        }
    }

    ///
    pub fn invert(&mut self) {
        unsafe {
            ffi::cogl_quaternion_invert(self.to_glib_none_mut().0);
        }
    }

    /// This combines the rotations of two quaternions into `self`. The
    /// operation is not commutative so the order is important because AxB
    /// != BxA. Cogl follows the standard convention for quaternions here
    /// so the rotations are applied `right` to `left`. This is similar to the
    /// combining of matrices.
    ///
    /// `<note>`It is possible to multiply the `a` quaternion in-place, so
    /// `self` can be equal to `a` but can't be equal to `b`.`</note>`
    ///
    /// ## `left`
    /// The second `Quaternion` rotation to apply
    /// ## `right`
    /// The first `Quaternion` rotation to apply
    pub fn multiply(&mut self, left: &Quaternion, right: &Quaternion) {
        unsafe {
            ffi::cogl_quaternion_multiply(
                self.to_glib_none_mut().0,
                left.to_glib_none().0,
                right.to_glib_none().0,
            );
        }
    }

    /// Performs a normalized linear interpolation between two quaternions.
    /// That is it does a linear interpolation of the quaternion components
    /// and then normalizes the result. This will follow the shortest arc
    /// between the two orientations (just like the `slerp` function) but
    /// will not progress at a constant speed. Unlike `slerp` nlerp is
    /// commutative which is useful if you are blending animations
    /// together. (I.e. nlerp (tmp, a, b) followed by nlerp (result, tmp,
    /// d) is the same as nlerp (tmp, a, d) followed by nlerp (result, tmp,
    /// b)). Finally nlerp is cheaper than slerp so it can be a good choice
    /// if you don't need the constant speed property of the `slerp` function.
    ///
    /// Notable properties:
    /// `<itemizedlist>`
    /// `<listitem>`
    /// commutative: Yes
    /// `</listitem>`
    /// `<listitem>`
    /// constant velocity: No
    /// `</listitem>`
    /// `<listitem>`
    /// torque minimal (travels along the surface of the 4-sphere): Yes
    /// `</listitem>`
    /// `<listitem>`
    /// faster than `Quaternion::slerp`
    /// `</listitem>`
    /// `</itemizedlist>`
    /// ## `a`
    /// The first `Quaternion`
    /// ## `b`
    /// The second `Quaternion`
    /// ## `t`
    /// The factor in the range [0,1] used to interpolate between
    /// quaterion `a` and `b`.
    pub fn nlerp(&mut self, a: &Quaternion, b: &Quaternion, t: f32) {
        unsafe {
            ffi::cogl_quaternion_nlerp(
                self.to_glib_none_mut().0,
                a.to_glib_none().0,
                b.to_glib_none().0,
                t,
            );
        }
    }

    ///
    pub fn normalize(&mut self) {
        unsafe {
            ffi::cogl_quaternion_normalize(self.to_glib_none_mut().0);
        }
    }

    ///
    /// ## `exponent`
    /// the exponent
    pub fn pow(&mut self, exponent: f32) {
        unsafe {
            ffi::cogl_quaternion_pow(self.to_glib_none_mut().0, exponent);
        }
    }

    /// Performs a spherical linear interpolation between two quaternions.
    ///
    /// Noteable properties:
    /// `<itemizedlist>`
    /// `<listitem>`
    /// commutative: No
    /// `</listitem>`
    /// `<listitem>`
    /// constant velocity: Yes
    /// `</listitem>`
    /// `<listitem>`
    /// torque minimal (travels along the surface of the 4-sphere): Yes
    /// `</listitem>`
    /// `<listitem>`
    /// more expensive than `Quaternion::nlerp`
    /// `</listitem>`
    /// `</itemizedlist>`
    /// ## `a`
    /// The first `Quaternion`
    /// ## `b`
    /// The second `Quaternion`
    /// ## `t`
    /// The factor in the range [0,1] used to interpolate between
    /// quaternion `a` and `b`.
    pub fn slerp(&mut self, a: &Quaternion, b: &Quaternion, t: f32) {
        unsafe {
            ffi::cogl_quaternion_slerp(
                self.to_glib_none_mut().0,
                a.to_glib_none().0,
                b.to_glib_none().0,
                t,
            );
        }
    }

    ///
    /// ## `prev`
    /// A `Quaternion` used before `a`
    /// ## `a`
    /// The first `Quaternion`
    /// ## `b`
    /// The second `Quaternion`
    /// ## `next`
    /// A `Quaternion` that will be used after `b`
    /// ## `t`
    /// The factor in the range [0,1] used to interpolate between
    /// quaternion `a` and `b`.
    pub fn squad(
        &mut self,
        prev: &Quaternion,
        a: &Quaternion,
        b: &Quaternion,
        next: &Quaternion,
        t: f32,
    ) {
        unsafe {
            ffi::cogl_quaternion_squad(
                self.to_glib_none_mut().0,
                prev.to_glib_none().0,
                a.to_glib_none().0,
                b.to_glib_none().0,
                next.to_glib_none().0,
                t,
            );
        }
    }

    //pub fn equal(v1: /*Unimplemented*/Option<Fundamental: Pointer>, v2: /*Unimplemented*/Option<Fundamental: Pointer>) -> Bool {
    //    unsafe { TODO: call cogl_sys:cogl_quaternion_equal() }
    //}
}

//TODO:
// impl PartialEq for Quaternion {
//     #[inline]
//     fn eq(&self, other: &Self) -> bool {
//         // self.equal(other)
//     }
// }

// impl Eq for Quaternion {}
