# ##### BEGIN GPL LICENSE BLOCK #####
#
#  Copyright (C) 2022 VFX Grace - All Rights Reserved
#
#  This program is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <https://www.gnu.org/licenses/>.
#
# ##### END GPL LICENSE BLOCK #####

import bpy
from bpy.types import PropertyGroup, UILayout, NodeTree
from bpy.props import (BoolProperty, CollectionProperty, EnumProperty,
                       FloatProperty, IntProperty, PointerProperty,
                       StringProperty)
from .utils import generate_unique_name


def draw_prop(context,
              layout: UILayout,
              md_props,
              parent_prop_name,
              prop_name,
              draw_random,
              draw_mask,
              invert_value="",
              draw_curve=False,
              disable_value_when_curve=False):
    parent_prop = getattr(md_props, parent_prop_name)
    lay = layout
    rand_prop = mask_prop = curve_prop = curve_node = None
    use_rand = use_curve = False
    if draw_random:
        rand_prop = getattr(parent_prop, prop_name + "_random")
        use_rand = rand_prop.use_random
    if draw_mask:
        mask_prop = getattr(parent_prop, prop_name + "_mask")
    if draw_curve:
        curve_prop = getattr(parent_prop, prop_name + "_curve")
        use_curve = curve_prop.use_curve
        if use_curve and curve_prop.curve:
            curve_node_name = f"{md_props.name}.{parent_prop_name}.{prop_name}_curve"
            if curve_node_name in curve_prop.curve.nodes:
                curve_node = curve_prop.curve.nodes[curve_node_name]
    if use_rand or use_curve:
        lay = layout.box()
    if use_rand or (mask_prop and (mask_prop.vertex_group or mask_prop.use_texture)):
        lay = lay.column(align=True)
    row = lay.row(align=True)
    prop_row = row
    if disable_value_when_curve and curve_node:
        prop_row = row.row(align=True)
        prop_row.use_property_decorate = False
    prop_row.prop(parent_prop, prop_name)
    if rand_prop:
        prop_row.prop(rand_prop, "use_random", text="", icon='STICKY_UVS_DISABLE')
    if mask_prop:
        if mask_prop.vertex_group:
            sub_row = lay.row(align=True)
            sub_row.prop_search(mask_prop, "vertex_group", context.object, "vertex_groups", text="└─")
            sub_row.prop_enum(md_props, "mask_invert_flag", invert_value, text="", icon='ARROW_LEFTRIGHT')
        else:
            props = prop_row.operator("particle.assign_vertex_group", text="", icon="GROUP_VERTEX")
            props.prop_path = mask_prop.path_from_id()
        prop_row.prop(mask_prop, "use_texture", text="", icon='TEXTURE_DATA')
        if mask_prop.use_texture:
            obj = context.object
            if obj and obj.type == 'MESH' and obj.data.uv_layers.active is None:
                sub_row = lay.row()
                sub_row.alignment = 'CENTER'
                sub_row.label(text="UV Map Needed", icon='ERROR')
            else:
                sub_row = lay.row(align=True)
                sub_row.prop(mask_prop, "texture", text="└─")
                prop_path = mask_prop.path_from_id()
                if mask_prop.texture:
                    sub_row.prop_enum(md_props, "tex_invert_flag", invert_value, text="", icon='ARROW_LEFTRIGHT')
                    sub_row.operator("particle.edit_texture", text="", icon='OPTIONS').prop_path = prop_path
                    if mask_prop.texture.type == 'IMAGE':
                        props = sub_row.operator("particle.edit_texture", text="", icon='TPAINT_HLT')
                        props.prop_path = prop_path
                        props.edit_image = True
                else:
                    sub_row.operator("particle.add_texture", text="", icon='ADD').prop_path = prop_path

    if curve_prop:
        row.prop(curve_prop, "use_curve", text="", icon='RNDCURVE')

    if use_rand and (not use_curve or not disable_value_when_curve):
        lay.prop(rand_prop, "seed")
        lay.prop(rand_prop, "random")
        lay.prop(rand_prop, "threshold")

    if use_curve and curve_node:
        lay.template_curve_mapping(curve_node, "mapping")
        if disable_value_when_curve:
            prop_row.enabled = False


class HairModifierCurveProp(PropertyGroup):
    def get_curve_node_name(self, mdname=None):
        if mdname is None:
            child_mod = self.id_data.child_modifier
            mdname = child_mod.modifiers[child_mod.active_index].name
        prop_path = self.path_from_id()
        mp_idx = prop_path.rfind("].")
        assert mp_idx != -1
        return mdname + prop_path[mp_idx + 1:]

    def rename_curve_node_name(self, old_mdname, new_mdname):
        if "curve" not in self:
            return
        if self.curve is None:
            return
        prop_path = self.path_from_id()
        mp_idx = prop_path.rfind("].")
        assert mp_idx != -1
        oldname = old_mdname + prop_path[mp_idx + 1:]
        if oldname in self.curve.nodes:
            self.curve.nodes[oldname].name = new_mdname + prop_path[mp_idx + 1:]

    def remove_curve_node(self, mdname, other_nodes=set()):
        if self.curve is None:
            return
        curve_name = self.get_curve_node_name(mdname)
        if curve_name in self.curve.nodes:
            node = self.curve.nodes[curve_name]
            if node not in other_nodes:
                self.curve.nodes.remove(node)

    def copy_curve_map(self, src, dest):
        i = 0
        while i < len(src.points):
            p = src.points[i]
            if i >= len(dest.points):
                dp = dest.points.new(p.location[0], p.location[1])
            else:
                dp = dest.points[i]
                dp.location[0] = p.location[0]
                dp.location[1] = p.location[1]
            dp.handle_type = p.handle_type
            dp.select = p.select
            i += 1

    def copy_curve_mapping(self, src, dest):
        dest.black_level = src.black_level
        dest.clip_max_x = src.clip_max_x
        dest.clip_max_y = src.clip_max_y
        dest.clip_min_x = src.clip_min_x
        dest.clip_min_y = src.clip_min_y
        dest.extend = src.extend
        dest.tone = src.tone
        dest.use_clip = src.use_clip
        dest.white_level = src.white_level
        for s, d in zip(src.curves, dest.curves):
            self.copy_curve_map(s, d)
        dest.update()

    def copy_curve(self, src_mdname, dest, dest_mdname):
        if "curve" not in self:
            return
        if self.curve is None:
            return
        src_node_name = self.get_curve_node_name(src_mdname)
        if src_node_name in self.curve.nodes:
            src_node = self.curve.nodes[src_node_name]
            dest_node = self.curve.nodes.new(type='TextureNodeCurveRGB')
            dest_node.name = src_node_name.replace(src_mdname, dest_mdname)
            dest.curve = self.curve
            self.copy_curve_mapping(src_node.mapping, dest_node.mapping)
            dest.use_curve = self.use_curve

    def get_curve_node(self, mdname):
        if self.curve is None:
            return None
        node_name = self.get_curve_node_name(mdname)
        if node_name not in self.curve.nodes:
            return None
        return self.curve.nodes[node_name]

    def create_curve_node(self, ntree, node_name):
        curve = ntree.nodes.new(type='TextureNodeCurveRGB')
        curve.name = node_name
        for c in curve.mapping.curves:
            if len(c.points) > 0:
                c.points[0].location[1] = 1.0
        curve.mapping.initialize()
        return curve

    def reset_curve(self, mdname, new_ntree):
        if "curve" not in self or self.curve is None:
            return
        node_name = self.get_curve_node_name(mdname)
        if node_name in new_ntree.nodes:
            return

        ntree = self.curve
        new_curve_node = self.create_curve_node(new_ntree, node_name)
        self.curve = new_ntree
        if node_name not in ntree.nodes:
            return
        self.copy_curve_mapping(ntree.nodes[node_name].mapping, new_curve_node.mapping)

    def use_curve_updated(self, context):
        node_name = self.get_curve_node_name()
        if self.use_curve and self.curve is None:
            idname = self.id_data.name
            if idname in bpy.data.node_groups:
                ntree = bpy.data.node_groups[idname]
            else:
                ntree = bpy.data.node_groups.new(name=idname, type='TextureNodeTree')
            self.create_curve_node(ntree, node_name)
            self.curve = ntree
        elif self.curve and node_name not in self.curve.nodes:
            self.curve = None

    use_curve: BoolProperty(name="Use Curve", default=False, update=use_curve_updated)
    curve: PointerProperty(name="Curve", type=NodeTree)


class HairModifierMaskProp(PropertyGroup):
    def texture_updated(self, context):
        from .modifier_manager import modifier_updated, UpdateType
        modifier_updated(context, self.id_data, UpdateType.MODIFIED, None)

    use_texture: BoolProperty(name="Use Texture", default=False)
    vertex_group: StringProperty(name="Vertex Group")
    texture: PointerProperty(name="Texture", type=bpy.types.Texture, update=texture_updated)

    def copy_props(self, dest):
        dest.use_texture = self.use_texture
        dest.vertex_group = self.vertex_group
        dest.texture = self.texture


class HairModifierRandomProp(PropertyGroup):
    use_random: BoolProperty(name="Use Random", default=False)
    seed: IntProperty(name="Seed", min=0)
    random: FloatProperty(name="Random Range", min=0, soft_max=1.0, default=1.0, step=1)
    threshold: FloatProperty(name="Threshold", min=0, max=1, subtype='FACTOR')

    def copy_props(self, dest):
        dest.use_random = self.use_random
        dest.seed = self.seed
        dest.random = self.random
        dest.threshold = self.threshold


class HairModifierIntRandomProp(PropertyGroup):
    use_random: BoolProperty(name="Use Random", default=False)
    seed: IntProperty(name="Seed", min=0)
    random: IntProperty(name="Random Range", min=0)
    threshold: FloatProperty(name="Threshold", min=0, max=1, subtype='FACTOR')

    def copy_props(self, dest):
        dest.use_random = self.use_random
        dest.seed = self.seed
        dest.random = self.random
        dest.threshold = self.threshold


class HairLengthModifierProps(PropertyGroup):
    MASK_INVERT_PROPS = ("LENGTH_SCALE", "LENGTH_LENGTH")

    mode: EnumProperty(name="Mode", items=[('SCALE', "Scale", "", 0), ('CUT', "Cut", "", 1)])
    scale: FloatProperty(name="Scale", min=0, soft_min=0.1, default=1, step=1)
    length: FloatProperty(name="Length", min=0, max=1.0, soft_min=0.1, default=1, step=1)
    scale_random: PointerProperty(name="", type=HairModifierRandomProp)
    length_random: PointerProperty(name="", type=HairModifierRandomProp)
    scale_mask: PointerProperty(name="Scale Mask", type=HairModifierMaskProp)
    length_mask: PointerProperty(name="Length Mask", type=HairModifierMaskProp)

    @classmethod
    def get_modifier_props(cls, md_props):
        return md_props.length_props

    @classmethod
    def draw_influence_threshold(cls, md_props, col: bpy.types.UILayout):
        col.prop(md_props, "influence_threshold")

    @classmethod
    def draw(cls, md_props, context, layout: bpy.types.UILayout):
        props = md_props.length_props
        layout.prop(props, "mode")
        if props.mode == 'SCALE':
            draw_prop(context, layout, md_props, "length_props", "scale", True, True, 'LENGTH_SCALE')
        else:
            draw_prop(context, layout, md_props, "length_props", "length", True, True, 'LENGTH_LENGTH')


class HairNoiseModifierProps(PropertyGroup):
    MASK_INVERT_PROPS = ("NOISE_SIZE", "NOISE_MAGNITUDE", "NOISE_ENDPOINT_ROUGHNESS")

    size: FloatProperty(name="Size", min=0.01, soft_max=10, default=1, step=0.1, precision=3)
    size_random: PointerProperty(name="", type=HairModifierRandomProp)
    size_mask: PointerProperty(name="Size Mask", type=HairModifierMaskProp)
    endpoint_roughness: FloatProperty(name="Endpoint", min=0.0, max=100000.0, soft_max=10, default=0, step=0.1, precision=3)
    endpoint_roughness_random: PointerProperty(name="", type=HairModifierRandomProp)
    endpoint_roughness_mask: PointerProperty(name="", type=HairModifierMaskProp)
    endpoint_shape: FloatProperty(name="Endpoint Shape", min=0.01, max=10, default=1, precision=3)
    endpoint_shape_random: PointerProperty(name="", type=HairModifierRandomProp)
    magnitude: FloatProperty(name="Magnitude", min=0, max=100000.0, default=1, step=0.1, precision=3)
    magnitude_random: PointerProperty(name="", type=HairModifierRandomProp)
    magnitude_curve: PointerProperty(name="Magnitude Curve", type=HairModifierCurveProp)
    magnitude_mask: PointerProperty(name="Magnitude Mask", type=HairModifierMaskProp)
    preserve_length: FloatProperty(name="Preserve Length", min=0, max=1, default=0, subtype='FACTOR')

    @classmethod
    def get_modifier_props(cls, md_props):
        return md_props.noise_props

    @classmethod
    def draw_influence_threshold(cls, md_props, col: bpy.types.UILayout):
        col.prop(md_props, "influence_threshold")

    @classmethod
    def draw(cls, md_props, context, layout: bpy.types.UILayout):
        props = md_props.noise_props
        col = layout.column(align=True)
        draw_prop(context, col, md_props, "noise_props", "endpoint_roughness", True, True, 'NOISE_ENDPOINT_ROUGHNESS')
        draw_prop(context, col, md_props, "noise_props", "endpoint_shape", True, False)

        col = layout.column(align=True)
        draw_prop(context, col, md_props, "noise_props", "size", True, True, 'NOISE_SIZE')
        draw_prop(context, col, md_props, "noise_props", "magnitude", True, True, 'NOISE_MAGNITUDE', True)
        layout.prop(props, "preserve_length")


class HairClumpModifierProps(PropertyGroup):
    MASK_INVERT_PROPS = ('CLUMP_FACTOR', 'CLUMP_CLUMP', 'CLUMP_DENSITY',
                         'CLUMP_SUBDIV', 'CLUMP_AMPLITUDE', 'CLUMP_FREQ')

    influence_threshold_flag: EnumProperty(
        name="",
        items=[('CLUMP', "Clump", "Effect clump", 1),
               ('KINK', "Kink", "Effect kink", 2)],
        default={'CLUMP', 'KINK'},
        options={'ENUM_FLAG'})
    factor: FloatProperty(name="Factor", min=0, max=1, default=1, subtype='FACTOR',
        description="The clump factor for *clumps*")
    factor_random: PointerProperty(type=HairModifierRandomProp)
    factor_mask: PointerProperty(name="Factor Mask", type=HairModifierMaskProp)
    clump: FloatProperty(name="Clump", min=-1, max=1, default=1, subtype='FACTOR',
        description="Amount of clumping for *paths*")
    clump_random: PointerProperty(type=HairModifierRandomProp)
    clump_mask: PointerProperty(name="Clump Mask", type=HairModifierMaskProp)
    shape: FloatProperty(name="Shape", min=-1, max=1)
    shape_curve: PointerProperty(name="Shape Curve", type=HairModifierCurveProp)
    preserve_length: FloatProperty(name="Preserve Length", min=0, max=1, default=0, subtype='FACTOR')
    clump_type: EnumProperty(name="Clump Type", items=[('PARENT', "Parent", "", 0), ('GENERATED', "Generated", "", 1)])
    generate_method: EnumProperty(
        name="Generating Method",
        items=
        [('GRID', "Grid",
          "Distributed more evenly but calculated slower",
          1),
         ('POISSON', "Poisson Disk",
          "Distribute the points randomly on the surface while taking a minimum distance between "
          "points into account",
          2)],
          default='GRID')
    subdiv: IntProperty(name="Subdivision", min=0, description="How many clump the parent clump subdivided into")
    subdiv_random: PointerProperty(type=HairModifierIntRandomProp)
    subdiv_mask: PointerProperty(name="Subdivision Mask", type=HairModifierMaskProp)
    points_seed: IntProperty(name="Seed", min=0)
    randomness: FloatProperty(name="Randomness", min=0, max=1, default=1, subtype='FACTOR')
    density: FloatProperty(name="Density", min=0, default=1)
    density_mask: PointerProperty(name="Density Mask", type=HairModifierMaskProp)
    distance_min: FloatProperty(name="Distance Min", min=0, default=0.05, subtype='DISTANCE')
    kink_type: EnumProperty(name="Kink Type",
                            items=[('CURL', "Curl", "", 0),
                                   ('RADIAL', "Radial", "", 1),
                                   ('WAVE', "Wave", "", 2),
                                   ('BRAID', "Braid", "", 3)])
    kink_curl_type: EnumProperty(name="Curl Type", items=[('CURL', "Curl", "", 0), ('TWIST', "Twist", "", 1)])
    kink_amplitude: FloatProperty(name="Amplitude", step=0.1, precision=3)
    kink_amplitude_random: PointerProperty(type=HairModifierRandomProp)
    kink_amplitude_curve: PointerProperty(name="Amplitude Curve", type=HairModifierCurveProp)
    kink_amplitude_mask: PointerProperty(name="Amplitude Mask", type=HairModifierMaskProp)
    kink_orient: FloatProperty(name="Orient", min=0, max=1, default=0, subtype='FACTOR', step=0.1, precision=3)
    kink_orient_random: PointerProperty(type=HairModifierRandomProp)
    kink_freq: FloatProperty(name="Frequency", precision=3)
    kink_freq_random: PointerProperty(type=HairModifierRandomProp)
    kink_freq_mask: PointerProperty(name="Frequency Mask", type=HairModifierMaskProp)
    kink_shape: FloatProperty(name="Shape", min=-1, max=1, subtype='FACTOR')
    kink_shape_random: PointerProperty(type=HairModifierRandomProp)
    kink_shape_curve: PointerProperty(name="Shape Curve", type=HairModifierCurveProp)

    @classmethod
    def get_modifier_props(cls, md_props):
        return md_props.clump_props

    @classmethod
    def draw_influence_threshold(cls, md_props, col: bpy.types.UILayout):
        if md_props.influence_threshold > 0.0:
            row = col.row(align=True)
            props = md_props.clump_props
            row.prop(md_props, "influence_threshold")
            row.prop_enum(props, "influence_threshold_flag", "CLUMP", text="", icon='GP_MULTIFRAME_EDITING')
            row.prop_enum(props, "influence_threshold_flag", "KINK", text="", icon='STROKE')
        else:
            col.prop(md_props, "influence_threshold")

    @classmethod
    def draw(cls, md_props, context, layout: bpy.types.UILayout):
        from .modifier_manager import get_clump_point_count
        props = md_props.clump_props
        col = layout.column(align=True)
        draw_prop(context, col, md_props, "clump_props", "factor", True, True, 'CLUMP_FACTOR')
        draw_prop(context, col, md_props, "clump_props", "clump", True, True, 'CLUMP_CLUMP')
        draw_prop(context, col, md_props, "clump_props", "shape", False, False, draw_curve=True,
            disable_value_when_curve=True)
        col.prop(props, "preserve_length")
        col = layout.column(align=True)
        col.prop(props, "clump_type")
        if props.clump_type == 'GENERATED':
            col.prop(props, "generate_method")
        col.prop(props, "points_seed")
        if props.clump_type == 'GENERATED':
            if props.generate_method == 'GRID':
                # draw_mask_prop(col, context, md_props, props, "density", "CLUMP_DENSITY")
                col.prop(props, "randomness")
            else:
                col.prop(props, "distance_min")
            draw_prop(context, col, md_props, "clump_props", "density", False, True, 'CLUMP_DENSITY')
        else:
            draw_prop(context, col, md_props, "clump_props", "subdiv", True, True, 'CLUMP_SUBDIV')
        obj = context.object
        evaled_obj = obj.evaluated_get(context.evaluated_depsgraph_get())
        child_count = len(evaled_obj.particle_systems.active.child_particles)
        cpc = get_clump_point_count(obj, obj.particle_systems.active, md_props.id_data.child_modifier.active_index)
        sub_row = col.row(align=True)
        if cpc >= child_count:
            sub_row.alert = True
        sub_row.alignment = 'CENTER'
        sub_row.label(text="Clump Points Count: ")
        sub_row.label(text=str(cpc), icon='NONE' if cpc < child_count else 'ERROR')
        col = layout.column(align=True)
        col.prop(props, "kink_type")
        if props.kink_type == 'CURL':
            col.row().prop(props, "kink_curl_type", expand=True)
        draw_prop(context, col, md_props, "clump_props", "kink_amplitude", True, True, 'CLUMP_AMPLITUDE', True)
        if props.kink_type == 'WAVE':
            draw_prop(context, col, md_props, "clump_props", "kink_orient", True, False)
        draw_prop(context, col, md_props, "clump_props", "kink_freq", True, True, 'CLUMP_FREQ')
        draw_prop(context, col, md_props, "clump_props", "kink_shape", True, False, draw_curve=True,
            disable_value_when_curve=True)


MODIFIER_TYPES = {
    "LENGTH": HairLengthModifierProps,
    "NOISE": HairNoiseModifierProps,
    "CLUMP": HairClumpModifierProps,
}


class HairModifierProps(PropertyGroup):
    MODIFIER_TYPE_ITEMS = [('LENGTH', "Length", "", 'FULLSCREEN_ENTER', 0),
                           ('NOISE', "Noise", "", 'FORCE_FORCE', 1),
                           ('CLUMP', "Clump", "", 'GP_MULTIFRAME_EDITING', 2)]

    def get_name(self):
        return self['name']

    def set_name(self, value):
        if "name" not in self:
            self["name"] = value
            return
        if self['name'] == value:
            return
        name = generate_unique_name(value, ".", 1, self.id_data.child_modifier.modifiers)
        oldname = str(self['name'])
        self['name'] = name
        for curve_prop in self._get_pointer_props_by_type(HairModifierCurveProp):
            curve_prop.rename_curve_node_name(oldname, name)

    def mask_invert_items(self, context):
        items = [("INFLUENCE", "INFLUENCE", "", 1 << 0)]
        i = 1
        for mf in MODIFIER_TYPES[self.type].MASK_INVERT_PROPS:
            items.append((mf, mf, "", 1 << i))
            i += 1
        return items

    def touch_props(self):
        self.influence
        self.length_props
        self.noise_props
        self.clump_props

    def copy_props(self, dest):
        dest.type = self.type
        dest.name = generate_unique_name(self.name + "_copy", ".", 1, self.id_data.child_modifier.modifiers)
        dest.enabled = self.enabled
        dest.influence = self.influence
        dest.seed = self.seed
        dest.influence_threshold = self.influence_threshold
        self.influence_mask.copy_props(dest.influence_mask)
        dest.mask_invert_flag = self.mask_invert_flag
        dest.tex_invert_flag = self.tex_invert_flag
        src_props = MODIFIER_TYPES[self.type].get_modifier_props(self)
        dst_props = MODIFIER_TYPES[self.type].get_modifier_props(dest)
        for name, prop in src_props.bl_rna.properties.items():
            if not prop.is_runtime:
                continue
            src_prop = getattr(src_props, name)
            if type(prop) is bpy.types.PointerProperty:
                dst_prop = getattr(dst_props, name)
                if type(prop.fixed_type) is HairModifierCurveProp:
                    src_prop.copy_curve(self.name, dst_prop, dest.name)
                elif type(prop.fixed_type) in (HairModifierRandomProp,
                                               HairModifierMaskProp,
                                               HairModifierIntRandomProp):
                    src_prop.copy_props(dst_prop)
            else:
                setattr(dst_props, name, src_prop)

    def _get_pointer_props_by_type(self, prop_type):
        pointer_props = []
        props = MODIFIER_TYPES[self.type].get_modifier_props(self)
        for name, prop in props.bl_rna.properties.items():
            if (prop.is_runtime and type(prop) is bpy.types.PointerProperty and type(prop.fixed_type) is prop_type):
                pointer_props.append(getattr(props, name))
        return pointer_props

    def remove_curve_nodes(self):
        nodes = set()
        for part in bpy.data.particles:
            if part == self.id_data:
                continue
            for md in part.child_modifier.modifiers:
                md.get_curve_nodes(nodes)
        for curve_prop in self._get_pointer_props_by_type(HairModifierCurveProp):
            curve_prop.remove_curve_node(self.name, nodes)

    def get_curve_ntrees(self, ntrees: set):
        for curve_prop in self._get_pointer_props_by_type(HairModifierCurveProp):
            if curve_prop.curve:
                ntrees.add(curve_prop.curve)

    def get_curve_nodes(self, nodes: set):
        for curve_prop in self._get_pointer_props_by_type(HairModifierCurveProp):
            node = curve_prop.get_curve_node(self.name)
            if node:
                nodes.add(node)

    def reset_curves(self, ntree):
        for curve_prop in self._get_pointer_props_by_type(HairModifierCurveProp):
            curve_prop.reset_curve(self.name, ntree)

    def rename_vertex_group(self, oldname, newname):
        if self.influence_mask.vertex_group and self.influence_mask.vertex_group == oldname:
            self.influence_mask.vertex_group = newname
        for mask_prop in self._get_pointer_props_by_type(HairModifierMaskProp):
            if mask_prop.vertex_group and mask_prop.vertex_group == oldname:
                mask_prop.vertex_group = newname

    name: StringProperty(name="Modifier Name", get=get_name, set=set_name)
    type: EnumProperty(name="Modifier Type", items=MODIFIER_TYPE_ITEMS)
    enabled: BoolProperty(name="Enabled", default=True)
    influence: FloatProperty(name="Influence", min=0, max=1, default=1, subtype='FACTOR')
    seed: IntProperty(name="Seed", min=0, default=0)
    influence_threshold: FloatProperty(name="Threshold", min=0, max=1, subtype='FACTOR',
        description="Amount of paths that are not affected by the modifier")
    influence_mask: PointerProperty(name="Influence Mask", type=HairModifierMaskProp)
    mask_invert_flag: EnumProperty(name="Invert VertexGroup Mask", items=mask_invert_items, options={'ENUM_FLAG'})
    tex_invert_flag: EnumProperty(name="Invert Texture Mask", items=mask_invert_items, options={'ENUM_FLAG'})
    length_props: PointerProperty(type=HairLengthModifierProps)
    noise_props: PointerProperty(type=HairNoiseModifierProps)
    clump_props: PointerProperty(type=HairClumpModifierProps)


class HairModifiersProps(PropertyGroup):
    modifiers: CollectionProperty(
        type=HairModifierProps,
        name="Hair Modifiers",
        description="Modifiers of hair child")

    active_index: IntProperty(name="Active Modifier Index", default=0)
    use_child_modifier: BoolProperty(name="Use Child Modifier", default=False)

    @classmethod
    def register(cls):
        bpy.types.ParticleSettings.child_modifier = bpy.props.PointerProperty(type=cls)

    @classmethod
    def unregister(cls):
        del bpy.types.ParticleSettings.child_modifier


classes = [
    HairModifierCurveProp, HairModifierMaskProp, HairModifierRandomProp,
    HairModifierIntRandomProp, HairLengthModifierProps, HairClumpModifierProps,
    HairNoiseModifierProps, HairModifierProps, HairModifiersProps
]


def register():
    from bpy.utils import register_class
    for cls in classes:
        register_class(cls)


def unregister():
    from bpy.utils import unregister_class
    for cls in reversed(classes):
        unregister_class(cls)
