Documentation
use super::*;

/// Represents formatted text that can be attached to a Render component.
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub struct FormattedText {
    pub(crate) data: WorldData,
}

impl FormattedText {
    /// Creates the `FormattedText` from `data`.
    ///
    /// data contains a formatted string, formatted using a custom markup language documented
    /// elsewhere. If the string is empty, nothing will be drawn. Only characters supported
    /// by Ark's current hardcoded fonts will work, others may be replaced with little squares
    /// or similar. Multiline is OK, \n will be treated as a linebreak (\r is ignored). A native
    /// \n character will also be considered a line break.
    ///
    /// TODO: Replace this constructor with one that takes a high level representation and
    /// encodes the string, similar to the one in ark-modules.
    ///
    /// # Panics
    ///
    /// If there are mistakes in the formatting, it will panic. This is intentional
    /// currently since we only intend the markup to be generated from a higher level source,
    /// so panics will catch bugs in that conversion but should otherwise never happen.
    pub fn new(data: &str) -> Self {
        Self {
            data: WorldData::create_formatted_text(data),
        }
    }
}

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

/// Represents raw data that can be attached to a Render component to be rendered by a
/// client-side render module.
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub struct RenderModuleData {
    pub(crate) data: WorldData,
}

impl RenderModuleData {
    /// Creates the `RenderModuleData` from `data`.
    ///
    /// `module_dependency_name` is the module name, same as specified in module-dependencies in the Cargo.toml.
    pub fn new(module_dependency_name: &'static str, data: &[u8]) -> Self {
        Self {
            data: WorldData::create_render_module_data(module_dependency_name, data),
        }
    }

    /// Creates the `RenderModuleData` from `data` in a unsafe manner.
    ///
    /// `module_dependency_name` is the module name, same as specified in module-dependencies in the Cargo.toml.
    /// Takes a non-'static str as parameter.
    /// [`RenderModuleData::new`] should be preferred over this.
    pub fn new_dynamic(module_dependency_name: &str, data: &[u8]) -> Self {
        Self {
            data: WorldData::create_render_module_data(module_dependency_name, data),
        }
    }
}

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

#[allow(missing_docs)]
pub enum Shape {
    Mesh(WorldMesh),
    SdfProgram(SdfProgram),
    SdfSkin(SdfSkin),
    Text(FormattedText),
    RenderModule(RenderModuleData),
    None,
}

impl Shape {
    /// Gets the materials of this shape if any.
    pub fn get_material_descs_with_names(&self) -> Vec<(Option<String>, MaterialDesc)> {
        match self {
            Self::Mesh(m) => m.get_material_descs_with_names(),
            Self::SdfSkin(s) => s
                .replacement_mesh()
                .as_ref()
                .map_or_else(Default::default, WorldMesh::get_material_descs_with_names),
            Self::SdfProgram(_) | Self::Text(_) | Self::RenderModule(_) | Self::None => vec![],
        }
    }

    /// Gets the names of all morph target associated with the mesh.
    ///
    /// It is recommended to store the return value instead of calling it every frame.
    pub fn get_morph_target_names(&self) -> Vec<String> {
        match self {
            Self::Mesh(m) => m.get_morph_target_names(),
            Self::SdfSkin(s) => s
                .replacement_mesh()
                .as_ref()
                .map_or_else(Default::default, WorldMesh::get_morph_target_names),
            Self::SdfProgram(_) | Self::Text(_) | Self::RenderModule(_) | Self::None => vec![],
        }
    }
}

/// Render component.
///
/// Lets you configure the look of a entity by assigning a mesh.
/// Usually accessed through entity.render().

pub struct Render {
    id: Entity,
}

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

impl Render {
    impl_world_accessor!(
        /// Returns a `ValueAccessor` for the shape used by the entity.
        ///
        /// Used to set/get the shape.
        /// Sets which render shape is attached to the render component of this entity (assuming it has one).
        Render,
        Shape,
        Shape,
        shape,
        ValueAccessorDataReadWrite
    );

    impl_world_accessor!(
        /// Returns a `ValueAccessor` for the mesh used by the entity.
        ///
        /// Used to set the mesh
        /// Sets which mesh is attached to the render component of this entity (assuming it has one).
        Render,
        Shape,
        WorldMesh,
        mesh,
        ValueAccessorDataReadWrite
    );

    impl_world_accessor!(
        /// Returns a `ValueAccessor` for the optional mesh style.
        ///
        /// Used to set/get the mesh style. See `MeshStyle`.
        Render,
        MeshStyle,
        Option<MeshStyle>,
        mesh_style,
        ValueAccessorReadWrite
    );

    impl_world_accessor!(
        /// Controls whether the entity will be rendered or not.
        Render,
        Visible,
        bool,
        visible,
        ValueAccessorReadWrite
    );

    impl_world_accessor!(
        /// Returns a `ValueAccessor` for the set of player ids this mesh should be displayed for.
        ///
        /// Used to set/get the player id set.
        /// Sets which player id set is attached to the render component of this entity (assuming it has one).
        Render,
        PlayerIdSet,
        PlayerIdSet,
        player_id_set,
        ValueAccessorDataReadWrite
    );

    impl_world_accessor!(
        /// Returns a `ValueAccessor` for setting dynamic data that is passed to the render module.
        /// Data is expected to be of type "Binary"
        ///
        /// Used to set/get the dynamic data. The dynamic data is only used if the render component has the `Shape` `RenderModule`.
        Render,
        DynamicModuleData,
        BinaryData,
        dynamic_module_data,
        ValueAccessorDataReadWrite
    );

    /// Used to set a mesh that this render component should render.
    pub fn set_mesh(&self, mesh: WorldMesh) {
        self.shape().set(Shape::Mesh(mesh));
    }
}

impl ValueConverterTrait<Shape> for ValueConverter {
    fn into_value(v: Shape) -> Value {
        // Partial move of mesh (WorldData) here, will not invoke drop
        match v {
            Shape::Mesh(v) => <Self as ValueConverterTrait<WorldData>>::into_value(v.mesh),
            Shape::SdfProgram(v) => <Self as ValueConverterTrait<WorldData>>::into_value(v.data),
            Shape::SdfSkin(v) => <Self as ValueConverterTrait<WorldData>>::into_value(v.data),
            Shape::Text(v) => <Self as ValueConverterTrait<WorldData>>::into_value(v.data),
            Shape::RenderModule(v) => <Self as ValueConverterTrait<WorldData>>::into_value(v.data),
            Shape::None => Value::from_i64(DataHandle::invalid().as_ffi() as i64),
        }
    }
    fn from_value(v: &Value) -> Shape {
        let data = <Self as ValueConverterTrait<WorldData>>::from_value(v);
        if !data.is_valid() {
            return Shape::None;
        }

        // needed because of non_exhaustive together with `clippy::wildcard_enum_match_arm`
        #[allow(clippy::wildcard_in_or_patterns)]
        match data.data_type() {
            WorldDataType::Mesh => Shape::Mesh(WorldMesh {
                mesh: <Self as ValueConverterTrait<WorldData>>::from_value(v),
            }),
            WorldDataType::SdfProgram => Shape::SdfProgram(SdfProgram {
                data: <Self as ValueConverterTrait<WorldData>>::from_value(v),
            }),
            WorldDataType::SdfSkin => Shape::SdfSkin(SdfSkin {
                data: <Self as ValueConverterTrait<WorldData>>::from_value(v),
            }),
            WorldDataType::FormattedText => Shape::Text(FormattedText {
                data: <Self as ValueConverterTrait<WorldData>>::from_value(v),
            }),
            WorldDataType::RenderModule => Shape::RenderModule(RenderModuleData {
                data: <Self as ValueConverterTrait<WorldData>>::from_value(v),
            }),
            WorldDataType::String
            | WorldDataType::Binary
            | WorldDataType::ComponentsMetaData
            | WorldDataType::EntityQuery
            | WorldDataType::CompoundPhysicsShape
            | WorldDataType::AudioClip
            | WorldDataType::MorphTargetWeights
            | WorldDataType::WorldMaterial
            | WorldDataType::WorldMaterials
            | WorldDataType::PlayerIdSet
            | _ => Shape::None,
        }
    }
}

impl_world_component!(Render);