use crate::{Bool, Euler, Quaternion};
use ffi;
use glib::translate::*;

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

    match fn {
        copy => |ptr| ffi::cogl_matrix_copy(mut_override(ptr)),
        free => |ptr| ffi::cogl_matrix_free(ptr),
        get_type => || ffi::cogl_matrix_get_gtype(),
    }
}

impl Matrix {
    /// Multiplies `self` by the given frustum perspective matrix.
    /// ## `left`
    /// X position of the left clipping plane where it
    ///  intersects the near clipping plane
    /// ## `right`
    /// X position of the right clipping plane where it
    ///  intersects the near clipping plane
    /// ## `bottom`
    /// Y position of the bottom clipping plane where it
    ///  intersects the near clipping plane
    /// ## `top`
    /// Y position of the top clipping plane where it intersects
    ///  the near clipping plane
    /// ## `z_near`
    /// The distance to the near clipping plane (Must be positive)
    /// ## `z_far`
    /// The distance to the far clipping plane (Must be positive)
    pub fn frustum(
        &mut self,
        left: f32,
        right: f32,
        bottom: f32,
        top: f32,
        z_near: f32,
        z_far: f32,
    ) {
        unsafe {
            ffi::cogl_matrix_frustum(
                self.to_glib_none_mut().0,
                left,
                right,
                bottom,
                top,
                z_near,
                z_far,
            );
        }
    }

    //TODO:
    // /// Casts `self` to a float array which can be directly passed to OpenGL.
    // ///
    // /// # Returns
    // ///
    // /// a pointer to the float array
    // pub fn get_array(&self) -> f32 {
    //     // unsafe { ffi::cogl_matrix_get_array(self.to_glib_none().0) }
    // }

    //TODO:
    // /// Gets the inverse transform of a given matrix and uses it to initialize
    // /// a new `Matrix`.
    // ///
    // /// `<note>`Although the first parameter is annotated as const to indicate
    // /// that the transform it represents isn't modified this function may
    // /// technically save a copy of the inverse transform within the given
    // /// `Matrix` so that subsequent requests for the inverse transform may
    // /// avoid costly inversion calculations.`</note>`
    // /// ## `inverse`
    // /// The destination for a 4x4 inverse transformation matrix
    // ///
    // /// # Returns
    // ///
    // /// `true` if the inverse was successfully calculated or `false`
    // ///  for degenerate transformations that can't be inverted (in this case the
    // ///  `inverse` matrix will simply be initialized with the identity matrix)
    // pub fn get_inverse(&self) -> (Bool, Matrix) {
    //     // unsafe {
    //     //     let mut inverse = Matrix::uninitialized();
    //     //     let ret = ffi::cogl_matrix_get_inverse(
    //     //         self.to_glib_none().0,
    //     //         inverse.to_glib_none_mut().0,
    //     //     );
    //     //     (ret, inverse)
    //     // }
    // }

    //TODO:
    // /// Initializes `self` with the contents of `array`
    // /// ## `array`
    // /// A linear array of 16 floats (column-major order)
    // pub fn init_from_array(&mut self, array: f32) {
    //     // unsafe {
    //     //     ffi::cogl_matrix_init_from_array(self.to_glib_none_mut().0, array);
    //     // }
    // }

    /// Initializes `self` from a `Euler` rotation.
    ///
    /// ## `euler`
    /// A `Euler`
    pub fn init_from_euler(&mut self, euler: &Euler) {
        unsafe {
            ffi::cogl_matrix_init_from_euler(self.to_glib_none_mut().0, euler.to_glib_none().0);
        }
    }

    /// Initializes `self` from a `Quaternion` rotation.
    /// ## `quaternion`
    /// A `Quaternion`
    pub fn init_from_quaternion(&mut self, quaternion: &Quaternion) {
        unsafe {
            ffi::cogl_matrix_init_from_quaternion(
                self.to_glib_none_mut().0,
                quaternion.to_glib_none().0,
            );
        }
    }

    /// Resets matrix to the identity matrix:
    ///
    ///
    /// ```text
    ///   .xx=1; .xy=0; .xz=0; .xw=0;
    ///   .yx=0; .yy=1; .yz=0; .yw=0;
    ///   .zx=0; .zy=0; .zz=1; .zw=0;
    ///   .wx=0; .wy=0; .wz=0; .ww=1;
    /// ```
    pub fn init_identity(&mut self) {
        unsafe {
            ffi::cogl_matrix_init_identity(self.to_glib_none_mut().0);
        }
    }

    /// Resets matrix to the (tx, ty, tz) translation matrix:
    ///
    ///
    /// ```text
    ///   .xx=1; .xy=0; .xz=0; .xw=tx;
    ///   .yx=0; .yy=1; .yz=0; .yw=ty;
    ///   .zx=0; .zy=0; .zz=1; .zw=tz;
    ///   .wx=0; .wy=0; .wz=0; .ww=1;
    /// ```
    ///
    /// ## `tx`
    /// x coordinate of the translation vector
    /// ## `ty`
    /// y coordinate of the translation vector
    /// ## `tz`
    /// z coordinate of the translation vector
    pub fn init_translation(&mut self, tx: f32, ty: f32, tz: f32) {
        unsafe {
            ffi::cogl_matrix_init_translation(self.to_glib_none_mut().0, tx, ty, tz);
        }
    }

    /// Determines if the given matrix is an identity matrix.
    ///
    /// # Returns
    ///
    /// `true` if `self` is an identity matrix else `false`
    pub fn is_identity(&self) -> Bool {
        unsafe { ffi::cogl_matrix_is_identity(self.to_glib_none().0) }
    }

    /// Applies a view transform `self` that positions the camera at
    /// the coordinate (`eye_position_x`, `eye_position_y`, `eye_position_z`)
    /// looking towards an object at the coordinate (`object_x`, `object_y`,
    /// `object_z`). The top of the camera is aligned to the given world up
    /// vector, which is normally simply (0, 1, 0) to map up to the
    /// positive direction of the y axis.
    ///
    /// Because there is a lot of missleading documentation online for
    /// gluLookAt regarding the up vector we want to try and be a bit
    /// clearer here.
    ///
    /// The up vector should simply be relative to your world coordinates
    /// and does not need to change as you move the eye and object
    /// positions. Many online sources may claim that the up vector needs
    /// to be perpendicular to the vector between the eye and object
    /// position (partly because the man page is somewhat missleading) but
    /// that is not necessary for this function.
    ///
    /// `<note>`You should never look directly along the world-up
    /// vector.`</note>`
    ///
    /// `<note>`It is assumed you are using a typical projection matrix where
    /// your origin maps to the center of your viewport.`</note>`
    ///
    /// `<note>`Almost always when you use this function it should be the first
    /// transform applied to a new modelview transform`</note>`
    /// ## `eye_position_x`
    /// The X coordinate to look from
    /// ## `eye_position_y`
    /// The Y coordinate to look from
    /// ## `eye_position_z`
    /// The Z coordinate to look from
    /// ## `object_x`
    /// The X coordinate of the object to look at
    /// ## `object_y`
    /// The Y coordinate of the object to look at
    /// ## `object_z`
    /// The Z coordinate of the object to look at
    /// ## `world_up_x`
    /// The X component of the world's up direction vector
    /// ## `world_up_y`
    /// The Y component of the world's up direction vector
    /// ## `world_up_z`
    /// The Z component of the world's up direction vector
    pub fn look_at(
        &mut self,
        eye_position_x: f32,
        eye_position_y: f32,
        eye_position_z: f32,
        object_x: f32,
        object_y: f32,
        object_z: f32,
        world_up_x: f32,
        world_up_y: f32,
        world_up_z: f32,
    ) {
        unsafe {
            ffi::cogl_matrix_look_at(
                self.to_glib_none_mut().0,
                eye_position_x,
                eye_position_y,
                eye_position_z,
                object_x,
                object_y,
                object_z,
                world_up_x,
                world_up_y,
                world_up_z,
            );
        }
    }

    /// Multiplies the two supplied matrices together and stores
    /// the resulting matrix inside `self`.
    ///
    /// `<note>`It is possible to multiply the `a` matrix in-place, so
    /// `self` can be equal to `a` but can't be equal to `b`.`</note>`
    /// ## `a`
    /// A 4x4 transformation matrix
    /// ## `b`
    /// A 4x4 transformation matrix
    pub fn multiply(&mut self, a: &Matrix, b: &Matrix) {
        unsafe {
            ffi::cogl_matrix_multiply(
                self.to_glib_none_mut().0,
                a.to_glib_none().0,
                b.to_glib_none().0,
            );
        }
    }

    /// Multiplies `self` by a parallel projection matrix.
    /// ## `x_1`
    /// The x coordinate for the first vertical clipping plane
    /// ## `y_1`
    /// The y coordinate for the first horizontal clipping plane
    /// ## `x_2`
    /// The x coordinate for the second vertical clipping plane
    /// ## `y_2`
    /// The y coordinate for the second horizontal clipping plane
    /// ## `near`
    /// The `<emphasis>`distance`</emphasis>` to the near clipping
    ///  plane (will be `<emphasis>`negative`</emphasis>` if the plane is
    ///  behind the viewer)
    /// ## `far`
    /// The `<emphasis>`distance`</emphasis>` to the far clipping
    ///  plane (will be `<emphasis>`negative`</emphasis>` if the plane is
    ///  behind the viewer)
    pub fn orthographic(&mut self, x_1: f32, y_1: f32, x_2: f32, y_2: f32, near: f32, far: f32) {
        unsafe {
            ffi::cogl_matrix_orthographic(self.to_glib_none_mut().0, x_1, y_1, x_2, y_2, near, far);
        }
    }

    /// Multiplies `self` by the described perspective matrix
    ///
    /// `<note>`You should be careful not to have to great a `z_far` / `z_near`
    /// ratio since that will reduce the effectiveness of depth testing
    /// since there wont be enough precision to identify the depth of
    /// objects near to each other.`</note>`
    /// ## `fov_y`
    /// Vertical field of view angle in degrees.
    /// ## `aspect`
    /// The (width over height) aspect ratio for display
    /// ## `z_near`
    /// The distance to the near clipping plane (Must be positive,
    ///  and must not be 0)
    /// ## `z_far`
    /// The distance to the far clipping plane (Must be positive)
    pub fn perspective(&mut self, fov_y: f32, aspect: f32, z_near: f32, z_far: f32) {
        unsafe {
            ffi::cogl_matrix_perspective(self.to_glib_none_mut().0, fov_y, aspect, z_near, z_far);
        }
    }

    //pub fn project_points(&self, n_components: i32, stride_in: usize, points_in: /*Unimplemented*/Option<Fundamental: Pointer>, stride_out: usize, points_out: /*Unimplemented*/Option<Fundamental: Pointer>, n_points: i32) {
    //    unsafe { TODO: call cogl_sys:cogl_matrix_project_points() }
    //}

    /// Multiplies `self` with a rotation matrix that applies a rotation
    /// of `angle` degrees around the specified 3D vector.
    /// ## `angle`
    /// The angle you want to rotate in degrees
    /// ## `x`
    /// X component of your rotation vector
    /// ## `y`
    /// Y component of your rotation vector
    /// ## `z`
    /// Z component of your rotation vector
    pub fn rotate(&mut self, angle: f32, x: f32, y: f32, z: f32) {
        unsafe {
            ffi::cogl_matrix_rotate(self.to_glib_none_mut().0, angle, x, y, z);
        }
    }

    /// Multiplies `self` with a rotation transformation described by the
    /// given `Euler`.
    ///
    /// ## `euler`
    /// A euler describing a rotation
    pub fn rotate_euler(&mut self, euler: &Euler) {
        unsafe {
            ffi::cogl_matrix_rotate_euler(self.to_glib_none_mut().0, euler.to_glib_none().0);
        }
    }

    /// Multiplies `self` with a rotation transformation described by the
    /// given `Quaternion`.
    ///
    /// ## `quaternion`
    /// A quaternion describing a rotation
    pub fn rotate_quaternion(&mut self, quaternion: &Quaternion) {
        unsafe {
            ffi::cogl_matrix_rotate_quaternion(
                self.to_glib_none_mut().0,
                quaternion.to_glib_none().0,
            );
        }
    }

    /// Multiplies `self` with a transform matrix that scales along the X,
    /// Y and Z axis.
    /// ## `sx`
    /// The X scale factor
    /// ## `sy`
    /// The Y scale factor
    /// ## `sz`
    /// The Z scale factor
    pub fn scale(&mut self, sx: f32, sy: f32, sz: f32) {
        unsafe {
            ffi::cogl_matrix_scale(self.to_glib_none_mut().0, sx, sy, sz);
        }
    }

    /// Transforms a point whos position is given and returned as four float
    /// components.
    /// ## `x`
    /// The X component of your points position
    /// ## `y`
    /// The Y component of your points position
    /// ## `z`
    /// The Z component of your points position
    /// ## `w`
    /// The W component of your points position
    pub fn transform_point(&self, x: &mut f32, y: &mut f32, z: &mut f32, w: &mut f32) {
        unsafe {
            ffi::cogl_matrix_transform_point(self.to_glib_none().0, x, y, z, w);
        }
    }

    //pub fn transform_points(&self, n_components: i32, stride_in: usize, points_in: /*Unimplemented*/Option<Fundamental: Pointer>, stride_out: usize, points_out: /*Unimplemented*/Option<Fundamental: Pointer>, n_points: i32) {
    //    unsafe { TODO: call cogl_sys:cogl_matrix_transform_points() }
    //}

    /// Multiplies `self` with a transform matrix that translates along
    /// the X, Y and Z axis.
    /// ## `x`
    /// The X translation you want to apply
    /// ## `y`
    /// The Y translation you want to apply
    /// ## `z`
    /// The Z translation you want to apply
    pub fn translate(&mut self, x: f32, y: f32, z: f32) {
        unsafe {
            ffi::cogl_matrix_translate(self.to_glib_none_mut().0, x, y, z);
        }
    }

    /// Replaces `self` with its transpose. Ie, every element (i,j) in the
    /// new matrix is taken from element (j,i) in the old matrix.
    pub fn transpose(&mut self) {
        unsafe {
            ffi::cogl_matrix_transpose(self.to_glib_none_mut().0);
        }
    }

    /// Multiplies `self` by a view transform that maps the 2D coordinates
    /// (0,0) top left and (`width_2d`,`height_2d`) bottom right the full viewport
    /// size. Geometry at a depth of 0 will now lie on this 2D plane.
    ///
    /// Note: this doesn't multiply the matrix by any projection matrix,
    /// but it assumes you have a perspective projection as defined by
    /// passing the corresponding arguments to `Matrix::frustum`.
    ///
    /// Toolkits such as Clutter that mix 2D and 3D drawing can use this to
    /// create a 2D coordinate system within a 3D perspective projected
    /// view frustum.
    /// ## `left`
    /// coord of left vertical clipping plane
    /// ## `right`
    /// coord of right vertical clipping plane
    /// ## `bottom`
    /// coord of bottom horizontal clipping plane
    /// ## `top`
    /// coord of top horizontal clipping plane
    /// ## `z_near`
    /// The distance to the near clip plane. Never pass 0 and always pass
    ///  a positive number.
    /// ## `z_2d`
    /// The distance to the 2D plane. (Should always be positive and
    ///  be between `z_near` and the z_far value that was passed to
    ///  `Matrix::frustum`)
    /// ## `width_2d`
    /// The width of the 2D coordinate system
    /// ## `height_2d`
    /// The height of the 2D coordinate system
    pub fn view_2d_in_frustum(
        &mut self,
        left: f32,
        right: f32,
        bottom: f32,
        top: f32,
        z_near: f32,
        z_2d: f32,
        width_2d: f32,
        height_2d: f32,
    ) {
        unsafe {
            ffi::cogl_matrix_view_2d_in_frustum(
                self.to_glib_none_mut().0,
                left,
                right,
                bottom,
                top,
                z_near,
                z_2d,
                width_2d,
                height_2d,
            );
        }
    }

    /// Multiplies `self` by a view transform that maps the 2D coordinates
    /// (0,0) top left and (`width_2d`,`height_2d`) bottom right the full viewport
    /// size. Geometry at a depth of 0 will now lie on this 2D plane.
    ///
    /// Note: this doesn't multiply the matrix by any projection matrix,
    /// but it assumes you have a perspective projection as defined by
    /// passing the corresponding arguments to `Matrix::perspective`.
    ///
    /// Toolkits such as Clutter that mix 2D and 3D drawing can use this to
    /// create a 2D coordinate system within a 3D perspective projected
    /// view frustum.
    /// ## `fov_y`
    /// A field of view angle for the Y axis
    /// ## `aspect`
    /// The ratio of width to height determining the field of view angle
    ///  for the x axis.
    /// ## `z_near`
    /// The distance to the near clip plane. Never pass 0 and always pass
    ///  a positive number.
    /// ## `z_2d`
    /// The distance to the 2D plane. (Should always be positive and
    ///  be between `z_near` and the z_far value that was passed to
    ///  `Matrix::frustum`)
    /// ## `width_2d`
    /// The width of the 2D coordinate system
    /// ## `height_2d`
    /// The height of the 2D coordinate system
    pub fn view_2d_in_perspective(
        &mut self,
        fov_y: f32,
        aspect: f32,
        z_near: f32,
        z_2d: f32,
        width_2d: f32,
        height_2d: f32,
    ) {
        unsafe {
            ffi::cogl_matrix_view_2d_in_perspective(
                self.to_glib_none_mut().0,
                fov_y,
                aspect,
                z_near,
                z_2d,
                width_2d,
                height_2d,
            );
        }
    }

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

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

// impl Eq for Matrix {}
