# ***************************************************************************
# *                                                                         *
# *   Copyright (c) 2022 Yorik van Havre <yorik@uncreated.net>              *
# *                                                                         *
# *   This program is free software; you can redistribute it and/or modify  *
# *   it under the terms of the GNU General Public License (GPL)            *
# *   as published by the Free Software Foundation; either version 3 of     *
# *   the License, or (at your option) any later version.                   *
# *   for detail see the LICENCE text file.                                 *
# *                                                                         *
# *   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 Library General Public     *
# *   License along with this program; if not, write to the Free Software   *
# *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  *
# *   USA                                                                   *
# *                                                                         *
# ***************************************************************************

"""This module contains IFC object definitions"""


class ifc_object:
    """Base class for all IFC-based objects"""

    def __init__(self, otype=None):
        self.cached = True  # this marks that the object is freshly created and its shape should be taken from cache
        self.virgin_placement = True  # this allows to set the initial placement without triggering any placement change
        if otype:
            self.Type = (
                otype[0].upper() + otype[1:]
            )  # capitalize to match Draft standard
        else:
            self.Type = "IfcObject"

    def onBeforeChange(self, obj, prop):
        if prop == "Schema":
            self.old_schema = obj.Schema
        elif prop == "Placement":
            self.old_placement = obj.Placement

    def onChanged(self, obj, prop):
        # link class property to its hidder IfcClass counterpart
        if prop == "IfcClass" and hasattr(obj, "Class") and obj.Class != obj.IfcClass:
            obj.Class = obj.IfcClass
            self.rebuild_classlist(obj, setprops=True)
        elif prop == "Class" and hasattr(obj, "IfcClass") and obj.Class != obj.IfcClass:
            obj.IfcClass = obj.Class
            self.rebuild_classlist(obj, setprops=True)
        elif prop == "Schema":
            self.edit_schema(obj, obj.Schema)
        elif prop == "Group":
            self.edit_group(obj)
        elif obj.getGroupOfProperty(prop) == "IFC":
            if prop not in ["StepId"]:
                self.edit_attribute(obj, prop)
        elif prop == "Label":
            self.edit_attribute(obj, "Name", obj.Label)
        elif prop == "Placement":
            if getattr(self, "virgin_placement", False):
                self.virgin_placement = False
            elif obj.Placement != getattr(self, "old_placement", None):
                # print("placement changed for",obj.Label,"to",obj.Placement)
                self.edit_placement(obj)
        elif prop == "Modified":
            if obj.ViewObject:
                obj.ViewObject.signalChangeIcon()
        elif obj.getGroupOfProperty(prop) == "Geometry":
            self.edit_geometry(obj, prop)
        elif obj.getGroupOfProperty(prop) not in ["Base", "IFC", "", "Geometry"]:
            # Treat all property groups outside the default ones as Psets
            # print("DEBUG: editinog pset prop",prop)
            self.edit_pset(obj, prop)

    def onDocumentRestored(self, obj):
        self.rebuild_classlist(obj)
        if hasattr(obj, "IfcFilePath"):
            # once we have loaded the project, recalculate child coin nodes
            from PySide import QtCore  # lazy loading

            if obj.OutListRecursive:
                for child in obj.OutListRecursive:
                    if getattr(child, "ShapeMode", None) == "Coin":
                        child.Proxy.cached = True
                        child.touch()
            else:
                obj.Proxy.cached = True
                QtCore.QTimer.singleShot(100, obj.touch)
            QtCore.QTimer.singleShot(100, obj.Document.recompute)
            QtCore.QTimer.singleShot(100, self.fit_all)

    def fit_all(self):
        """Fits the view"""

        import FreeCAD

        if FreeCAD.GuiUp:
            import FreeCADGui

            FreeCADGui.SendMsgToActiveView("ViewFit")

    def rebuild_classlist(self, obj, setprops=False):
        """rebuilds the list of Class enum property according to current class"""

        from nativeifc import ifc_tools  # lazy import

        obj.Class = [obj.IfcClass]
        obj.Class = ifc_tools.get_ifc_classes(obj, obj.IfcClass)
        obj.Class = obj.IfcClass
        if setprops:
            ifc_tools.remove_unused_properties(obj)
            ifc_tools.add_properties(obj)

    def __getstate__(self):
        return getattr(self, "Type", None)

    def __setstate__(self, state):
        self.loads(state)

    def dumps(self):
        return getattr(self, "Type", None)

    def loads(self, state):
        if state and hasattr(state, "Type"):
            self.Type = state
        return None

    def execute(self, obj):
        from nativeifc import ifc_generator  # lazy import

        if obj.isDerivedFrom("Part::Feature"):
            cached = getattr(self, "cached", False)
            ifc_generator.generate_geometry(obj, cached=cached)
            self.cached = False
            self.rebuild_classlist(obj)

    def addObject(self, obj, child):
        if child not in obj.Group:
            g = obj.Group
            g.append(child)
            obj.Group = g

    def removeObject(self, obj, child):
        if child in obj.Group:
            g = obj.Group
            g.remove(child)
            obj.Group = g

    def edit_attribute(self, obj, attribute, value=None):
        """Edits an attribute of an underlying IFC object"""

        from nativeifc import ifc_tools  # lazy import

        if not value:
            value = obj.getPropertyByName(attribute)
        ifcfile = ifc_tools.get_ifcfile(obj)
        elt = ifc_tools.get_ifc_element(obj, ifcfile)
        if elt:
            result = ifc_tools.set_attribute(ifcfile, elt, attribute, value)
            if result:
                if hasattr(result, "id") and (result.id() != obj.StepId):
                    obj.StepId = result.id()

    def edit_geometry(self, obj, prop):
        """Edits a geometry property of an object"""

        from nativeifc import ifc_geometry  # lazy loading
        from nativeifc import ifc_tools  # lazy import

        result = ifc_geometry.set_geom_property(obj, prop)
        if result:
            obj.touch()

    def edit_schema(self, obj, schema):
        """Changes the schema of an IFC document"""

        from nativeifc import ifc_tools  # lazy import

        ifcfile = ifc_tools.get_ifcfile(obj)
        if not ifcfile:
            return
        if not getattr(self, "old_schema", None):
            return
        if schema != ifcfile.wrapped_data.schema_name():
            # set obj.Proxy.silent = True to disable the schema change warning
            if obj.ViewObject and not getattr(self, "silent", False):
                if not obj.ViewObject.Proxy.schema_warning():
                    return
            ifcfile, migration_table = ifc_tools.migrate_schema(ifcfile, schema)
            self.ifcfile = ifcfile
            for old_id, new_id in migration_table.items():
                child = [
                    o
                    for o in obj.OutListRecursive
                    if getattr(o, "StepId", None) == old_id
                ]
                if len(child) == 1:
                    child[0].StepId = new_id

    def edit_placement(self, obj):
        """Syncs the internal IFC placement"""

        from nativeifc import ifc_tools  # lazy import

        ifc_tools.set_placement(obj)

    def edit_pset(self, obj, prop):
        """Edits a Pset value"""

        from nativeifc import ifc_psets  # lazy import

        ifc_psets.edit_pset(obj, prop)

    def edit_group(self, obj):
        """Edits the children list"""

        from nativeifc import ifc_tools  # lazy import
        from nativeifc import ifc_layers

        if obj.Class in [
            "IfcPresentationLayerAssignment",
            "IfcPresentationLayerWithStyle",
        ]:
            ifcfile = ifc_tools.get_ifcfile(obj)
            if not ifcfile:
                return
            newlist = []
            for child in obj.Group:
                if (
                    not getattr(child, "StepId", None)
                    or ifc_tools.get_ifcfile(child) != ifcfile
                ):
                    print(
                        "DEBUG: Not an IFC object. Removing",
                        child.Label,
                        "from layer",
                        obj.Label,
                    )
                else:
                    # print("DEBUG: adding", child.Label, "to layer", obj.Label)
                    newlist.append(child)
                    ifc_layers.add_to_layer(child, obj)
            if newlist != obj.Group:
                obj.Group = newlist


class document_object:
    """Holder for the document's IFC objects"""

    def __init__(self):
        pass
