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

# ***************************************************************************
# *   Copyright (c) 2014 Yorik van Havre <yorik@uncreated.net>              *
# *   Copyright (c) 2020 Eliud Cabrera Castillo <e.cabrera-castillo@tum.de> *
# *   Copyright (c) 2025 FreeCAD Project Association                        *
# *                                                                         *
# *   This file is part of the FreeCAD CAx development system.              *
# *                                                                         *
# *   This program is free software; you can redistribute it and/or modify  *
# *   it under the terms of the GNU Lesser General Public License (LGPL)    *
# *   as published by the Free Software Foundation; either version 2 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 Library 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                                                                   *
# *                                                                         *
# ***************************************************************************
"""Provides GUI tools to create Layer objects."""
## @package gui_layers
# \ingroup draftguitools
# \brief Provides GUI tools to create Layer objects.

## \addtogroup draftguitools
# @{
from PySide.QtCore import QT_TRANSLATE_NOOP

import os
import FreeCAD as App
import FreeCADGui as Gui
import Draft_rc
from draftguitools import gui_base
from draftmake import make_layer
from draftobjects import layer
from draftutils import params
from draftutils import utils
from draftutils.translate import translate

# The module is used to prevent complaints from code checkers (flake8)
bool(Draft_rc.__name__)


def getColorIcon(color):
    "returns a QtGui.QIcon from a color 3-float tuple"

    from PySide import QtCore, QtGui

    c = QtGui.QColor(int(color[0] * 255), int(color[1] * 255), int(color[2] * 255))
    im = QtGui.QImage(48, 48, QtGui.QImage.Format_ARGB32)
    im.fill(c)
    px = QtGui.QPixmap.fromImage(im)
    return QtGui.QIcon(px)


class Layer(gui_base.GuiCommandSimplest):
    """GuiCommand to create a Layer object in the document."""

    def __init__(self):
        super().__init__(name="Draft_Layer")

    def GetResources(self):
        """Set icon, menu and tooltip."""
        return {
            "Pixmap": "Draft_Layer",
            "MenuText": QT_TRANSLATE_NOOP("Draft_Layer", "New Layer"),
            "ToolTip": QT_TRANSLATE_NOOP(
                "Draft_Layer",
                "Adds a layer to the document.\nObjects added to this layer can share the same visual properties.",
            ),
        }

    def Activated(self):
        """Execute when the command is called.

        It calls the `finish(False)` method of the active Draft command.
        """
        super().Activated()

        self.doc.openTransaction(translate("draft", "Create layer"))
        Gui.addModule("Draft")
        Gui.doCommand(
            "layer = Draft.make_layer(name=None, line_color=None, shape_color=None, line_width=None, draw_style=None, transparency=None)"
        )
        Gui.doCommand("FreeCAD.ActiveDocument.recompute()")
        self.doc.commitTransaction()


class AddToLayer(gui_base.GuiCommandNeedsSelection):
    """GuiCommand for the Draft_AddToLayer tool."""

    def __init__(self):
        super().__init__(name="Draft_AddToLayer")

    def GetResources(self):
        """Set icon, menu and tooltip."""
        return {
            "Pixmap": "Draft_AddToLayer",
            "MenuText": QT_TRANSLATE_NOOP("Draft_AddToLayer", "Add to Layer"),
            "ToolTip": QT_TRANSLATE_NOOP(
                "Draft_AddToLayer",
                "Adds selected objects to a layer, or removes them from any layer",
            ),
        }

    def Activated(self):
        """Execute when the command is called."""
        super().Activated()

        if not hasattr(Gui, "draftToolBar"):
            return

        self.ui = Gui.draftToolBar
        objs = [obj for obj in App.ActiveDocument.Objects if utils.get_type(obj) == "Layer"]
        objs.sort(key=lambda obj: obj.Label)
        self.objects = [None] + [None] + objs
        self.labels = (
            [translate("draft", "Remove From Layer")]
            + ["---"]
            + [obj.Label for obj in objs]
            + ["---"]
            + [translate("draft", "Add to New Layer")]
        )
        self.icons = (
            [self.ui.getIcon(":/icons/list-remove.svg")]
            + [None]
            + [obj.ViewObject.Icon for obj in objs]
            + [None]
            + [self.ui.getIcon(":/icons/list-add.svg")]
        )
        self.ui.sourceCmd = self
        self.ui.popupMenu(self.labels, self.icons)

    def proceed(self, option):
        self.ui.sourceCmd = None

        if option == self.labels[0]:
            # "Remove from layer"
            changed = False
            for obj in Gui.Selection.getSelection():
                lyr = layer.get_layer(obj)
                if lyr is not None:
                    if not changed:
                        self.doc.openTransaction(translate("draft", "Remove from layer"))
                        changed = True
                    lyr.Proxy.removeObject(lyr, obj)
            if changed:
                self.doc.commitTransaction()
                self.doc.recompute()
            return

        if option == self.labels[-1]:
            # "Add to new layer..."
            from PySide import QtWidgets

            txt, ok = QtWidgets.QInputDialog.getText(
                None,
                translate("draft", "New Layer"),
                translate("draft", "Layer name"),
                text=translate("draft", "Layer", "Object label"),
            )
            if not ok:
                return
            if not txt:
                return
            self.doc.openTransaction(translate("draft", "Add to new layer"))
            lyr = make_layer.make_layer(
                name=txt,
                line_color=None,
                shape_color=None,
                line_width=None,
                draw_style=None,
                transparency=None,
            )
            for obj in Gui.Selection.getSelection():
                lyr.Proxy.addObject(lyr, obj)
            self.doc.commitTransaction()
            self.doc.recompute()
            return

        # Layer has been selected
        i = self.labels.index(option)
        lyr = self.objects[i]
        self.doc.openTransaction(translate("draft", "Add to layer"))
        for obj in Gui.Selection.getSelection():
            lyr.Proxy.addObject(lyr, obj)
        self.doc.commitTransaction()
        self.doc.recompute()


class LayerManager:
    """GuiCommand that displays a Layers manager dialog"""

    def GetResources(self):

        return {
            "Pixmap": "Draft_LayerManager",
            "MenuText": QT_TRANSLATE_NOOP("Draft_LayerManager", "Manage Layers"),
            "ToolTip": QT_TRANSLATE_NOOP("Draft_LayerManager", "Allows to modify the layers"),
        }

    def IsActive(self):
        """Return True when this command should be available."""
        return bool(App.activeDocument())

    def Activated(self):

        from PySide import QtCore, QtGui, QtWidgets

        # store changes to be committed
        self.deleteList = []

        # create the dialog
        self.dialog = Gui.PySideUic.loadUi(":/ui/dialogLayers.ui")

        # set nice icons
        self.dialog.setWindowIcon(QtGui.QIcon(":/icons/Draft_Layer.svg"))
        self.dialog.buttonNew.setIcon(QtGui.QIcon(":/icons/document-new.svg"))
        self.dialog.buttonDelete.setIcon(QtGui.QIcon(":/icons/delete.svg"))
        self.dialog.buttonSelectAll.setIcon(QtGui.QIcon(":/icons/edit-select-all.svg"))
        self.dialog.buttonToggle.setIcon(QtGui.QIcon(":/icons/dagViewVisible.svg"))
        self.dialog.buttonIsolate.setIcon(QtGui.QIcon(":/icons/Std_ShowSelection.svg"))
        self.dialog.buttonCancel.setIcon(QtGui.QIcon(":/icons/edit_Cancel.svg"))
        self.dialog.buttonOK.setIcon(QtGui.QIcon(":/icons/edit_OK.svg"))

        # restore window geometry from stored state
        w = params.get_param("LayersManagerWidth")
        h = params.get_param("LayersManagerHeight")
        self.dialog.resize(w, h)

        # center the dialog over FreeCAD window
        mw = Gui.getMainWindow()
        self.dialog.move(
            mw.frameGeometry().topLeft() + mw.rect().center() - self.dialog.rect().center()
        )

        # connect signals/slots
        self.dialog.buttonNew.clicked.connect(self.addItem)
        self.dialog.buttonDelete.clicked.connect(self.onDelete)
        self.dialog.buttonSelectAll.clicked.connect(self.dialog.tree.selectAll)
        self.dialog.buttonToggle.clicked.connect(self.onToggle)
        self.dialog.buttonCancel.clicked.connect(self.dialog.reject)
        self.dialog.buttonIsolate.clicked.connect(self.onIsolate)
        self.dialog.buttonOK.clicked.connect(self.accept)
        self.dialog.rejected.connect(self.reject)

        # set the model up
        self.model = QtGui.QStandardItemModel()
        self.dialog.tree.setModel(self.model)
        self.dialog.tree.setUniformRowHeights(True)
        self.dialog.tree.setItemDelegate(Layers_Delegate())
        self.dialog.tree.setItemsExpandable(False)
        self.dialog.tree.setRootIsDecorated(False)  # removes spacing in first column
        self.dialog.tree.setSelectionMode(
            QtWidgets.QTreeView.ExtendedSelection
        )  # allow one to select many

        # fill the tree view
        self.update()

        # rock 'n roll!!!
        self.dialog.exec_()

    def accept(self):
        "when OK button is pressed"

        doc = App.ActiveDocument
        changed = False
        trans_name = translate("draft", "Layers change")

        # delete layers
        for name in self.deleteList:
            if not changed:
                doc.openTransaction(trans_name)
                changed = True
            doc.removeObject(name)

        # apply changes
        for row in range(self.model.rowCount()):

            # get or create layer
            name = self.model.item(row, 1).toolTip()
            obj = None
            if name:
                obj = doc.getObject(name)
            if not obj:
                if not changed:
                    doc.openTransaction(trans_name)
                    changed = True
                obj = make_layer.make_layer(
                    name=None,
                    line_color=None,
                    shape_color=None,
                    line_width=None,
                    draw_style=None,
                    transparency=None,
                )
            vobj = obj.ViewObject

            # visibility
            checked = self.model.item(row, 0).checkState() == QtCore.Qt.Checked
            if checked != vobj.Visibility:
                if not changed:
                    doc.openTransaction(trans_name)
                    changed = True
                vobj.Visibility = checked

            # label
            label = self.model.item(row, 1).text()
            # Setting Label="" is possible in the Property editor but we avoid it here:
            if label and obj.Label != label:
                if not changed:
                    doc.openTransaction(trans_name)
                    changed = True
                obj.Label = label

            # line width
            width = self.model.item(row, 2).data(QtCore.Qt.DisplayRole)
            # Setting LineWidth=0 is possible in the Property editor but we avoid it here:
            if width and vobj.LineWidth != width:
                if not changed:
                    doc.openTransaction(trans_name)
                    changed = True
                vobj.LineWidth = width

            # draw style
            style = self.model.item(row, 3).text()
            if style is not None and vobj.DrawStyle != style:
                if not changed:
                    doc.openTransaction(trans_name)
                    changed = True
                vobj.DrawStyle = style

            # line color
            color = self.model.item(row, 4).data(QtCore.Qt.UserRole)
            if color is not None and vobj.LineColor[:3] != color:
                if not changed:
                    doc.openTransaction(trans_name)
                    changed = True
                vobj.LineColor = color

            # shape color
            color = self.model.item(row, 5).data(QtCore.Qt.UserRole)
            if color is not None and vobj.ShapeColor[:3] != color:
                if not changed:
                    doc.openTransaction(trans_name)
                    changed = True
                vobj.ShapeColor = color

            # transparency
            transparency = self.model.item(row, 6).data(QtCore.Qt.DisplayRole)
            if transparency is not None and vobj.Transparency != transparency:
                if not changed:
                    doc.openTransaction(trans_name)
                    changed = True
                vobj.Transparency = transparency

            # line print color
            color = self.model.item(row, 7).data(QtCore.Qt.UserRole)
            if color is not None and vobj.LinePrintColor[:3] != color:
                if not changed:
                    doc.openTransaction(trans_name)
                    changed = True
                vobj.LinePrintColor = color

        # recompute
        if changed:
            doc.commitTransaction()
            doc.recompute()

        # exit
        self.dialog.reject()

    def reject(self):
        "when Cancel button is pressed or dialog is closed"

        # save dialog size
        params.set_param("LayersManagerWidth", self.dialog.width())
        params.set_param("LayersManagerHeight", self.dialog.height())

        return True

    def update(self):
        "rebuild the model from document contents"

        self.model.clear()

        # set header
        self.model.setHorizontalHeaderLabels(
            [
                translate("Draft", "On"),
                translate("Draft", "Name"),
                translate("Draft", "Line Width"),
                translate("Draft", "Draw Style"),
                translate("Draft", "Line Color"),
                translate("Draft", "Face Color"),
                translate("Draft", "Transparency"),
                translate("Draft", "Line Print Color"),
            ]
        )
        self.dialog.tree.header().setDefaultSectionSize(72)
        self.dialog.tree.setColumnWidth(0, 32)  # on/off column
        self.dialog.tree.setColumnWidth(1, 128)  # name column

        # populate
        objs = [obj for obj in App.ActiveDocument.Objects if utils.get_type(obj) == "Layer"]
        objs.sort(key=lambda o: o.Label)
        for obj in objs:
            self.addItem(obj)

    def addItem(self, obj=None):
        "adds a row to the model"

        from PySide import QtCore, QtGui

        # create row with default values
        onItem = QtGui.QStandardItem()
        onItem.setCheckable(True)
        onItem.setCheckState(QtCore.Qt.Checked)
        nameItem = QtGui.QStandardItem(translate("Draft", "New Layer"))
        widthItem = QtGui.QStandardItem()
        widthItem.setData(params.get_param_view("DefaultShapeLineWidth"), QtCore.Qt.DisplayRole)
        styleItem = QtGui.QStandardItem(utils.DRAW_STYLES[params.get_param("DefaultDrawStyle")])
        lineColorItem = QtGui.QStandardItem()
        lineColorItem.setData(
            utils.get_rgba_tuple(params.get_param_view("DefaultShapeLineColor"))[:3],
            QtCore.Qt.UserRole,
        )
        shapeColorItem = QtGui.QStandardItem()
        shapeColorItem.setData(
            utils.get_rgba_tuple(params.get_param_view("DefaultShapeColor"))[:3], QtCore.Qt.UserRole
        )
        transparencyItem = QtGui.QStandardItem()
        transparencyItem.setData(
            params.get_param_view("DefaultShapeTransparency"), QtCore.Qt.DisplayRole
        )
        linePrintColorItem = QtGui.QStandardItem()
        linePrintColorItem.setData(
            utils.get_rgba_tuple(params.get_param("DefaultPrintColor"))[:3], QtCore.Qt.UserRole
        )

        # populate with object data
        if obj:
            vobj = obj.ViewObject
            onItem.setCheckState(QtCore.Qt.Checked if vobj.Visibility else QtCore.Qt.Unchecked)
            nameItem.setText(obj.Label)
            nameItem.setToolTip(obj.Name)
            widthItem.setData(vobj.LineWidth, QtCore.Qt.DisplayRole)
            styleItem.setText(vobj.DrawStyle)
            lineColorItem.setData(vobj.LineColor[:3], QtCore.Qt.UserRole)
            shapeColorItem.setData(vobj.ShapeColor[:3], QtCore.Qt.UserRole)
            transparencyItem.setData(vobj.Transparency, QtCore.Qt.DisplayRole)
            if hasattr(vobj, "LinePrintColor"):
                linePrintColorItem.setData(vobj.LinePrintColor[:3], QtCore.Qt.UserRole)
        lineColorItem.setIcon(getColorIcon(lineColorItem.data(QtCore.Qt.UserRole)))
        shapeColorItem.setIcon(getColorIcon(shapeColorItem.data(QtCore.Qt.UserRole)))
        linePrintColorItem.setIcon(getColorIcon(linePrintColorItem.data(QtCore.Qt.UserRole)))

        # append row
        self.model.appendRow(
            [
                onItem,
                nameItem,
                widthItem,
                styleItem,
                lineColorItem,
                shapeColorItem,
                transparencyItem,
                linePrintColorItem,
            ]
        )

    def onDelete(self):
        "delete selected rows"

        rows = []
        for index in self.dialog.tree.selectedIndexes():
            if not index.row() in rows:
                rows.append(index.row())

            # append layer name to the delete list
            if index.column() == 1:
                name = self.model.itemFromIndex(index).toolTip()
                if name:
                    if not name in self.deleteList:
                        self.deleteList.append(name)

        # delete rows starting from the lowest, to not alter row indexes while deleting
        rows.sort()
        rows.reverse()
        for row in rows:
            self.model.takeRow(row)

    def onToggle(self):
        "toggle selected layers on/off"

        from PySide import QtCore

        state = None
        for index in self.dialog.tree.selectedIndexes():
            if index.column() == 0:
                # get state from first selected row
                if state is None:
                    if self.model.itemFromIndex(index).checkState() == QtCore.Qt.Checked:
                        state = QtCore.Qt.Unchecked
                    else:
                        state = QtCore.Qt.Checked
                self.model.itemFromIndex(index).setCheckState(state)

    def onIsolate(self):
        "isolates the selected layers (turns all the others off"

        from PySide import QtCore

        onrows = []
        for index in self.dialog.tree.selectedIndexes():
            if not index.row() in onrows:
                onrows.append(index.row())
        for row in range(self.model.rowCount()):
            if not row in onrows:
                self.model.item(row, 0).setCheckState(QtCore.Qt.Unchecked)


if App.GuiUp:

    from PySide import QtCore, QtGui, QtWidgets

    class Layers_Delegate(QtWidgets.QStyledItemDelegate):
        "model delegate"

        def __init__(self, parent=None, *args):

            QtWidgets.QStyledItemDelegate.__init__(self, parent, *args)
            # setEditorData() is triggered several times.
            # But we want to show the color dialog only the first time
            self.first = True

        def createEditor(self, parent, option, index):

            if index.column() == 0:  # Layer on/off
                editor = QtWidgets.QCheckBox(parent)
            if index.column() == 1:  # Layer name
                editor = QtWidgets.QLineEdit(parent)
            elif index.column() == 2:  # Line width
                editor = QtWidgets.QSpinBox(parent)
                editor.setMaximum(99)
            elif index.column() == 3:  # Line style
                editor = QtWidgets.QComboBox(parent)
                editor.addItems(utils.DRAW_STYLES)
            elif index.column() == 4:  # Line color
                editor = QtWidgets.QLineEdit(parent)
                self.first = True
            elif index.column() == 5:  # Shape color
                editor = QtWidgets.QLineEdit(parent)
                self.first = True
            elif index.column() == 6:  # Transparency
                editor = QtWidgets.QSpinBox(parent)
                editor.setMaximum(100)
            elif index.column() == 7:  # Line print color
                editor = QtWidgets.QLineEdit(parent)
                self.first = True
            return editor

        def setEditorData(self, editor, index):

            if index.column() == 0:  # Layer on/off
                editor.setChecked(index.data())
            elif index.column() == 1:  # Layer name
                editor.setText(index.data())
            elif index.column() == 2:  # Line width
                editor.setValue(index.data())
            elif index.column() == 3:  # Line style
                editor.setCurrentIndex(utils.DRAW_STYLES.index(index.data()))
            elif index.column() == 4:  # Line color
                editor.setText(str(index.data(QtCore.Qt.UserRole)))
                if self.first:
                    c = index.data(QtCore.Qt.UserRole)
                    color = QtWidgets.QColorDialog.getColor(
                        QtGui.QColor(int(c[0] * 255), int(c[1] * 255), int(c[2] * 255))
                    )
                    editor.setText(str(color.getRgbF()))
                    self.first = False
            elif index.column() == 5:  # Shape color
                editor.setText(str(index.data(QtCore.Qt.UserRole)))
                if self.first:
                    c = index.data(QtCore.Qt.UserRole)
                    color = QtWidgets.QColorDialog.getColor(
                        QtGui.QColor(int(c[0] * 255), int(c[1] * 255), int(c[2] * 255))
                    )
                    editor.setText(str(color.getRgbF()))
                    self.first = False
            elif index.column() == 6:  # Transparency
                editor.setValue(index.data())
            elif index.column() == 7:  # Line print color
                editor.setText(str(index.data(QtCore.Qt.UserRole)))
                if self.first:
                    c = index.data(QtCore.Qt.UserRole)
                    color = QtWidgets.QColorDialog.getColor(
                        QtGui.QColor(int(c[0] * 255), int(c[1] * 255), int(c[2] * 255))
                    )
                    editor.setText(str(color.getRgbF()))
                    self.first = False

        def setModelData(self, editor, model, index):

            if index.column() == 0:  # Layer on/off
                model.setData(index, editor.isChecked())
            elif index.column() == 1:  # Layer name
                model.setData(index, editor.text())
            elif index.column() == 2:  # Line width
                model.setData(index, editor.value())
            elif index.column() == 3:  # Line style
                model.setData(index, utils.DRAW_STYLES[editor.currentIndex()])
            elif index.column() == 4:  # Line color
                model.setData(index, eval(editor.text()), QtCore.Qt.UserRole)
                model.itemFromIndex(index).setIcon(getColorIcon(eval(editor.text())))
            elif index.column() == 5:  # Shape color
                model.setData(index, eval(editor.text()), QtCore.Qt.UserRole)
                model.itemFromIndex(index).setIcon(getColorIcon(eval(editor.text())))
            elif index.column() == 6:  # Transparency
                model.setData(index, editor.value())
            elif index.column() == 7:  # Line prin color
                model.setData(index, eval(editor.text()), QtCore.Qt.UserRole)
                model.itemFromIndex(index).setIcon(getColorIcon(eval(editor.text())))


Gui.addCommand("Draft_Layer", Layer())
Gui.addCommand("Draft_AddToLayer", AddToLayer())
Gui.addCommand("Draft_LayerManager", LayerManager())

## @}
