use crate::toolbox::geometry::cart_to_sph;
use crate::{Matrix10xX, MatrixSlice10x1, MatrixSliceMut10x1, V3, V3X, VX};
use na::{
    MatrixSlice1xX, MatrixSlice3x1, MatrixSlice3xX, MatrixSliceMut1xX, MatrixSliceMut3xX, U1, U10,
    U3,
};

use obj::{load_obj, Obj, Position};
use std::fs::File;
use std::io::BufReader;
use std::path::Path;

/*
/// A matrix of spherical coordinates.
///
/// # Definition
///
/// Derives from [`V3X`] to define explicit methods related to spherical coordinates.
///
/// # Example
///
/// ```
/// use kalast::{V3X, cart_to_sph, Spherical, TAU};
///
/// let vector_cartesian = V3X::from_column_slice(&[1., 0., 0., 0., 1., 0.]);
/// let vector_spherical: Spherical<f64> = cart_to_sph(&vector_cartesian);
///
/// assert_eq!(vector_spherical.0, Spherical::from_column_slice(&[0., 0., 1., TAU / 4.,
/// 0., 1.]).0)
/// ```
pub struct Spherical<T: RealField>(pub V3X<T>);

impl<T> Spherical<T>
where
    T: RealField,
{
    /// Constructor that initializes all elements to zeros for a matrix of `size` columns.
    pub fn zeros(size: usize) -> Self {
        Self(V3X::<T>::zeros(size))
    }

    /// Constructor from a column-major array.
    pub fn from_column_slice(data: &[T]) -> Self {
        Self(V3X::<T>::from_column_slice(data))
    }

    /// Get the longitudes.
    pub fn longitudes(&self) -> MatrixSlice1xX<T, U1, U3> {
        self.0.row(0)
    }

    /// Get the latitudes.
    pub fn latitudes(&self) -> MatrixSlice1xX<T, U1, U3> {
        self.0.row(1)
    }

    /// Get the radiuses.
    pub fn radiuses(&self) -> VX<T> {
        self.0.row(2).transpose()
    }

    /// Get the longitudes as mutable.
    pub fn longitudes_mut(&mut self) -> MatrixSliceMut1xX<T, U1, U3> {
        self.0.row_mut(0)
    }

    /// Get the latitudes as mutable.
    pub fn latitudes_mut(&mut self) -> MatrixSliceMut1xX<T, U1, U3> {
        self.0.row_mut(1)
    }

    /// Get the radiuses as mutable.
    pub fn radiuses_mut(&mut self) -> MatrixSliceMut1xX<T, U1, U3> {
        self.0.row_mut(2)
    }

    /// Get the vector at the given given indice.
    pub fn vector(&self, indice: usize) -> MatrixSlice3x1<T> {
        self.0.column(indice)
    }

    /// Get the mutable vector at the given given indice.
    pub fn vector_mut(&mut self, indice: usize) -> MatrixSliceMut3x1<T> {
        self.0.column_mut(indice)
    }

    /// Get an iterator over vectors.
    pub fn vector_iter(&self) -> impl Iterator<Item = MatrixSlice3x1<T>> {
        self.0.column_iter()
    }

    /// Get a mutable iterator over vectors.
    pub fn vector_iter_mut(&mut self) -> impl Iterator<Item = MatrixSliceMut3x1<T>> {
        self.0.column_iter_mut()
    }

    /// Get an iterator over the elements
    pub fn iter(&self) -> impl Iterator<Item = &T> {
        self.0.iter()
    }
}

pub struct SphericalSlice<'a, T: RealField>(pub DMatrixSlice<'a, T, U1, U3>);

impl<'a, T> SphericalSlice<'a, T>
where
    T: RealField,
{
    /// Constructor that takes the slice of a [`Spherical`].
    pub fn from_spherical(spherical: &'a Spherical<T>) -> Self {
        Self(spherical.0.slice((0, 0), spherical.0.shape()))
    }
}
*/

/// A shape model representation for 3D object files.
///
/// # Definition
///
/// Compute attributes relative of the shape model of a 3D object file. Faces are assumed
/// triangular. The file must contain positions of vertices and indices of vertices that form a
/// face.
///
/// Calculate for each face:
///
/// + the position of the center point (in cartesian and spherical)
/// + the outward normal vector
/// + the area
///
/// # Example
///
/// Example of the creation of a 3D object.
///
/// ```
/// use kalast::Object3D;
/// use std::path::Path;
///
/// let object = Object3D::new(Path::new("rsc/obj/dimorphos.obj"));
/// ```
pub struct Object3D {
    /// Path to the 3D object file.
    pub path: &'static Path,
    /// Table data as a matrix of 10 rows by X columns (rows are attributes of faces, columns are
    /// faces).
    ///
    /// Indices of rows:
    ///
    /// +  0, 1, 2: centers' x, y and z axis
    /// +  3, 4, 5: normals' x, y and z axis
    /// +  6, 7, 8: sphericals' x, y and z axis
    /// +  9: areas
    _table: Matrix10xX<f64>,
    /// Vertices of the 3D object, expected triplet for triangular faces.
    _vertices: V3X<f64>,
    /// Indices of the triplet of vertices to form the faces of the 3D object.
    _faces: V3X<usize>,
    /// A mask that hides and shows only some faces: face hidden = false.
    _faces_mask: VX<bool>,
}

impl Object3D {
    /// 3D object constructor. Reads a 3D object file and compute attributes for the faces.
    pub fn new(path: &'static Path) -> Self {
        // Read obj file
        let input = BufReader::new(File::open(path).unwrap());
        let dome: Obj<Position> = load_obj(input).unwrap();
        let number_vertices: usize = dome.vertices.len();
        let number_faces: usize = dome.indices.len() / 3;
        // Get vertices
        let mut _vertices = V3X::zeros(number_vertices);
        for (mut col, position) in izip!(
            _vertices.column_iter_mut(),
            dome.vertices.iter().map(|x| x.position),
        ) {
            col.copy_from(&V3::from_iterator(position.iter().map(|&e| e as f64)));
        }
        // Get faces
        let _faces = V3X::from_iterator(
            number_faces,
            dome.indices.iter().cloned().map(|x| x as usize),
        );
        // Build Object3D
        Self {
            path,
            _table: Matrix10xX::from_element(number_faces, f64::NAN),
            _vertices,
            _faces,
            _faces_mask: VX::from_element(number_faces, true),
        }
    }

    /// Get the number of faces of the 3D object without the mask.
    pub fn number_faces_raw(&self) -> usize {
        self._faces.ncols()
    }

    /// Get the number of faces of the 3D object with the mask applied.
    pub fn number_faces(&self) -> usize {
        self._faces_mask.iter().filter(|&&face| face).count()
    }

    /// Apply a multiplication factor to vertices positions.
    pub fn apply_factor(&mut self, factor: f64) {
        self.set_vertices(self.vertices().clone() * factor);
    }

    /// In case the position of vertices, the indices of faces or the mask is updated, recompute
    /// the table data.
    pub fn recompute_faces(&mut self) {
        self._table = Matrix10xX::from_element(self.number_faces(), f64::NAN);
        self.compute_centers();
        self.compute_normals();
        self.compute_sphericals();
        self.compute_areas();
    }

    /// Vertices getter.
    pub fn vertices(&self) -> &V3X<f64> {
        &self._vertices
    }

    /// Vertices setter. Also recompute the table data.
    pub fn set_vertices(&mut self, vertices: V3X<f64>) {
        self._vertices = vertices;
        self.recompute_faces();
    }

    /// Faces getter.
    pub fn faces(&self) -> &V3X<usize> {
        &self._faces
    }

    /// Faces setter. Also recompute the table data.
    pub fn set_faces(&mut self, faces: V3X<usize>) {
        self._faces = faces;
        self.recompute_faces();
    }

    /// Iterators over the three vertices of each face.
    pub fn vertices_iter(
        &self,
    ) -> impl Iterator<
        Item = (
            MatrixSlice3x1<f64>,
            MatrixSlice3x1<f64>,
            MatrixSlice3x1<f64>,
        ),
    > {
        self.faces()
            .column_iter()
            .enumerate()
            .filter(move |&(i, _)| self._faces_mask[i])
            .map(move |(_, face)| {
                (
                    self.vertices().column(face[0]),
                    self.vertices().column(face[1]),
                    self.vertices().column(face[2]),
                )
            })
    }

    /// Iterators over the two first edges of each face.
    pub fn edges_iter(&self) -> impl Iterator<Item = (V3<f64>, V3<f64>)> + '_ {
        self.faces()
            .column_iter()
            .enumerate()
            .filter(move |&(i, _)| self._faces_mask[i])
            .map(move |(_, face)| {
                (
                    self.vertices().column(face[2]) - self.vertices().column(face[0]),
                    self.vertices().column(face[1]) - self.vertices().column(face[0]),
                )
            })
    }

    /// Centers getter.
    pub fn centers(&self) -> MatrixSlice3xX<f64, U1, U10> {
        self._table.fixed_rows::<U3>(0)
    }

    /// Centers getter as mutable.
    pub fn centers_mut(&mut self) -> MatrixSliceMut3xX<f64, U1, U10> {
        self._table.fixed_rows_mut::<U3>(0)
    }

    /// Compute centers from vertices and faces.
    pub fn compute_centers(&mut self) {
        let mut centers = V3X::zeros(self.number_faces());
        for (vertex, mut center) in izip!(self.vertices_iter(), centers.column_iter_mut()) {
            center.copy_from(&((vertex.0 + vertex.1 + vertex.2) / 3.));
        }
        self.centers_mut().copy_from(&centers);
    }

    /// Normals getter.
    pub fn normals(&self) -> MatrixSlice3xX<f64, U1, U10> {
        self._table.fixed_rows::<U3>(3)
    }

    /// Normals getter as mutable.
    pub fn normals_mut(&mut self) -> MatrixSliceMut3xX<f64, U1, U10> {
        self._table.fixed_rows_mut::<U3>(3)
    }

    /// Compute normals from vertices and faces.
    pub fn compute_normals(&mut self) {
        let mut normals = V3X::zeros(self.number_faces());
        for (edge, mut normal) in izip!(self.edges_iter(), normals.column_iter_mut()) {
            normal.copy_from(&(-edge.0.cross(&edge.1) / edge.1.cross(&edge.0).norm()));
        }
        self.normals_mut().copy_from(&normals);
    }

    /// Sphericals getter.
    pub fn sphericals(&self) -> MatrixSlice3xX<f64, U1, U10> {
        self._table.fixed_rows::<U3>(6)
    }

    /// Sphericals getter as mutable.
    pub fn sphericals_mut(&mut self) -> MatrixSliceMut3xX<f64, U1, U10> {
        self._table.fixed_rows_mut::<U3>(6)
    }

    /// Compute sphericals from centers.
    pub fn compute_sphericals(&mut self) {
        let centers = cart_to_sph(&self.centers());
        self.sphericals_mut().copy_from(&centers);
    }

    /// Areas getter.
    pub fn areas(&self) -> MatrixSlice1xX<f64, U1, U10> {
        self._table.fixed_rows::<U1>(9)
    }

    /// Areas getter as mutable.
    pub fn areas_mut(&mut self) -> MatrixSliceMut1xX<f64, U1, U10> {
        self._table.fixed_rows_mut::<U1>(9)
    }

    /// Compute areas from vertices and faces.
    pub fn compute_areas(&mut self) {
        let mut areas = VX::zeros(self.number_faces());
        for (edge, area) in izip!(self.edges_iter(), areas.iter_mut()) {
            *area = 0.5 * edge.0.angle(&edge.1).sin() * edge.1.norm() * edge.0.norm();
        }
        self.areas_mut().tr_copy_from(&areas);
    }

    /// Iterate over table data face by face yielding all attributes of faces.
    pub fn face_iter_all(
        &self,
    ) -> impl Iterator<Item = MatrixSlice10x1<f64, U1, U10>> + '_ + Clone {
        self._table.column_iter().map(|x| x)
    }

    /// Iterate over table data face by face yielding all attributes of faces as mutable.
    pub fn face_iter_mut_all(
        &mut self,
    ) -> impl Iterator<Item = MatrixSliceMut10x1<f64, U1, U10>> + '_ {
        self._table.column_iter_mut().map(|x| x)
    }

    /// Faces' mask getter.
    pub fn faces_mask(&self) -> &VX<bool> {
        &self._faces_mask
    }

    /// Faces' mask setter.
    pub fn set_faces_mask(&mut self, faces_mask: VX<bool>) {
        self._faces_mask = faces_mask;
    }

    /// Faces' mask getter with true indices only.
    pub fn faces_mask_true_indices(&self) -> VX<usize> {
        VX::from_iterator(
            self.number_faces(),
            self._faces_mask
                .iter()
                .enumerate()
                .filter(|&(_, face)| *face)
                .map(|(indice, _)| indice),
        )
    }
}
