Documentation
use super::*;
use std::collections::HashMap;
use std::mem::ManuallyDrop;

/// `MeshStyle` component.
///
/// Usually accessed through `Entity::mesh_style`().

pub struct MeshStyle {
    id: Entity,
}

/// Represents morph target weights attached to the `MeshStyle`.
pub struct MorphTargetData {
    data: WorldData,
}

impl MorphTargetData {
    /// Creates `MorphTargetData` from a `HashMap`.
    pub fn new(weights_map: HashMap<String, f32>) -> Self {
        let names: Vec<(u32, u32)> = weights_map
            .keys()
            .map(|name| (name.as_ptr() as u32, name.len() as u32))
            .collect();
        let weights: Vec<f32> = weights_map.values().cloned().collect();

        let desc = ffi::v3::MorphTargetDesc {
            num_weights: weights.len() as u32,
            weights_ptr: weights.as_ptr() as u32,
            names_ptr: names.as_ptr() as u32,
        };
        Self {
            data: WorldData::create_struct(ffi::CreateDataType::MorphTargetData, &desc),
        }
    }

    /// Sets a debug name of this data object. Useful for debugging memory usage and leaks.
    pub fn set_debug_name(&self, name: &str) {
        self.data.set_debug_name(name);
    }
}

impl ValueConverterTrait<MorphTargetData> for ValueConverter {
    fn into_value(v: MorphTargetData) -> Value {
        <Self as ValueConverterTrait<WorldData>>::into_value(v.data)
    }
    fn from_value(v: &Value) -> MorphTargetData {
        MorphTargetData {
            data: <Self as ValueConverterTrait<WorldData>>::from_value(v),
        }
    }
}

/// Represents a vector of `WorldMaterial`'s attached to the `MeshStyle`.
pub struct WorldMaterials {
    data: WorldData,
}

impl WorldMaterials {
    /// Creates `WorldMaterials` from a `Vec`.
    pub fn new(materials: Vec<WorldMaterial>) -> Self {
        // Would be nice to prevent the extra clone here if possible.
        let materials: Vec<ManuallyDrop<WorldMaterial>> =
            materials.into_iter().map(ManuallyDrop::new).collect();

        let desc = ffi::v3::WorldMaterialsDesc {
            materials_len: materials.len() as u32,
            materials_ptr: materials.as_ptr() as u32,
        };
        Self {
            data: WorldData::create_struct(ffi::CreateDataType::WorldMaterials, &desc),
        }
    }

    /// Sets a debug name of this data object. Useful for debugging memory usage and leaks.
    pub fn set_debug_name(&self, name: &str) {
        self.data.set_debug_name(name);
    }
}

impl ValueConverterTrait<WorldMaterials> for ValueConverter {
    fn into_value(v: WorldMaterials) -> Value {
        <Self as ValueConverterTrait<WorldData>>::into_value(v.data)
    }
    fn from_value(v: &Value) -> WorldMaterials {
        WorldMaterials {
            data: <Self as ValueConverterTrait<WorldData>>::from_value(v),
        }
    }
}

impl std::fmt::Debug for MeshStyle {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("MeshStyle")
            .field("entity", &self.id.name())
            .field("flags", &self.flags().get())
            .field("diffuse_tint", &self.diffuse_tint().get())
            .finish()
    }
}

impl MeshStyle {
    /// Creates a `MeshStyle` entity.
    ///
    /// * `name` - Just a name, to keep track of the entity during development.
    pub fn create(name: &str) -> Self {
        let id = World::create_entity(name, ffi::EntityTemplate::MeshStyle);
        Self { id }
    }

    /// Sets the mesh style parameters to match this mesh style.
    pub fn set_mesh_style(&self, mesh_style: &MeshStyleData) {
        self.diffuse_tint().set(mesh_style.diffuse_tint);
        self.flags().set(mesh_style.flags);
    }

    impl_world_accessor!(
        /// Returns a `ValueAccessor` for the `world::MeshStyleFlags` of the entity.
        ///
        /// Used to set/get the flags.
        MeshStyle,
        Flags,
        MeshStyleFlags,
        flags,
        ValueAccessorReadWrite
    );

    impl_world_accessor!(
        /// Returns a `ValueAccessor` for the diffuse tint of the entity.
        ///
        /// Used to set/get/animate the diffuse tint.
        MeshStyle,
        DiffuseTint,
        Vec4,
        diffuse_tint,
        ValueAccessorReadWriteAnimate
    );

    impl_world_accessor!(
        /// Returns a `ValueAccessor` for the morph target weights of the entity.
        ///
        /// Used to set/get/animate weights.
        /// There's a limit to the number of morph targets weights that can be non-zero at once
        /// and still affect the mesh. Currently that limit is 4, but it will soon be changed higher.
        MeshStyle,
        MorphTargetWeights,
        MorphTargetData,
        morph_target_weights,
        ValueAccessorDataReadWrite
    );

    impl_world_accessor!(
        /// Returns a `ValueAccessor` for the material overrides of the entity.
        ///
        /// Used to set/get per-instance materials that override the materials of a mesh.
        MeshStyle,
        MaterialOverrides,
        WorldMaterials,
        materials,
        ValueAccessorDataReadWrite
    );

    impl_world_accessor!(
        /// Sets a view-space offset vector for billboarded meshes (if flags contains BILLBOARD).
        ///
        /// Lets you compose multiple billboards together at the same worldspace location in "2D".
        /// Useful for things like speech bubbles.
        MeshStyle,
        BillboardOffset,
        Vec3,
        billboard_offset,
        ValueAccessorReadWriteAnimate
    );

    impl_world_accessor!(
        /// Sets the thickness of the outline displayed if flags contains OUTLINE.
        ///
        /// The thickness is specified in in screen space in sort of virtual pixels, of which there are
        /// 1024 across the screen. This is so results don't feel drastically different between an 1024x768
        /// and a 4k screen. A 2px border is generally pretty appropriate as a thin border, and a 5px is a
        /// nice thick one.
        MeshStyle,
        OutlineThickness,
        f32,
        outline_thickness,
        ValueAccessorReadWriteAnimate
    );

    impl_world_accessor!(
        /// Sets the color of the outline displayed if flags contains OUTLINE.
        MeshStyle,
        OutlineColor,
        Vec4,
        outline_color,
        ValueAccessorReadWriteAnimate
    );

    impl_world_accessor!(
        /// Returns a `ValueAccessor` for the `world::MeshVisibilityFlags` of the entity.
        ///
        /// Used to set/get the flags.
        MeshStyle,
        MeshVisibility,
        MeshVisibilityFlags,
        visibility_flags,
        ValueAccessorReadWrite
    );

    impl_world_accessor!(
        /// Sets the visibility flags used for the player id set which overwrites [`Self::visibility_flags`].
        ///
        /// Used to set/get the visibility flag.
        MeshStyle,
        MeshVisibilityPlayerIdSetFlags,
        MeshVisibilityFlags,
        visibility_player_id_set_flags,
        ValueAccessorReadWrite
    );

    impl_world_accessor!(
        /// Returns a `ValueAccessor` for the set of player ids the `visibility_player_id_set_flags` value should affect.
        ///
        /// Used to set/get the player id set.
        MeshStyle,
        MeshVisibilityPlayerIdSet,
        PlayerIdSet,
        visibility_player_id_set,
        ValueAccessorDataReadWrite
    );

    impl_world_accessor!(
        /// Sets the mesh style flags used for the player id set which overwrites `flags`.
        ///
        /// Used to set/get the mesh style flag.
        MeshStyle,
        MeshStylePlayerIdSetFlags,
        MeshStyleFlags,
        mesh_style_player_id_set_flags,
        ValueAccessorReadWrite
    );

    impl_world_accessor!(
        /// The players which we will use [`Self::mesh_style_player_id_set_flags`] for instead of `[Self::flags`].
        ///
        /// Default: the empty set (use `[Self::flags`] for everyone).
        ///
        /// Used to set/get the player id set.
        MeshStyle,
        MeshStylePlayerIdSet,
        PlayerIdSet,
        mesh_style_player_id_set,
        ValueAccessorDataReadWrite
    );
}

impl_world_component!(MeshStyle);