# SPDX-License-Identifier: LGPL-2.1-or-later

# ***************************************************************************
# *                                                                         *
# *   Copyright (c) 2024 Yorik van Havre <yorik@uncreated.net>              *
# *                                                                         *
# *   This file is part of FreeCAD.                                         *
# *                                                                         *
# *   FreeCAD is free software: you can redistribute it and/or modify it    *
# *   under the terms of the GNU Lesser General Public License as           *
# *   published by the Free Software Foundation, either version 2.1 of the  *
# *   License, or (at your option) any later version.                       *
# *                                                                         *
# *   FreeCAD 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      *
# *   Lesser General Public License for more details.                       *
# *                                                                         *
# *   You should have received a copy of the GNU Lesser General Public      *
# *   License along with FreeCAD. If not, see                               *
# *   <https://www.gnu.org/licenses/>.                                      *
# *                                                                         *
# ***************************************************************************

"""Misc Arch util commands"""

import FreeCAD
import FreeCADGui

QT_TRANSLATE_NOOP = FreeCAD.Qt.QT_TRANSLATE_NOOP
translate = FreeCAD.Qt.translate

PARAMS = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/BIM")


class Arch_Add:
    "the Arch Add command definition"

    def GetResources(self):
        return {
            "Pixmap": "Arch_Add",
            "MenuText": QT_TRANSLATE_NOOP("Arch_Add", "Add Component"),
            "ToolTip": QT_TRANSLATE_NOOP(
                "Arch_Add", "Adds the selected components to the active object"
            ),
        }

    def IsActive(self):
        v = hasattr(FreeCADGui.getMainWindow().getActiveWindow(), "getSceneGraph")
        return v and len(FreeCADGui.Selection.getSelection()) > 1

    def Activated(self):
        import Draft
        import Arch

        sel = FreeCADGui.Selection.getSelection()
        if Draft.getType(sel[-1]) == "Space":
            FreeCAD.ActiveDocument.openTransaction(translate("Arch", "Add space boundary"))
            FreeCADGui.addModule("Arch")
            FreeCADGui.doCommand(
                "Arch.addSpaceBoundaries( FreeCAD.ActiveDocument."
                + sel[-1].Name
                + ", FreeCADGui.Selection.getSelectionEx() )"
            )
        elif Draft.getType(sel[-1]).startswith("Ifc"):
            FreeCADGui.addModule("nativeifc.ifc_tools")
            for s in sel[:-1]:
                FreeCADGui.doCommand(
                    "nativeifc.ifc_tools.aggregate(FreeCAD.ActiveDocument."
                    + s.Name
                    + ",FreeCAD.ActiveDocument."
                    + sel[-1].Name
                    + ")"
                )
        else:
            FreeCAD.ActiveDocument.openTransaction(translate("Arch", "Grouping"))
            if not Arch.mergeCells(sel):
                host = sel.pop()
                ss = "["
                for o in sel:
                    if len(ss) > 1:
                        ss += ","
                    ss += "FreeCAD.ActiveDocument." + o.Name
                ss += "]"
                FreeCADGui.addModule("Arch")
                FreeCADGui.doCommand(
                    "Arch.addComponents(" + ss + ",FreeCAD.ActiveDocument." + host.Name + ")"
                )
        FreeCAD.ActiveDocument.commitTransaction()
        FreeCAD.ActiveDocument.recompute()


class Arch_Remove:
    "the Arch Add command definition"

    def GetResources(self):
        return {
            "Pixmap": "Arch_Remove",
            "MenuText": QT_TRANSLATE_NOOP("Arch_Remove", "Remove Component"),
            "ToolTip": QT_TRANSLATE_NOOP(
                "Arch_Remove",
                "Removes the selected components from their parents, or creates a hole in a component",
            ),
        }

    def IsActive(self):
        v = hasattr(FreeCADGui.getMainWindow().getActiveWindow(), "getSceneGraph")
        return v and bool(FreeCADGui.Selection.getSelection())

    def Activated(self):
        import Draft

        sel = FreeCADGui.Selection.getSelection()
        if Draft.getType(sel[-1]) == "Space":
            FreeCAD.ActiveDocument.openTransaction(translate("Arch", "Remove space boundary"))
            FreeCADGui.addModule("Arch")
            FreeCADGui.doCommand(
                "Arch.removeSpaceBoundaries( FreeCAD.ActiveDocument."
                + sel[-1].Name
                + ", FreeCADGui.Selection.getSelection() )"
            )
        elif Draft.getType(sel[-1]).startswith("Ifc"):
            FreeCADGui.addModule("nativeifc.ifc_tools")
            for s in sel[:-1]:
                FreeCADGui.doCommand(
                    "nativeifc.ifc_tools.aggregate(FreeCAD.ActiveDocument."
                    + s.Name
                    + ",FreeCAD.ActiveDocument."
                    + sel[-1].Name
                    + ",mode='opening')"
                )
        else:
            FreeCAD.ActiveDocument.openTransaction(translate("Arch", "Ungrouping"))
            if len(sel) > 1:
                host = sel.pop()
                ss = "["
                for o in sel:
                    if len(ss) > 1:
                        ss += ","
                    ss += "FreeCAD.ActiveDocument." + o.Name
                ss += "]"
                FreeCADGui.addModule("Arch")
                FreeCADGui.doCommand(
                    "Arch.removeComponents(" + ss + ",FreeCAD.ActiveDocument." + host.Name + ")"
                )
            else:
                FreeCADGui.addModule("Arch")
                FreeCADGui.doCommand(
                    "Arch.removeComponents(FreeCAD.ActiveDocument." + sel[0].Name + ")"
                )
        FreeCAD.ActiveDocument.commitTransaction()
        FreeCAD.ActiveDocument.recompute()


class Arch_SplitMesh:
    "the Arch SplitMesh command definition"

    def GetResources(self):
        return {
            "Pixmap": "Arch_SplitMesh",
            "MenuText": QT_TRANSLATE_NOOP("Arch_SplitMesh", "Split Mesh"),
            "ToolTip": QT_TRANSLATE_NOOP(
                "Arch_SplitMesh", "Splits selected meshes into independent components"
            ),
        }

    def IsActive(self):
        v = hasattr(FreeCADGui.getMainWindow().getActiveWindow(), "getSceneGraph")
        return v and bool(FreeCADGui.Selection.getSelection())

    def Activated(self):
        import Arch

        if FreeCADGui.Selection.getSelection():
            sel = FreeCADGui.Selection.getSelection()
            FreeCAD.ActiveDocument.openTransaction(translate("Arch", "Split Mesh"))
            for obj in sel:
                n = obj.Name
                nobjs = Arch.splitMesh(obj)
                if len(nobjs) > 1:
                    g = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", n)
                    for o in nobjs:
                        g.addObject(o)
            FreeCAD.ActiveDocument.commitTransaction()
            FreeCAD.ActiveDocument.recompute()


class Arch_MeshToShape:
    "the Arch MeshToShape command definition"

    def GetResources(self):
        return {
            "Pixmap": "Arch_MeshToShape",
            "MenuText": QT_TRANSLATE_NOOP("Arch_MeshToShape", "Mesh to Shape"),
            "ToolTip": QT_TRANSLATE_NOOP(
                "Arch_MeshToShape", "Turns selected meshes into Part shape objects"
            ),
        }

    def IsActive(self):
        v = hasattr(FreeCADGui.getMainWindow().getActiveWindow(), "getSceneGraph")
        return v and bool(FreeCADGui.Selection.getSelection())

    def Activated(self):
        import Arch
        from draftutils import params

        if FreeCADGui.Selection.getSelection():
            f = FreeCADGui.Selection.getSelection()[0]
            g = None
            if f.isDerivedFrom("App::DocumentObjectGroup"):
                g = f
                FreeCADGui.Selection.clearSelection()
                for o in f.OutList:
                    FreeCADGui.Selection.addSelection(o)
            else:
                if f.InList:
                    if f.InList[0].isDerivedFrom("App::DocumentObjectGroup"):
                        g = f.InList[0]
            fast = params.get_param_arch("ConversionFast")
            tol = params.get_param_arch("ConversionTolerance")
            flat = params.get_param_arch("ConversionFlat")
            cut = params.get_param_arch("ConversionCut")
            FreeCAD.ActiveDocument.openTransaction(translate("Arch", "Mesh to shape"))
            for obj in FreeCADGui.Selection.getSelection():
                newobj = Arch.meshToShape(obj, True, fast, tol, flat, cut)
                if g and newobj:
                    g.addObject(newobj)
            FreeCAD.ActiveDocument.commitTransaction()


class Arch_SelectNonSolidMeshes:
    "the Arch SelectNonSolidMeshes command definition"

    def GetResources(self):
        return {
            "Pixmap": "Arch_SelectNonManifold.svg",
            "MenuText": QT_TRANSLATE_NOOP(
                "Arch_SelectNonSolidMeshes", "Select Non-Manifold Meshes"
            ),
            "ToolTip": QT_TRANSLATE_NOOP(
                "Arch_SelectNonSolidMeshes",
                "Selects all non-manifold meshes from the document or from the selected groups",
            ),
        }

    def IsActive(self):
        v = hasattr(FreeCADGui.getMainWindow().getActiveWindow(), "getSceneGraph")
        return v

    def Activated(self):
        msel = []
        if FreeCADGui.Selection.getSelection():
            for o in FreeCADGui.Selection.getSelection():
                if o.isDerivedFrom("App::DocumentObjectGroup"):
                    msel.extend(o.OutList)
        if not msel:
            msel = FreeCAD.ActiveDocument.Objects
        sel = []
        for o in msel:
            if o.isDerivedFrom("Mesh::Feature"):
                if (not o.Mesh.isSolid()) or o.Mesh.hasNonManifolds():
                    sel.append(o)
        if sel:
            FreeCADGui.Selection.clearSelection()
            for o in sel:
                FreeCADGui.Selection.addSelection(o)


class Arch_RemoveShape:
    "the Arch RemoveShape command definition"

    def GetResources(self):
        return {
            "Pixmap": "Arch_RemoveShape",
            "MenuText": QT_TRANSLATE_NOOP("Arch_RemoveShape", "Remove Shape From BIM"),
            "ToolTip": QT_TRANSLATE_NOOP(
                "Arch_RemoveShape", "Removes cubic shapes from BIM components"
            ),
        }

    def IsActive(self):
        v = hasattr(FreeCADGui.getMainWindow().getActiveWindow(), "getSceneGraph")
        return v and bool(FreeCADGui.Selection.getSelection())

    def Activated(self):
        import Arch

        sel = FreeCADGui.Selection.getSelection()
        Arch.removeShape(sel)


class Arch_CloseHoles:
    "the Arch CloseHoles command definition"

    def GetResources(self):
        return {
            "Pixmap": "Arch_CloseHoles",
            "MenuText": QT_TRANSLATE_NOOP("Arch_CloseHoles", "Close Holes"),
            "ToolTip": QT_TRANSLATE_NOOP(
                "Arch_CloseHoles", "Closes holes in open shapes, turning them into solids"
            ),
        }

    def IsActive(self):
        v = hasattr(FreeCADGui.getMainWindow().getActiveWindow(), "getSceneGraph")
        return v and bool(FreeCADGui.Selection.getSelection())

    def Activated(self):
        import Arch

        for o in FreeCADGui.Selection.getSelection():
            s = Arch.closeHole(o.Shape)
            if s:
                o.Shape = s


class Arch_Check:
    "the Arch Check command definition"

    def GetResources(self):
        return {
            "Pixmap": "Arch_Check",
            "MenuText": QT_TRANSLATE_NOOP("Arch_Check", "Check"),
            "ToolTip": QT_TRANSLATE_NOOP("Arch_Check", "Checks the selected objects for problems"),
        }

    def IsActive(self):
        v = hasattr(FreeCADGui.getMainWindow().getActiveWindow(), "getSceneGraph")
        return v and bool(FreeCADGui.Selection.getSelection())

    def Activated(self):
        import Arch

        result = Arch.check(FreeCADGui.Selection.getSelection())
        if not result:
            FreeCAD.Console.PrintMessage(str(translate("Arch", "No problems found!")))
        else:
            FreeCADGui.Selection.clearSelection()
            for i in result:
                FreeCAD.Console.PrintWarning(
                    "Object " + i[0].Name + " (" + i[0].Label + ") " + i[1]
                )
                FreeCADGui.Selection.addSelection(i[0])


class Arch_Survey:
    "the Arch Survey command definition"

    def GetResources(self):
        return {
            "Pixmap": "Arch_Survey",
            "MenuText": QT_TRANSLATE_NOOP("Arch_Survey", "Survey"),
            "ToolTip": QT_TRANSLATE_NOOP("Arch_Survey", "Starts survey"),
        }

    def IsActive(self):
        v = hasattr(FreeCADGui.getMainWindow().getActiveWindow(), "getSceneGraph")
        return v

    def Activated(self):
        FreeCADGui.addModule("Arch")
        FreeCADGui.doCommandGui("Arch.survey()")


class Arch_ToggleIfcBrepFlag:
    "the Toggle IFC B-rep flag command definition"

    def GetResources(self):
        return {
            "Pixmap": "Arch_ToggleIfcBrepFlag",
            "MenuText": QT_TRANSLATE_NOOP("Arch_ToggleIfcBrepFlag", "Toggle IFC B-Rep Flag"),
            "ToolTip": QT_TRANSLATE_NOOP(
                "Arch_ToggleIfcBrepFlag", "Forces an object to be exported as B-rep or not"
            ),
        }

    def IsActive(self):
        v = hasattr(FreeCADGui.getMainWindow().getActiveWindow(), "getSceneGraph")
        return v and bool(FreeCADGui.Selection.getSelection())

    def Activated(self):
        import Arch

        for o in FreeCADGui.Selection.getSelection():
            Arch.toggleIfcBrepFlag(o)


class Arch_Component:
    "the Arch Component command definition"

    def GetResources(self):
        return {
            "Pixmap": "Arch_Component",
            "MenuText": QT_TRANSLATE_NOOP("Arch_Component", "Component"),
            "Accel": "C, M",
            "ToolTip": QT_TRANSLATE_NOOP(
                "Arch_Component", "Creates an undefined architectural component"
            ),
        }

    def IsActive(self):
        v = hasattr(FreeCADGui.getMainWindow().getActiveWindow(), "getSceneGraph")
        return v

    def Activated(self):
        sel = FreeCADGui.Selection.getSelection()
        if sel:
            FreeCAD.ActiveDocument.openTransaction(translate("Arch", "Create Component"))
            FreeCADGui.addModule("Arch")
            FreeCADGui.addModule("Draft")
            FreeCADGui.Control.closeDialog()
            for o in sel:
                FreeCADGui.doCommand(
                    "obj = Arch.makeComponent(FreeCAD.ActiveDocument." + o.Name + ")"
                )
                FreeCADGui.doCommand("Draft.autogroup(obj)")
            FreeCAD.ActiveDocument.commitTransaction()
            FreeCAD.ActiveDocument.recompute()


class Arch_CloneComponent:
    "the Arch Clone Component command definition"

    def GetResources(self):
        return {
            "Pixmap": "Arch_Component_Clone",
            "MenuText": QT_TRANSLATE_NOOP("Arch_CloneComponent", "Clone Component"),
            "Accel": "C, C",
            "ToolTip": QT_TRANSLATE_NOOP(
                "Arch_CloneComponent", "Clones an object as an undefined architectural component"
            ),
        }

    def IsActive(self):
        v = hasattr(FreeCADGui.getMainWindow().getActiveWindow(), "getSceneGraph")
        return v

    def Activated(self):
        sel = FreeCADGui.Selection.getSelection()
        if sel:
            FreeCAD.ActiveDocument.openTransaction(translate("Arch", "Create Component"))
            FreeCADGui.addModule("Arch")
            FreeCADGui.addModule("Draft")
            FreeCADGui.Control.closeDialog()
            for o in sel:
                FreeCADGui.doCommand(
                    "obj = Arch.cloneComponent(FreeCAD.ActiveDocument." + o.Name + ")"
                )
                FreeCADGui.doCommand("Draft.autogroup(obj)")
            FreeCAD.ActiveDocument.commitTransaction()
            FreeCAD.ActiveDocument.recompute()


class Arch_IfcSpreadsheet:
    "the Arch Schedule command definition"

    def GetResources(self):
        return {
            "Pixmap": "Arch_Schedule",
            "MenuText": QT_TRANSLATE_NOOP("Arch_IfcSpreadsheet", "New IFC Spreadsheet"),
            "Accel": "I, S",
            "ToolTip": QT_TRANSLATE_NOOP(
                "Arch_IfcSpreadsheet", "Creates a spreadsheet to store IFC properties of an object"
            ),
        }

    def IsActive(self):
        v = hasattr(FreeCADGui.getMainWindow().getActiveWindow(), "getSceneGraph")
        return v

    def Activated(self):
        sel = FreeCADGui.Selection.getSelection()
        FreeCAD.ActiveDocument.openTransaction(
            translate("Arch", "Create IFC properties spreadsheet")
        )
        FreeCADGui.addModule("Arch")
        FreeCADGui.Control.closeDialog()
        if sel:
            for o in sel:
                FreeCADGui.doCommand(
                    "Arch.makeIfcSpreadsheet(FreeCAD.ActiveDocument." + o.Name + ")"
                )
        else:
            FreeCADGui.doCommand("Arch.makeIfcSpreadsheet()")
        FreeCAD.ActiveDocument.commitTransaction()
        FreeCAD.ActiveDocument.recompute()


class Arch_ToggleSubs:
    "the ToggleSubs command definition"

    def GetResources(self):
        return {
            "Pixmap": "Arch_ToggleSubs",
            "Accel": "Ctrl+Space",
            "MenuText": QT_TRANSLATE_NOOP("Arch_ToggleSubs", "Toggle Subcomponents"),
            "ToolTip": QT_TRANSLATE_NOOP(
                "Arch_ToggleSubs", "Shows or hides the subcomponents of this object"
            ),
        }

    def IsActive(self):
        v = hasattr(FreeCADGui.getMainWindow().getActiveWindow(), "getSceneGraph")
        return v and bool(FreeCADGui.Selection.getSelection())

    def Activated(self):
        import Draft

        mode = None
        for obj in FreeCADGui.Selection.getSelection():
            if hasattr(obj, "Subtractions"):
                for sub in obj.Subtractions:
                    if not (Draft.getType(sub) in ["Window", "Roof"]):
                        if mode is None:
                            # take the first sub as base
                            mode = sub.ViewObject.isVisible()
                        if mode:
                            sub.ViewObject.hide()
                        else:
                            sub.ViewObject.show()


class Arch_MergeWalls:
    """The command definition for the Arch workbench's gui tool, Arch MergeWalls.

    A tool for merging walls.

    Join two or more walls by using the ArchWall.joinWalls() function.

    Find documentation on the end user usage of Arch Wall here:
    https://wiki.freecad.org/Arch_MergeWalls
    """

    def GetResources(self):
        """Returns a dictionary with the visual aspects of the Arch MergeWalls tool."""

        return {
            "Pixmap": "Arch_MergeWalls",
            "MenuText": QT_TRANSLATE_NOOP("Arch_MergeWalls", "Merge Walls"),
            "ToolTip": QT_TRANSLATE_NOOP(
                "Arch_MergeWalls", "Merges the selected walls, if possible"
            ),
        }

    def IsActive(self):
        """Determines whether or not the Arch MergeWalls tool is active.

        Inactive commands are indicated by a greyed-out icon in the menus and
        toolbars.
        """

        v = hasattr(FreeCADGui.getMainWindow().getActiveWindow(), "getSceneGraph")
        return v and bool(FreeCADGui.Selection.getSelection())

    def Activated(self):
        """Executed when Arch MergeWalls is called.

        Call ArchWall.joinWalls() on walls selected by the user, with the
        delete option enabled. If the user has selected a single wall, check to
        see if the wall has any Additions that are walls. If so, merges these
        additions to the wall, deleting the additions.
        """

        import Draft
        import ArchWall

        walls = FreeCADGui.Selection.getSelection()
        if len(walls) == 1:
            if Draft.getType(walls[0]) == "Wall":
                ostr = "FreeCAD.ActiveDocument." + walls[0].Name
                ok = False
                for o in walls[0].Additions:
                    if Draft.getType(o) == "Wall":
                        ostr += ",FreeCAD.ActiveDocument." + o.Name
                        ok = True
                if ok:
                    FreeCAD.ActiveDocument.openTransaction(translate("Arch", "Merge Walls"))
                    FreeCADGui.addModule("Arch")
                    FreeCADGui.doCommand("Arch.joinWalls([" + ostr + "],delete=True)")
                    FreeCAD.ActiveDocument.commitTransaction()
                    return
                else:
                    FreeCAD.Console.PrintWarning(
                        translate("Arch", "The selected wall contains no subwalls to merge")
                    )
                    return
            else:
                FreeCAD.Console.PrintWarning(translate("Arch", "Select only wall objects"))
                return
        for w in walls:
            if Draft.getType(w) != "Wall":
                FreeCAD.Console.PrintMessage(translate("Arch", "Select only wall objects"))
                return
        if not ArchWall.areSameWallTypes(walls):
            FreeCAD.Console.PrintMessage(
                translate(
                    "Arch",
                    "Walls with different 'Width', 'Height' and 'Align' properties cannot be merged",
                )
            )
            return
        FreeCAD.ActiveDocument.openTransaction(translate("Arch", "Merge Walls"))
        FreeCADGui.addModule("Arch")
        FreeCADGui.doCommand("Arch.joinWalls(FreeCADGui.Selection.getSelection(),delete=True)")
        FreeCAD.ActiveDocument.commitTransaction()


FreeCADGui.addCommand("Arch_Add", Arch_Add())
FreeCADGui.addCommand("Arch_Remove", Arch_Remove())
FreeCADGui.addCommand("Arch_SplitMesh", Arch_SplitMesh())
FreeCADGui.addCommand("Arch_MeshToShape", Arch_MeshToShape())
FreeCADGui.addCommand("Arch_SelectNonSolidMeshes", Arch_SelectNonSolidMeshes())
FreeCADGui.addCommand("Arch_RemoveShape", Arch_RemoveShape())
FreeCADGui.addCommand("Arch_CloseHoles", Arch_CloseHoles())
FreeCADGui.addCommand("Arch_Check", Arch_Check())
FreeCADGui.addCommand("Arch_Survey", Arch_Survey())
FreeCADGui.addCommand("Arch_ToggleIfcBrepFlag", Arch_ToggleIfcBrepFlag())
FreeCADGui.addCommand("Arch_Component", Arch_Component())
FreeCADGui.addCommand("Arch_CloneComponent", Arch_CloneComponent())
FreeCADGui.addCommand("Arch_IfcSpreadsheet", Arch_IfcSpreadsheet())
FreeCADGui.addCommand("Arch_ToggleSubs", Arch_ToggleSubs())
FreeCADGui.addCommand("Arch_MergeWalls", Arch_MergeWalls())
