use utilities::prelude::cgmath;
use utilities::prelude::cgmath::prelude::{One, SquareMatrix};

use anyhow::Result;

use gltf;

use std::collections::HashMap;
use std::{cmp::Ordering, sync::Mutex};

#[derive(Debug)]
pub struct Skin {
    pub index: usize,
    pub root_joint_id: usize,

    inverse_global_transform: Mutex<cgmath::Matrix4<f32>>,
    inverse_bind_matrices: HashMap<usize, cgmath::Matrix4<f32>>,
    joints: Vec<usize>,
}

impl Skin {
    pub fn new(
        gltf_skin: &gltf::Skin<'_>,
        node_index: usize,
        buffers: &Vec<gltf::buffer::Data>,
    ) -> Result<Skin> {
        let reader = gltf_skin.reader(|buffer| Some(&buffers[buffer.index()]));

        let ibms: Vec<cgmath::Matrix4<f32>> = match reader.read_inverse_bind_matrices() {
            Some(ibms) => ibms.map(|ibm| ibm.into()).collect(),
            None => {
                return Err(anyhow::Error::msg(
                    "Gltf: Skin does not contain a inverse bind matrix",
                ))
            }
        };

        let mut ibm_map = HashMap::new();
        let mut nodes = Vec::new();

        for (index, node) in gltf_skin.joints().enumerate() {
            ibm_map.insert(node.index(), ibms[index]);
            nodes.push(node.index());
        }

        let root_joint_id = match gltf_skin.skeleton() {
            Some(node) => node.index(),
            None => node_index,
        };

        Ok(Skin {
            index: gltf_skin.index(),
            root_joint_id,

            inverse_global_transform: Mutex::new(cgmath::Matrix4::one()),
            inverse_bind_matrices: ibm_map,
            joints: nodes,
        })
    }

    pub fn ibm(&self, node_id: usize) -> Option<&cgmath::Matrix4<f32>> {
        self.inverse_bind_matrices.get(&node_id)
    }

    pub fn joints(&self) -> &Vec<usize> {
        &self.joints
    }

    pub fn set_global_transform(&self, global_transform: cgmath::Matrix4<f32>) -> Result<()> {
        if let Some(inverse) = global_transform.invert() {
            *self.inverse_global_transform.lock().map_err(|_| {
                anyhow::Error::msg("Gltf: Failed locking inverse_global_transform of Skin")
            })? = inverse;
        }

        Ok(())
    }

    pub fn inverse_global_transform(&self) -> Result<cgmath::Matrix4<f32>> {
        Ok(self
            .inverse_global_transform
            .lock()
            .map_err(|_| {
                anyhow::Error::msg("Gltf: Failed locking inverse_global_transform of Skin")
            })?
            .clone())
    }
}

impl Ord for Skin {
    fn cmp(&self, other: &Self) -> Ordering {
        self.index.cmp(&other.index)
    }
}

impl PartialOrd for Skin {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl PartialEq for Skin {
    fn eq(&self, other: &Self) -> bool {
        self.index == other.index
    }
}

impl Eq for Skin {}