# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on 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.
#
# BlenderBIM Add-on 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 BlenderBIM Add-on.  If not, see <http://www.gnu.org/licenses/>.

import bpy
import json
import ifcopenshell.api
import ifcopenshell.util.element
import ifcopenshell.util.attribute
import ifcopenshell.util.representation
import blenderbim.bim.helper
import blenderbim.tool as tool
import blenderbim.core.material as core
import blenderbim.bim.module.model.profile as model_profile
from blenderbim.bim.module.material.prop import purge as material_prop_purge
from blenderbim.bim.ifc import IfcStore


class LoadMaterials(bpy.types.Operator, tool.Ifc.Operator):
    bl_idname = "bim.load_materials"
    bl_label = "Load Materials"
    bl_options = {"REGISTER", "UNDO"}

    def _execute(self, context):
        core.load_materials(tool.Material, context.scene.BIMMaterialProperties.material_type)


class DisableEditingMaterials(bpy.types.Operator, tool.Ifc.Operator):
    bl_idname = "bim.disable_editing_materials"
    bl_label = "Disable Editing Materials"
    bl_options = {"REGISTER", "UNDO"}

    def _execute(self, context):
        core.disable_editing_materials(tool.Material)


class SelectByMaterial(bpy.types.Operator, tool.Ifc.Operator):
    bl_idname = "bim.select_by_material"
    bl_label = "Select By Material"
    bl_options = {"REGISTER", "UNDO"}
    material: bpy.props.IntProperty()

    def _execute(self, context):
        core.select_by_material(tool.Material, material=tool.Ifc.get().by_id(self.material))


class AssignParameterizedProfile(bpy.types.Operator, tool.Ifc.Operator):
    bl_idname = "bim.assign_parameterized_profile"
    bl_label = "Assign Parameterized Profile"
    bl_options = {"REGISTER", "UNDO"}
    ifc_class: bpy.props.StringProperty()
    material_profile: bpy.props.IntProperty()
    obj: bpy.props.StringProperty()

    def _execute(self, context):
        obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
        self.file = IfcStore.get_file()
        profile = ifcopenshell.api.run(
            "profile.add_parameterized_profile",
            self.file,
            **{"ifc_class": self.ifc_class},
        )
        ifcopenshell.api.run(
            "material.assign_profile",
            self.file,
            **{"material_profile": self.file.by_id(self.material_profile), "profile": profile},
        )
        bpy.ops.bim.enable_editing_material_set_item(obj=obj.name, material_set_item=self.material_profile)


class AddMaterial(bpy.types.Operator, tool.Ifc.Operator):
    bl_idname = "bim.add_material"
    bl_label = "Add Material"
    bl_options = {"REGISTER", "UNDO"}
    obj: bpy.props.StringProperty()

    def _execute(self, context):
        obj = bpy.data.materials.get(self.obj) if self.obj else None
        core.add_material(tool.Ifc, tool.Material, tool.Style, obj=obj)
        material_prop_purge()


class AddMaterialSet(bpy.types.Operator, tool.Ifc.Operator):
    bl_idname = "bim.add_material_set"
    bl_label = "Add Material Set"
    bl_options = {"REGISTER", "UNDO"}
    set_type: bpy.props.StringProperty()

    def _execute(self, context):
        core.add_material_set(tool.Ifc, tool.Material, set_type=self.set_type)
        material_prop_purge()


class RemoveMaterial(bpy.types.Operator, tool.Ifc.Operator):
    bl_idname = "bim.remove_material"
    bl_label = "Remove Material"
    bl_options = {"REGISTER", "UNDO"}
    material: bpy.props.IntProperty()

    def _execute(self, context):
        core.remove_material(tool.Ifc, tool.Material, tool.Style, material=tool.Ifc.get().by_id(self.material))


class RemoveMaterialSet(bpy.types.Operator, tool.Ifc.Operator):
    bl_idname = "bim.remove_material_set"
    bl_label = "Remove Material Set"
    bl_options = {"REGISTER", "UNDO"}
    material: bpy.props.IntProperty()

    def _execute(self, context):
        core.remove_material_set(tool.Ifc, tool.Material, material=tool.Ifc.get().by_id(self.material))


class UnlinkMaterial(bpy.types.Operator, tool.Ifc.Operator):
    bl_idname = "bim.unlink_material"
    bl_label = "Unlink Material"
    bl_options = {"REGISTER", "UNDO"}

    def _execute(self, context):
        core.unlink_material(tool.Ifc, obj=context.active_object.active_material)


class AssignMaterial(bpy.types.Operator, tool.Ifc.Operator):
    bl_idname = "bim.assign_material"
    bl_label = "Assign Material"
    bl_options = {"REGISTER", "UNDO"}
    obj: bpy.props.StringProperty()
    material_type: bpy.props.StringProperty()

    def _execute(self, context):
        objects = [bpy.data.objects.get(self.obj)] if self.obj else tool.Blender.get_selected_objects()
        active_obj = context.active_object
        active_object_material_type = self.material_type or active_obj.BIMObjectMaterialProperties.material_type
        material = tool.Ifc.get().by_id(int(active_obj.BIMObjectMaterialProperties.material))
        for obj in objects:
            element = tool.Ifc.get_entity(obj)
            if not element:
                continue
            ifcopenshell.api.run(
                "material.assign_material",
                tool.Ifc.get(),
                product=element,
                type=active_object_material_type,
                material=material,
            )
            assigned_material = ifcopenshell.util.element.get_material(element)
            if assigned_material.is_a("IfcMaterialLayerSet"):
                if not assigned_material.MaterialLayers:
                    unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
                    layer = ifcopenshell.api.run(
                        "material.add_layer",
                        tool.Ifc.get(),
                        layer_set=assigned_material,
                        material=tool.Ifc.get().by_type("IfcMaterial")[0],
                    )
                    thickness = 0.1  # Arbitrary metric thickness for now
                    layer.LayerThickness = thickness / unit_scale
            elif assigned_material.is_a("IfcMaterialProfileSet"):
                if not assigned_material.MaterialProfiles:
                    named_profiles = [p for p in tool.Ifc.get().by_type("IfcProfileDef") if p.ProfileName]
                    if named_profiles:
                        profile = named_profiles[0]
                    else:
                        unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
                        size = 0.5 / unit_scale
                        profile = tool.Ifc.get().create_entity(
                            "IfcRectangleProfileDef",
                            ProfileName="New Profile",
                            ProfileType="AREA",
                            XDim=size,
                            YDim=size,
                        )
                        material_profile = ifcopenshell.api.run(
                            "material.add_profile",
                            tool.Ifc.get(),
                            profile_set=assigned_material,
                            material=tool.Ifc.get().by_type("IfcMaterial")[0],
                        )
                        ifcopenshell.api.run(
                            "material.assign_profile",
                            tool.Ifc.get(),
                            material_profile=material_profile,
                            profile=profile,
                        )


class UnassignMaterial(bpy.types.Operator, tool.Ifc.Operator):
    bl_idname = "bim.unassign_material"
    bl_label = "Unassign Material"
    bl_options = {"REGISTER", "UNDO"}
    obj: bpy.props.StringProperty()

    def _execute(self, context):
        objects = [bpy.data.objects.get(self.obj)] if self.obj else tool.Blender.get_selected_objects()
        for obj in objects:
            element = tool.Ifc.get_entity(obj)
            if element:
                material = ifcopenshell.util.element.get_material(element, should_inherit=False)
                if "Usage" not in material.is_a():
                    ifcopenshell.api.run("material.unassign_material", tool.Ifc.get(), product=element)


class AddConstituent(bpy.types.Operator, tool.Ifc.Operator):
    bl_idname = "bim.add_constituent"
    bl_label = "Add Constituent"
    bl_options = {"REGISTER", "UNDO"}
    obj: bpy.props.StringProperty()
    constituent_set: bpy.props.IntProperty()

    def _execute(self, context):
        obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
        self.file = IfcStore.get_file()
        ifcopenshell.api.run(
            "material.add_constituent",
            self.file,
            **{
                "constituent_set": self.file.by_id(self.constituent_set),
                "material": self.file.by_id(int(obj.BIMObjectMaterialProperties.material)),
            },
        )


class RemoveConstituent(bpy.types.Operator, tool.Ifc.Operator):
    bl_idname = "bim.remove_constituent"
    bl_label = "Remove Constituent"
    bl_options = {"REGISTER", "UNDO"}
    obj: bpy.props.StringProperty()
    constituent: bpy.props.IntProperty()

    def _execute(self, context):
        for inverse in tool.Ifc.get().get_inverse(layer):
            if inverse.is_a("IfcMaterialConstituentSet") and len(inverse.MaterialConstituents) == 1:
                return
        ifcopenshell.api.run(
            "material.remove_constituent", tool.Ifc.get(), constituent=tool.Ifc.get().by_id(self.constituent)
        )


class AddProfile(bpy.types.Operator, tool.Ifc.Operator):
    bl_idname = "bim.add_profile"
    bl_label = "Add Profile"
    bl_options = {"REGISTER", "UNDO"}
    obj: bpy.props.StringProperty()
    profile_set: bpy.props.IntProperty()

    def _execute(self, context):
        obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
        self.file = IfcStore.get_file()
        ifcopenshell.api.run(
            "material.add_profile",
            self.file,
            profile_set=self.file.by_id(self.profile_set),
            material=self.file.by_id(int(obj.BIMObjectMaterialProperties.material)),
            profile=self.file.by_id(int(context.scene.BIMMaterialProperties.profiles)),
        )


class RemoveProfile(bpy.types.Operator, tool.Ifc.Operator):
    bl_idname = "bim.remove_profile"
    bl_label = "Remove Profile"
    bl_options = {"REGISTER", "UNDO"}
    obj: bpy.props.StringProperty()
    profile: bpy.props.IntProperty()

    def _execute(self, context):
        for inverse in tool.Ifc.get().get_inverse(layer):
            if inverse.is_a("IfcMaterialProfileSet") and len(inverse.MaterialProfiles) == 1:
                return
        ifcopenshell.api.run("material.remove_profile", tool.Ifc.get(), profile=tool.Ifc.get().by_id(self.profile))


class AddLayer(bpy.types.Operator, tool.Ifc.Operator):
    bl_idname = "bim.add_layer"
    bl_label = "Add Layer"
    bl_options = {"REGISTER", "UNDO"}
    obj: bpy.props.StringProperty()
    layer_set: bpy.props.IntProperty()

    def _execute(self, context):
        obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
        self.file = IfcStore.get_file()
        ifcopenshell.api.run(
            "material.add_layer",
            self.file,
            **{
                "layer_set": self.file.by_id(self.layer_set),
                "material": self.file.by_id(int(obj.BIMObjectMaterialProperties.material)),
            },
        )


class ReorderMaterialSetItem(bpy.types.Operator, tool.Ifc.Operator):
    bl_idname = "bim.reorder_material_set_item"
    bl_label = "Reorder Material Set Item"
    bl_options = {"REGISTER", "UNDO"}
    obj: bpy.props.StringProperty()
    old_index: bpy.props.IntProperty()
    new_index: bpy.props.IntProperty()
    material_set: bpy.props.IntProperty()

    def _execute(self, context):
        obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
        self.file = IfcStore.get_file()
        material_set = self.file.by_id(self.material_set)
        ifcopenshell.api.run(
            "material.reorder_set_item",
            self.file,
            **{
                "material_set": material_set,
                "old_index": self.old_index,
                "new_index": self.new_index,
            },
        )


class RemoveLayer(bpy.types.Operator, tool.Ifc.Operator):
    bl_idname = "bim.remove_layer"
    bl_label = "Remove Layer"
    bl_options = {"REGISTER", "UNDO"}
    obj: bpy.props.StringProperty()
    layer: bpy.props.IntProperty()

    def _execute(self, context):
        for inverse in tool.Ifc.get().get_inverse(layer):
            if inverse.is_a("IfcMaterialLayerSet") and len(inverse.MaterialLayers) == 1:
                return
        ifcopenshell.api.run("material.remove_layer", tool.Ifc.get(), layer=tool.Ifc.get().by_id(self.layer))


class AddListItem(bpy.types.Operator, tool.Ifc.Operator):
    bl_idname = "bim.add_list_item"
    bl_label = "Add List Item"
    bl_options = {"REGISTER", "UNDO"}
    obj: bpy.props.StringProperty()
    list_item_set: bpy.props.IntProperty()

    def _execute(self, context):
        obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
        self.file = IfcStore.get_file()
        ifcopenshell.api.run(
            "material.add_list_item",
            self.file,
            **{
                "material_list": self.file.by_id(self.list_item_set),
                "material": self.file.by_id(int(obj.BIMObjectMaterialProperties.material)),
            },
        )


class RemoveListItem(bpy.types.Operator, tool.Ifc.Operator):
    bl_idname = "bim.remove_list_item"
    bl_label = "Remove List Item"
    bl_options = {"REGISTER", "UNDO"}
    obj: bpy.props.StringProperty()
    list_item_set: bpy.props.IntProperty()
    list_item: bpy.props.IntProperty()
    list_item_index: bpy.props.IntProperty()

    def _execute(self, context):
        obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
        self.file = IfcStore.get_file()
        ifcopenshell.api.run(
            "material.remove_list_item",
            self.file,
            **{
                "material_list": self.file.by_id(self.list_item_set),
                "material_index": self.list_item_index,
            },
        )


class EnableEditingAssignedMaterial(bpy.types.Operator):
    bl_idname = "bim.enable_editing_assigned_material"
    bl_label = "Enable Editing Assigned Material"
    bl_options = {"REGISTER", "UNDO"}
    obj: bpy.props.StringProperty()

    def execute(self, context):
        obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
        props = obj.BIMObjectMaterialProperties
        props.is_editing = True
        element = tool.Ifc.get_entity(obj)
        material = ifcopenshell.util.element.get_material(element)

        if material.is_a("IfcMaterial"):
            props.material = str(material.id())
            return {"FINISHED"}

        props.material_set_usage_attributes.clear()
        props.material_set_attributes.clear()

        if "Usage" in material.is_a():
            blenderbim.bim.helper.import_attributes2(
                material, props.material_set_usage_attributes, callback=self.import_attributes
            )
            blenderbim.bim.helper.import_attributes2(material[0], props.material_set_attributes)
        else:
            blenderbim.bim.helper.import_attributes2(material, props.material_set_attributes)
        return {"FINISHED"}

    def import_attributes(self, name, prop, data):
        if name == "CardinalPoint":
            # TODO: complain to buildingSMART
            cardinal_point_map = {
                1: "bottom left",
                2: "bottom centre",
                3: "bottom right",
                4: "mid-depth left",
                5: "mid-depth centre",
                6: "mid-depth right",
                7: "top left",
                8: "top centre",
                9: "top right",
                10: "geometric centroid",
                11: "bottom in line with the geometric centroid",
                12: "left in line with the geometric centroid",
                13: "right in line with the geometric centroid",
                14: "top in line with the geometric centroid",
                15: "shear centre",
                16: "bottom in line with the shear centre",
                17: "left in line with the shear centre",
                18: "right in line with the shear centre",
                19: "top in line with the shear centre",
            }
            prop.data_type = "enum"
            prop.enum_items = json.dumps(cardinal_point_map)
            if data[name]:
                prop.enum_value = str(data[name])
            return True


class DisableEditingAssignedMaterial(bpy.types.Operator):
    bl_idname = "bim.disable_editing_assigned_material"
    bl_label = "Disable Editing Assigned Material"
    bl_options = {"REGISTER", "UNDO"}
    obj: bpy.props.StringProperty()

    def execute(self, context):
        bpy.ops.bim.disable_editing_material_set_item()
        obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
        props = obj.BIMObjectMaterialProperties
        props.is_editing = False
        return {"FINISHED"}


class EditAssignedMaterial(bpy.types.Operator, tool.Ifc.Operator):
    bl_idname = "bim.edit_assigned_material"
    bl_label = "Edit Assigned Material"
    bl_options = {"REGISTER", "UNDO"}
    obj: bpy.props.StringProperty()
    material_set: bpy.props.IntProperty()
    material_set_usage: bpy.props.IntProperty()

    def _execute(self, context):
        self.file = IfcStore.get_file()
        active_obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
        props = active_obj.BIMObjectMaterialProperties
        element = tool.Ifc.get_entity(active_obj)
        material = ifcopenshell.util.element.get_material(element)

        objects = tool.Blender.get_selected_objects()

        if props.active_material_set_item_id != 0:  # We were editing a material layer set item
            bpy.ops.bim.edit_material_set_item(material_set_item=props.active_material_set_item_id)

        if material.is_a("IfcMaterial"):
            for obj in objects:
                bpy.ops.bim.unassign_material(obj=obj.name)
                bpy.ops.bim.assign_material(obj=obj.name, material_type="IfcMaterial")
            bpy.ops.bim.disable_editing_assigned_material(obj=active_obj.name)
            return {"FINISHED"}

        material_set = self.file.by_id(self.material_set)
        attributes = blenderbim.bim.helper.export_attributes(props.material_set_attributes)
        ifcopenshell.api.run(
            "material.edit_assigned_material",
            self.file,
            **{"element": material_set, "attributes": attributes},
        )

        if self.material_set_usage:
            material_set_usage = self.file.by_id(self.material_set_usage)
            attributes = blenderbim.bim.helper.export_attributes(props.material_set_usage_attributes)
            if material_set_usage.is_a("IfcMaterialLayerSetUsage"):
                ifcopenshell.api.run(
                    "material.edit_layer_usage",
                    self.file,
                    **{"usage": material_set_usage, "attributes": attributes},
                )
            elif material_set_usage.is_a("IfcMaterialProfileSetUsage"):
                if attributes.get("CardinalPoint", None):
                    attributes["CardinalPoint"] = int(attributes["CardinalPoint"])
                ifcopenshell.api.run(
                    "material.edit_profile_usage",
                    self.file,
                    **{"usage": material_set_usage, "attributes": attributes},
                )

        bpy.ops.bim.disable_editing_assigned_material(obj=active_obj.name)


class EnableEditingMaterialSetItemProfile(bpy.types.Operator):
    bl_idname = "bim.enable_editing_material_set_item_profile"
    bl_label = "Enable Editing Material Set Item"
    bl_options = {"REGISTER", "UNDO"}
    obj: bpy.props.StringProperty()
    material_set_item: bpy.props.IntProperty()

    def execute(self, context):
        obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
        self.props = obj.BIMObjectMaterialProperties
        self.props.material_set_item_profile_attributes.clear()
        profile = tool.Ifc.get().by_id(self.material_set_item).Profile
        blenderbim.bim.helper.import_attributes2(profile, self.props.material_set_item_profile_attributes)
        return {"FINISHED"}


class DisableEditingMaterialSetItemProfile(bpy.types.Operator):
    bl_idname = "bim.disable_editing_material_set_item_profile"
    bl_label = "Disable Editing Material Set Item"
    bl_options = {"REGISTER", "UNDO"}
    obj: bpy.props.StringProperty()

    def execute(self, context):
        obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
        self.props = obj.BIMObjectMaterialProperties
        self.props.material_set_item_profile_attributes.clear()
        return {"FINISHED"}


class EditMaterialSetItemProfile(bpy.types.Operator, tool.Ifc.Operator):
    bl_idname = "bim.edit_material_set_item_profile"
    bl_label = "Edit Material Set Item Profile"
    bl_options = {"REGISTER", "UNDO"}
    obj: bpy.props.StringProperty()
    material_set_item: bpy.props.IntProperty()

    def _execute(self, context):
        obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
        self.props = obj.BIMObjectMaterialProperties
        attributes = blenderbim.bim.helper.export_attributes(self.props.material_set_item_profile_attributes)
        profile = tool.Ifc.get().by_id(self.material_set_item).Profile
        ifcopenshell.api.run("profile.edit_profile", tool.Ifc.get(), profile=profile, attributes=attributes)
        self.props.material_set_item_profile_attributes.clear()
        model_profile.DumbProfileRegenerator().regenerate_from_profile_def(profile)


class EnableEditingMaterialSetItem(bpy.types.Operator):
    bl_idname = "bim.enable_editing_material_set_item"
    bl_label = "Enable Editing Material Set Item"
    bl_options = {"REGISTER", "UNDO"}
    obj: bpy.props.StringProperty()
    material_set_item: bpy.props.IntProperty()

    def execute(self, context):
        self.file = IfcStore.get_file()
        obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
        self.mprops = context.scene.BIMMaterialProperties
        self.props = obj.BIMObjectMaterialProperties
        self.props.active_material_set_item_id = self.material_set_item

        element = tool.Ifc.get_entity(obj)
        material = ifcopenshell.util.element.get_material(element, should_skip_usage=True)
        material_set_item = self.file.by_id(self.material_set_item)

        self.props.material_set_item_material = str(material_set_item.Material.id())

        self.props.material_set_item_attributes.clear()
        blenderbim.bim.helper.import_attributes2(material_set_item, self.props.material_set_item_attributes)

        if material_set_item.is_a("IfcMaterialProfile"):
            if material_set_item.Profile:
                self.mprops.profiles = str(material_set_item.Profile.id())

        return {"FINISHED"}


class DisableEditingMaterialSetItem(bpy.types.Operator):
    bl_idname = "bim.disable_editing_material_set_item"
    bl_label = "Disable Editing Material Set Item"
    bl_options = {"REGISTER", "UNDO"}
    obj: bpy.props.StringProperty()

    def execute(self, context):
        obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
        props = obj.BIMObjectMaterialProperties
        props.active_material_set_item_id = 0
        return {"FINISHED"}


class EditMaterialSetItem(bpy.types.Operator, tool.Ifc.Operator):
    bl_idname = "bim.edit_material_set_item"
    bl_label = "Edit Material Set Item"
    bl_options = {"REGISTER", "UNDO"}
    obj: bpy.props.StringProperty()
    material_set_item: bpy.props.IntProperty()

    def _execute(self, context):
        obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
        self.file = IfcStore.get_file()
        props = obj.BIMObjectMaterialProperties
        mprops = context.scene.BIMMaterialProperties
        element = tool.Ifc.get_entity(obj)
        material = ifcopenshell.util.element.get_material(element, should_skip_usage=True)

        attributes = blenderbim.bim.helper.export_attributes(props.material_set_item_attributes)

        if material.is_a("IfcMaterialConstituentSet"):
            ifcopenshell.api.run(
                "material.edit_constituent",
                self.file,
                **{
                    "constituent": self.file.by_id(self.material_set_item),
                    "attributes": attributes,
                    "material": self.file.by_id(int(obj.BIMObjectMaterialProperties.material_set_item_material)),
                },
            )
        elif material.is_a("IfcMaterialLayerSet"):
            ifcopenshell.api.run(
                "material.edit_layer",
                self.file,
                **{
                    "layer": self.file.by_id(self.material_set_item),
                    "attributes": attributes,
                    "material": self.file.by_id(int(obj.BIMObjectMaterialProperties.material_set_item_material)),
                },
            )
        elif material.is_a("IfcMaterialProfileSet"):
            profile_def = None
            if mprops.profiles:
                profile_def = tool.Ifc.get().by_id(int(mprops.profiles))

            ifcopenshell.api.run(
                "material.edit_profile",
                self.file,
                profile=self.file.by_id(self.material_set_item),
                attributes=attributes,
                profile_def=profile_def,
                material=self.file.by_id(int(obj.BIMObjectMaterialProperties.material_set_item_material)),
            )
        else:
            pass

        bpy.ops.bim.disable_editing_material_set_item(obj=obj.name)


class CopyMaterial(bpy.types.Operator, tool.Ifc.Operator):
    bl_idname = "bim.copy_material"
    bl_label = "Copy Material"
    bl_options = {"REGISTER", "UNDO"}

    def _execute(self, context):
        self.file = IfcStore.get_file()
        material = ifcopenshell.util.element.get_material(
            self.file.by_id(context.active_object.BIMObjectProperties.ifc_definition_id)
        )
        for obj in tool.Blender.get_selected_objects():
            if obj == context.active_object:
                continue
            if not obj.BIMObjectProperties.ifc_definition_id:
                continue
            ifcopenshell.api.run(
                "material.copy_material",
                self.file,
                **{
                    "material": material,
                    "element": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id),
                },
            )
            self.set_default_material(obj, material)

    def set_default_material(self, obj, material):
        object_material_ids = [
            om.BIMObjectProperties.ifc_definition_id
            for om in obj.data.materials
            if om is not None and om.BIMObjectProperties.ifc_definition_id
        ]

        if material.id() in object_material_ids:
            return
        obj.data.materials.append(IfcStore.get_element(material.id()))


class ExpandMaterialCategory(bpy.types.Operator):
    bl_idname = "bim.expand_material_category"
    bl_label = "Expand Material Category"
    bl_options = {"REGISTER", "UNDO"}
    category: bpy.props.StringProperty()

    def execute(self, context):
        props = context.scene.BIMMaterialProperties
        for category in [c for c in props.materials if c.is_category and c.name == self.category]:
            category.is_expanded = True
        core.load_materials(tool.Material, props.material_type)
        return {"FINISHED"}


class ContractMaterialCategory(bpy.types.Operator):
    bl_idname = "bim.contract_material_category"
    bl_label = "Contract Material Category"
    bl_options = {"REGISTER", "UNDO"}
    category: bpy.props.StringProperty()

    def execute(self, context):
        props = context.scene.BIMMaterialProperties
        for category in [c for c in props.materials if c.is_category and c.name == self.category]:
            category.is_expanded = False
        core.load_materials(tool.Material, props.material_type)
        return {"FINISHED"}
