use crate::toolbox::geometry::cart_to_sph;
use crate::{Matrix10xX, MatrixSlice10x1, MatrixSliceMut10x1, M3, 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;

/// Wavefront file's vertices are often written with 6 decimal digits. Plus, [`obj`] extract these
/// coordinates as f32 but f64 is more convenient to use. Thus, vertices' positions will be
/// converted to f64, but due to float error precision conversion, it will be rounded to the number
/// of significant figures.
const WAVEFRONT_PRECISION: f64 = 1e6;

/// 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;
///
/// let object = Object3D::new("rsc/obj/dimorphos.obj");
/// ```
pub struct Object3D {
    /// 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>,
    /// Invert normals.
    _invert_normals: bool,
}

impl Object3D {
    /// 3D object constructor. Reads a 3D object file and compute attributes for the faces.
    pub fn new<P: AsRef<Path>>(path: P) -> 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 * WAVEFRONT_PRECISION).round() / WAVEFRONT_PRECISION
            })));
        }
        // Get faces
        let _faces = V3X::from_iterator(
            number_faces,
            dome.indices.iter().cloned().map(|x| x as usize),
        );
        // Build Object3D
        let mut object_3d = Self {
            _table: Matrix10xX::from_element(number_faces, f64::NAN),
            _vertices,
            _faces,
            _faces_mask: VX::from_element(number_faces, true),
            _invert_normals: false,
        };
        object_3d.recompute_faces();
        object_3d
    }

    /// Create an empty 3D object.
    pub fn empty() -> Self {
        // Build Object3D
        Self {
            _table: Matrix10xX::from_element(0, f64::NAN),
            _vertices: V3X::from_element(0, f64::NAN),
            _faces: V3X::from_element(0, 0),
            _faces_mask: VX::from_element(0, true),
            _invert_normals: false,
        }
    }

    /// Add a vertex to the 3D object.
    pub fn add_vertex(&mut self, vertex: &V3<f64>) {
        let number_columns = self._vertices.ncols();
        self._vertices = self._vertices.clone().insert_column(number_columns, 0.);
        self._vertices.set_column(number_columns, vertex);
    }

    /// Add a face to the 3D object.
    pub fn add_face(&mut self, face: &V3<usize>) {
        let number_faces = self.number_faces_raw();
        self._faces = self._faces.clone().insert_column(number_faces, 0);
        self._faces.set_column(number_faces, face);
    }

    /// Add a face to the 3D object.
    pub fn new_face(&mut self, vertices: &M3<f64>) {
        let numer_vertices = self._vertices.ncols();
        self.add_vertex(&vertices.column(0).into_owned());
        self.add_vertex(&vertices.column(1).into_owned());
        self.add_vertex(&vertices.column(2).into_owned());
        self.add_face(&V3::new(
            numer_vertices,
            numer_vertices + 1,
            numer_vertices + 2,
        ));
    }

    /// 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);
        self.recompute_faces();
    }

    /// To invert the normals.
    pub fn invert_normals(&mut self) {
        self._invert_normals = !self._invert_normals;
        self.compute_normals();
    }

    /// In case the position of vertices, the indices of faces or the mask are updated, recompute
    /// the table data. If the size of the mask does not match the size of the number of faces, it
    /// reset the mask.
    pub fn recompute_faces(&mut self) {
        if self._faces_mask.nrows() != self.number_faces_raw() {
            self._faces_mask = VX::from_element(self.number_faces_raw(), true);
        }
        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)| {
                (
                    V3::from_iterator(
                        (self.vertices().column(face[2]) - self.vertices().column(face[0]))
                            .iter()
                            .map(|e| (e * WAVEFRONT_PRECISION).round() / WAVEFRONT_PRECISION),
                    ),
                    V3::from_iterator(
                        (self.vertices().column(face[1]) - self.vertices().column(face[0]))
                            .iter()
                            .map(|e| (e * WAVEFRONT_PRECISION).round() / WAVEFRONT_PRECISION),
                    ),
                )
            })
    }

    /// 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(&V3::from_iterator(
                ((vertex.0 + vertex.1 + vertex.2) / 3.)
                    .iter()
                    .map(|e| (e * WAVEFRONT_PRECISION).round() / WAVEFRONT_PRECISION),
            ));
        }
        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(&V3::from_iterator(
                (-edge.0.cross(&edge.1) / edge.1.cross(&edge.0).norm())
                    .iter()
                    .map(|e| (e * WAVEFRONT_PRECISION).round() / WAVEFRONT_PRECISION),
            ));
        }
        if self._invert_normals {
            normals = -normals;
        }
        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().into_owned());
        self.sphericals_mut().copy_from(&V3X::from_iterator(
            centers.ncols(),
            centers
                .iter()
                .map(|e| (e * WAVEFRONT_PRECISION).round() / WAVEFRONT_PRECISION),
        ));
    }

    /// 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()
                * WAVEFRONT_PRECISION)
                .round()
                / WAVEFRONT_PRECISION;
        }
        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()
    }

    /// 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()
    }

    /// 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;
        self.recompute_faces();
    }

    /// Mask one face.
    pub fn mask_face(&mut self, face: usize) {
        self._faces_mask[face] = false;
        self.recompute_faces();
    }

    /// Mask all faces except this one.
    pub fn mask_all_faces_except_this(&mut self, face: usize) {
        self._faces_mask.fill(false);
        self._faces_mask[face] = true;
        self.recompute_faces();
    }

    /// 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),
        )
    }

    /// Faces' mask that match the equator (latitude = 0°).
    /// By default, it will look up for the smallest latitude and consider it as the threshold for
    /// the equator. Otherwise, you can specify yourself the threshold.
    pub fn set_equator_mask(&mut self, mut equator_threshold: Option<f64>) {
        if equator_threshold.is_none() {
            equator_threshold = Some(self.sphericals().row(1).amin());
        }
        self.set_faces_mask(VX::from_iterator(
            self.number_faces_raw(),
            self.sphericals()
                .row(1)
                .abs()
                .iter()
                .map(|&latitude| latitude <= equator_threshold.unwrap()),
        ));
        self.recompute_faces();
    }

    /// Faces' mask that match the prime meridian (longitude = 0°).
    /// By default, it will look up for the smallest longitude and consider it as the threshold for
    /// the prime meridian. Otherwise, you can specify yourself the threshold.
    pub fn set_prime_meridian_mask(&mut self, mut longitude_threshold: Option<f64>) {
        if longitude_threshold.is_none() {
            longitude_threshold = Some(self.sphericals().row(0).amin());
        }
        self.set_faces_mask(VX::from_iterator(
            self.number_faces_raw(),
            self.sphericals()
                .row(0)
                .abs()
                .iter()
                .map(|&longitude| longitude <= longitude_threshold.unwrap()),
        ));
        self.recompute_faces();
    }

    /// Faces' mask that match the point at longitude = latitude = 0°.
    /// TODO
    pub fn set_longitude_latitude_zero(&mut self) {
        let indice_longitude = VX::from_iterator(
            self.number_faces(),
            self.sphericals().row(0).iter().cloned(),
        )
        .abs()
        .argmin();
        let indice_latitude = VX::from_iterator(
            self.number_faces(),
            self.sphericals().row(1).iter().cloned(),
        )
        .abs()
        .argmin();
        println!("{:?}, {:?}", indice_longitude, indice_latitude);
        self.recompute_faces();
    }
}
