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

# ***************************************************************************
# *                                                                         *
# *   Copyright (c) 2018 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/>.                                      *
# *                                                                         *
# ***************************************************************************

"""This module contains FreeCAD commands for the BIM workbench"""

import os
import sys

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 BIM_IfcProperties:

    def GetResources(self):
        return {
            "Pixmap": "BIM_IfcProperties",
            "MenuText": QT_TRANSLATE_NOOP("BIM_IfcProperties", "Manage IFC Properties"),
            "ToolTip": QT_TRANSLATE_NOOP(
                "BIM_IfcProperties",
                "Manages the different IFC properties of the BIM objects",
            ),
        }

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

    def Activated(self):

        # only raise the dialog if it is already open
        if getattr(self, "form", None):
            self.form.raise_()
            return

        from PySide import QtGui

        try:
            import ArchIFC

            self.ifcroles = ArchIFC.IfcTypes
        except (ImportError, AttributeError):
            import ArchComponent

            self.ifcroles = ArchComponent.IfcRoles

        # load the form and set the tree model up
        self.form = FreeCADGui.PySideUic.loadUi(":/ui/dialogIfcProperties.ui")
        self.form.setWindowIcon(QtGui.QIcon(":/icons/BIM_IfcProperties.svg"))
        self.form.groupMode.setItemIcon(2, QtGui.QIcon(":/icons/Document.svg"))
        self.model = QtGui.QStandardItemModel()
        self.form.tree.setModel(self.model)
        self.form.tree.setUniformRowHeights(True)

        # restore saved values
        self.form.onlySelected.setChecked(PARAMS.GetInt("IfcPropertiesSelectedState", 0))
        self.form.onlyVisible.setChecked(PARAMS.GetInt("IfcPropertiesVisibleState", 0))
        w = PARAMS.GetInt("BimIfcPropertiesDialogWidth", 1200)
        h = PARAMS.GetInt("BimIfcPropertiesDialogHeight", 608)
        self.form.resize(w, h)

        # build objects list and fill search terms
        self.objectslist, searchterms = self.rebuildObjectsList()
        self.form.searchField.addItems(searchterms)

        # set the properties editor
        try:
            import ArchIFCSchema

            self.ptypes = list(ArchIFCSchema.IfcTypes.keys())
        except (ImportError, AttributeError):
            import ArchComponent

            self.ptypes = ArchComponent.SimplePropertyTypes + ArchComponent.MeasurePropertyTypes
        self.plabels = [
            "".join(map(lambda x: x if x.islower() else " " + x, t[3:]))[1:] for t in self.ptypes
        ]
        self.psetdefs = {}
        psetpath = os.path.join(
            FreeCAD.getResourceDir(), "Mod", "BIM", "Presets", "pset_definitions.csv"
        )
        custompath = os.path.join(FreeCAD.getUserAppDataDir(), "BIM", "CustomPsets.csv")
        self.psetdefs = self.readFromCSV(psetpath)
        self.psetdefs.update(self.readFromCSV(custompath))
        self.psetkeys = [
            "".join(map(lambda x: x if x.islower() else " " + x, t[5:]))[1:]
            for t in self.psetdefs.keys()
        ]
        self.psetkeys.sort()
        self.propmodel = QtGui.QStandardItemModel()
        self.form.treeProperties.setModel(self.propmodel)
        # self.ifcEditor.treeProperties.setDragDropMode(QtGui.QAbstractItemView.InternalMove)
        self.form.treeProperties.setUniformRowHeights(True)
        self.form.treeProperties.setItemDelegate(
            propertiesDelegate(container=self, ptypes=self.ptypes, plabels=self.plabels)
        )
        self.form.labelinfo.setText(
            self.form.labelinfo.text()
            + " "
            + translate("BIM", "Custom property sets can be defined in")
            + " "
            + custompath
        )

        # set combos
        self.form.comboProperty.addItems([translate("BIM", "Add property")] + self.plabels)
        self.form.comboPset.addItems(
            [translate("BIM", "Add property set"), translate("BIM", "New")] + self.psetkeys
        )

        # connect signals
        self.form.tree.selectionModel().selectionChanged.connect(self.updateProperties)
        self.form.groupMode.currentIndexChanged.connect(self.update)
        if hasattr(self.form.onlyVisible, "checkStateChanged"):  # Qt version >= 6.7.0
            self.form.onlyVisible.checkStateChanged.connect(self.update)
            self.form.onlySelected.checkStateChanged.connect(self.onSelected)
            self.form.onlyMatches.checkStateChanged.connect(self.update)
        else:  # Qt version < 6.7.0
            self.form.onlyVisible.stateChanged.connect(self.update)
            self.form.onlySelected.stateChanged.connect(self.onSelected)
            self.form.onlyMatches.stateChanged.connect(self.update)
        self.form.buttonBox.accepted.connect(self.accept)
        self.form.rejected.connect(self.reject)  # also triggered by self.form.buttonBox.rejected
        self.form.searchField.currentIndexChanged.connect(self.update)
        self.form.searchField.editTextChanged.connect(self.update)
        self.form.comboProperty.currentIndexChanged.connect(self.addProperty)
        self.form.comboPset.currentIndexChanged.connect(self.addPset)
        self.form.buttonIFCPropertiesDelete.clicked.connect(self.removeProperty)
        self.form.treeProperties.setSortingEnabled(True)

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

        self.update()
        self.form.show()

    def rebuildObjectsList(self):

        # build objects list and fill search terms
        objectslist = {}
        searchterms = [""]
        if self.form.onlySelected.isChecked():
            objects = FreeCADGui.Selection.getSelection()
        else:
            objects = FreeCAD.ActiveDocument.Objects
        for obj in objects:
            role = self.getRole(obj)
            if role:
                if hasattr(obj, "IfcProperties") and isinstance(obj.IfcProperties, dict):
                    props = obj.IfcProperties
                elif hasattr(obj, "IfcClass"):
                    props = self.getNativeIfcProperties(obj)
                else:
                    props = {}
                objectslist[obj.Name] = [role, props]
                for key, val in props.items():
                    val = val.split(";;")
                    if ";;" in key:
                        # 0.19 format
                        # pset;;pname = ptype;;pvalue
                        key = key.split(";;")
                        val = [key[1]] + val
                        key = key[0]
                    if not key in searchterms:
                        searchterms.append(key)
                    if len(val) == 3:
                        if not val[0] in searchterms:
                            searchterms.append(val[0])
        return objectslist, searchterms

    def update(self, index=None):
        "updates the tree widgets in all tabs"

        self.model.clear()
        self.model.setHorizontalHeaderLabels(
            [
                translate("BIM", "Label"),
                translate("BIM", "IFC type"),
                translate("BIM", "Search results"),
            ]
        )
        # self.form.tree.header().setResizeMode(QtGui.QHeaderView.Stretch)
        # self.form.tree.resizeColumnsToContents()
        if self.form.groupMode.currentIndex() == 1:
            # group by type
            self.updateByType()
        elif self.form.groupMode.currentIndex() == 2:
            # group by model structure
            self.updateByTree()
        else:
            # group alphabetically
            self.updateDefault()
        self.model.sort(0)
        self.form.tree.setColumnWidth(0, 300)

    def getRole(self, obj):
        if hasattr(obj, "IfcType"):
            return obj.IfcType
        elif hasattr(obj, "IfcRole"):
            return obj.IfcRole
        elif hasattr(obj, "IfcClass"):
            return obj.IfcClass
        else:
            return None

    def readFromCSV(self, csvfile):
        """reads a csv file and returns a dict"""

        import csv

        result = {}
        if os.path.exists(csvfile):
            with open(csvfile, "r") as f:
                reader = csv.reader(f, delimiter=";")
                for row in reader:
                    result[row[0]] = row[1:]
        return result

    def updateByType(self):
        from PySide import QtGui

        groups = {}
        for name, role in self.objectslist.items():
            role = role[0]
            obj = FreeCAD.ActiveDocument.getObject(name)
            if obj:
                if (not self.form.onlyVisible.isChecked()) or obj.ViewObject.isVisible():
                    groups.setdefault(role, []).append(name)

        for group in groups.keys():
            s1 = group + " (" + str(len(groups[group])) + ")"
            top = QtGui.QStandardItem(s1)
            self.model.appendRow([top, QtGui.QStandardItem(), QtGui.QStandardItem()])
            for name in groups[group]:
                obj = FreeCAD.ActiveDocument.getObject(name)
                if obj:
                    it1 = QtGui.QStandardItem(obj.Label)
                    icon = obj.ViewObject.Icon
                    it1.setIcon(icon)
                    it1.setToolTip(obj.Name)
                    it2 = QtGui.QStandardItem(group)
                    if group != self.getRole(obj):
                        it2.setIcon(QtGui.QIcon(":/icons/edit-edit.svg"))
                    it3 = self.getSearchResults(obj)
                    if it3:
                        top.appendRow([it1, it2, it3])
            top.sortChildren(0)
        self.form.tree.expandAll()
        self.spanTopLevels()

    def updateByTree(self):
        from PySide import QtGui

        # order by hierarchy
        def istop(obj):
            for parent in obj.InListRecursive:
                if parent.Name in self.objectslist.keys():
                    return False
            return True

        rel = []
        deps = []
        for name in self.objectslist.keys():
            obj = FreeCAD.ActiveDocument.getObject(name)
            if obj:
                if istop(obj):
                    rel.append(obj)
                else:
                    deps.append(obj)
        pa = 1
        while deps:
            for obj in rel:
                for child in obj.OutList:
                    if child in deps:
                        rel.append(child)
                        deps.remove(child)
            pa += 1
            if pa == 10:  # max 10 hierarchy levels, okay? Let's keep civilised
                rel.extend(deps)
                break

        done = {}
        for obj in rel:
            role = self.objectslist[obj.Name][0]
            if (not self.form.onlyVisible.isChecked()) or obj.ViewObject.isVisible():
                it1 = QtGui.QStandardItem(obj.Label)
                icon = obj.ViewObject.Icon
                it1.setIcon(icon)
                it1.setToolTip(obj.Name)
                it2 = QtGui.QStandardItem(role)
                if role != self.getRole(obj):
                    it2.setIcon(QtGui.QIcon(":/icons/edit-edit.svg"))
                it3 = self.getSearchResults(obj)
                ok = False
                for par in obj.InListRecursive:
                    if par.Name in done:
                        if (not hasattr(par, "Hosts")) or (obj not in par.Hosts):
                            if it3:
                                done[par.Name].appendRow([it1, it2, it3])
                            done[obj.Name] = it1
                            ok = True
                            break
                if not ok:
                    if it3:
                        self.model.appendRow([it1, it2, it3])
                    done[obj.Name] = it1
        self.form.tree.expandAll()

    def updateDefault(self):
        from PySide import QtGui

        for name, role in self.objectslist.items():
            role = role[0]
            obj = FreeCAD.ActiveDocument.getObject(name)
            if obj:
                if (not self.form.onlyVisible.isChecked()) or obj.ViewObject.isVisible():
                    it1 = QtGui.QStandardItem(obj.Label)
                    icon = obj.ViewObject.Icon
                    it1.setIcon(icon)
                    it1.setToolTip(obj.Name)
                    it2 = QtGui.QStandardItem(role)
                    if role != self.getRole(obj):
                        it2.setIcon(QtGui.QIcon(":/icons/edit-edit.svg"))
                    it3 = self.getSearchResults(obj)
                    if it3:
                        self.model.appendRow([it1, it2, it3])

    def spanTopLevels(self):
        if self.form.groupMode.currentIndex() in [1, 2]:
            idx = self.model.invisibleRootItem().index()
            for i in range(self.model.rowCount()):
                if self.model.item(i, 0).hasChildren():
                    self.form.tree.setFirstColumnSpanned(i, idx, True)

    def accept(self):
        PARAMS.SetInt("BimIfcPropertiesDialogWidth", self.form.width())
        PARAMS.SetInt("BimIfcPropertiesDialogHeight", self.form.height())
        self.form.hide()

        # print(self.objectslist)
        changed = False
        for key, values in self.objectslist.items():
            obj = FreeCAD.ActiveDocument.getObject(key)
            if obj:
                if hasattr(obj, "IfcProperties"):
                    if not isinstance(obj.IfcProperties, dict):
                        FreeCAD.Console.PrintWarning(
                            translate(
                                "BIM",
                                "Warning: object %1 has old-styled IfcProperties and cannot be updated",
                            ).replace("%1", obj.Label)
                            + "\n"
                        )
                        continue
                    props = obj.IfcProperties
                elif hasattr(obj, "IfcClass"):
                    props = self.getNativeIfcProperties(obj)
                else:
                    props = {}
                if values[1] != props:
                    if not changed:
                        FreeCAD.ActiveDocument.openTransaction("Change properties")
                        changed = True
                    if hasattr(obj, "IfcClass"):
                        print("props:", props)
                        for key, value in values[1].items():
                            if ";;" in key and ";;" in value:
                                pname, pset = key.split(";;")
                                ptype, pvalue = value.split(";;")
                                from nativeifc import ifc_psets  # lazy loading

                                fctype = ifc_psets.get_freecad_type(ptype)
                                if not pname in obj.PropertiesList:
                                    obj.addProperty(fctype, pname, pset, ptype + ":" + pname)
                                    ifc_psets.edit_pset(obj, pname, force=True)
                                if pvalue:
                                    setattr(obj, pname, pvalue)
                    elif not hasattr(obj, "IfcProperties"):
                        obj.addProperty(
                            "App::PropertyMap",
                            "IfcPRoperties",
                            "IFC",
                            QT_TRANSLATE_NOOP("App::Property", "IFC properties of this object"),
                            locked=True,
                        )
                    if hasattr(obj, "IfcProperties"):
                        obj.IfcProperties = values[1]
        if changed:
            FreeCAD.ActiveDocument.commitTransaction()
            FreeCAD.ActiveDocument.recompute()
        return self.reject()

    def reject(self):
        self.form.hide()
        del self.form
        return True

    def getNativeIfcProperties(self, obj):
        props = {}
        for p in obj.PropertiesList:
            pset = obj.getGroupOfProperty(p)
            ttip = obj.getDocumentationOfProperty(p)
            if ":" in ttip:
                ptype, pname = ttip.split(":")
                if pset not in ["Base", "IFC", "Geometry"]:
                    props[pname + ";;" + pset] = ptype + ";;" + str(getattr(obj, p))
        return props

    def getSearchResults(self, obj):
        from PySide import QtGui

        text = self.form.searchField.currentText()
        if not text:
            return QtGui.QStandardItem()
        else:
            if obj.Name in self.objectslist:
                result = []
                for key, value in self.objectslist[obj.Name][1].items():
                    if ";;" in key:
                        # 0.19 format
                        key = key.split(";;")
                        pset = key[1]
                        key = key[0]
                    else:
                        pset = value.split(";;")[0]
                    if text.lower() in key.lower():
                        if not key in result:
                            result.append(key)
                    if text.lower() in pset.lower():
                        if not pset in result:
                            result.append(pset)
                if result:
                    return QtGui.QStandardItem(",".join(result))
                else:
                    if self.form.onlyMatches.isChecked():
                        return None
                    else:
                        return QtGui.QStandardItem()
            else:
                return QtGui.QStandardItem()

    def updateProperties(self, sel1=None, sel2=None):
        from PySide import QtGui

        self.propmodel.clear()
        self.propmodel.setHorizontalHeaderLabels(
            [
                translate("Arch", "Property", None),
                translate("Arch", "Type", None),
                translate("Arch", "Value", None),
            ]
        )

        self.form.treeProperties.setColumnWidth(0, 300)
        self.form.treeProperties.setColumnWidth(1, 200)

        # gather common properties
        allprops = []
        sel = self.form.tree.selectedIndexes()
        for index in sel:
            if index.column() == 0:
                name = self.model.itemFromIndex(index).toolTip()
                if name in self.objectslist:
                    allprops.append(self.objectslist[name][1])
        psets = {}
        if allprops:
            for key in allprops[0].keys():
                value = allprops[0][key].split(";;")
                iscommon = True
                if len(value) == 3:
                    pset = value[0]
                    ptype = value[1]
                    pvalue = value[2]
                elif (len(value) == 2) and (";;" in key):  # 0.19 format
                    pset = key.split(";;")[1]
                    ptype = value[0]
                    pvalue = value[1]
                elif len(value) == 2:  # old system
                    pset = "Default property set"
                    ptype = value[0]
                    pvalue = value[1]
                else:
                    print("Error: Unparsable property:", value)
                    return
                for other in allprops[1:]:
                    if key in other.keys():
                        othervalue = other[key].split(";;")
                        if len(value) == 3:
                            otherpset = othervalue[0]
                            otherptype = othervalue[1]
                            otherpvalue = othervalue[2]
                        elif (len(value) == 2) and (";;" in key):  # 0.19 format
                            otherpset = key.split(";;")[1]
                            otherptype = othervalue[0]
                            otherpvalue = othervalue[1]
                        elif len(value) == 2:  # old system
                            otherpset = "Default property set"
                            otherptype = othervalue[0]
                            otherpvalue = othervalue[1]
                        else:
                            print("Error: Unparsable property:", othervalue)
                            return
                        if otherpset != pset:
                            iscommon = False
                        if otherptype != ptype:
                            iscommon = False
                        if otherpvalue != pvalue:
                            pvalue = "*VARIES*"
                    else:
                        iscommon = False
                if iscommon:
                    plabel = ptype
                    if ptype in self.ptypes:
                        plabel = self.plabels[self.ptypes.index(ptype)]
                    psets.setdefault(pset, []).append([key, plabel, pvalue])

        # fill the tree
        for pset, plists in psets.items():
            top = QtGui.QStandardItem(pset)
            top.setDragEnabled(False)
            top.setToolTip("PropertySet")
            self.propmodel.appendRow([top, QtGui.QStandardItem(), QtGui.QStandardItem()])
            for plist in plists:
                pname = plist[0]
                if ";;" in pname:
                    pname = pname.split(";;")[0]
                it1 = QtGui.QStandardItem(pname)
                it1.setDropEnabled(False)
                it2 = QtGui.QStandardItem(plist[1])
                it2.setDropEnabled(False)
                it3 = QtGui.QStandardItem(plist[2])
                it3.setDropEnabled(False)
                top.appendRow([it1, it2, it3])
            top.sortChildren(0)
        # span top levels
        idx = self.propmodel.invisibleRootItem().index()
        for i in range(self.propmodel.rowCount()):
            if self.propmodel.item(i, 0).hasChildren():
                self.form.treeProperties.setFirstColumnSpanned(i, idx, True)
        self.form.treeProperties.expandAll()

    def updateDicts(self, remove=None):
        # update the stored dicts to reflect the editor

        sel = self.form.tree.selectedIndexes()

        for row in range(self.propmodel.rowCount()):
            pset = self.propmodel.item(row, 0).text()
            if self.propmodel.item(row, 0).hasChildren():
                for childrow in range(self.propmodel.item(row, 0).rowCount()):
                    prop = self.propmodel.item(row, 0).child(childrow, 0).text()
                    if ";;" in prop:
                        prop = prop.split(";;")[0]
                    ptype = self.propmodel.item(row, 0).child(childrow, 1).text()
                    if not ptype.startswith("Ifc"):
                        ptype = self.ptypes[self.plabels.index(ptype)]
                    pvalue = self.propmodel.item(row, 0).child(childrow, 2).text()
                    if (sys.version_info.major < 3) and isinstance(prop, unicode):
                        prop = prop.encode("utf8")

                    # update objects
                    for index in sel:
                        if index.column() == 0:
                            name = self.model.itemFromIndex(index).toolTip()
                            if name in self.objectslist:
                                # print("object",name,self.objectslist[name][1])
                                if pvalue == "*VARIES*":
                                    if not (prop + ";;" + pset in self.objectslist[name][1]):
                                        # print("adding",prop)
                                        self.objectslist[name][1][prop + ";;" + pset] = ptype + ";;"
                                else:
                                    pval = ptype + ";;" + pvalue
                                    if prop in self.objectslist[name][1]:
                                        if self.objectslist[name][1][prop + ";;" + pset] != pval:
                                            # print("modifying",prop)
                                            self.objectslist[name][1][prop + ";;" + pset] = pval
                                    else:
                                        # print("adding",prop)
                                        self.objectslist[name][1][prop + ";;" + pset] = pval
        if remove:
            for index in sel:
                if index.column() == 0:
                    name = self.model.itemFromIndex(index).toolTip()
                    if name in self.objectslist:
                        for prop in remove:
                            if prop in self.objectslist[name][1]:
                                # print("deleting",prop)
                                del self.objectslist[name][1][prop]

    def addProperty(self, idx=0, pset=None, prop=None, ptype=None):
        from PySide import QtGui

        if not self.form.tree.selectedIndexes():
            return
        if not pset:
            sel = self.form.treeProperties.selectedIndexes()
            if sel:
                item = self.propmodel.itemFromIndex(sel[0])
                if item.toolTip() == "PropertySet":
                    pset = item
        if pset:
            if not prop:
                basename = translate("Arch", "New property", None)
                # check for duplicate name
                names = []
                for i in range(self.propmodel.rowCount()):
                    topitem = self.propmodel.item(i, 0)
                    names.append(topitem.text())
                    if topitem.hasChildren():
                        for j in range(topitem.rowCount()):
                            childitem = topitem.child(j, 0)
                            names.append(childitem.text())
                suffix = 1
                newname = basename
                while newname in names:
                    newname = basename + str(suffix).zfill(3)
                    suffix += 1
                prop = newname
            if not ptype:
                if idx > 0:
                    ptype = self.plabels[idx - 1]
            if prop and ptype:
                if ptype in self.ptypes:
                    ptype = self.plabels[self.ptypes.index(ptype)]
                it1 = QtGui.QStandardItem(prop)
                it1.setDropEnabled(False)
                it2 = QtGui.QStandardItem(ptype)
                it2.setDropEnabled(False)
                it3 = QtGui.QStandardItem()
                it3.setDropEnabled(False)
                pset.appendRow([it1, it2, it3])
                self.updateDicts()
        else:
            if idx != 0:
                QtGui.QMessageBox.critical(
                    None,
                    "Error",
                    translate(
                        "BIM",
                        "Please select or create a property set first in which the new property should be placed.",
                    ),
                    QtGui.QMessageBox.Ok,
                )
        if idx != 0:
            self.form.comboProperty.setCurrentIndex(0)

    def addPset(self, idx):
        from PySide import QtGui

        if not self.form.tree.selectedIndexes():
            return
        if idx == 1:
            name = translate("Arch", "New property set", None)
            res = QtGui.QInputDialog.getText(
                None,
                translate("BIM", "New property set"),
                translate("BIM", "Property set name:"),
                QtGui.QLineEdit.Normal,
                name,
            )
            if res[1]:
                name = res[0]
            top = QtGui.QStandardItem(name)
            top.setDragEnabled(False)
            top.setToolTip("PropertySet")
            self.propmodel.appendRow([top, QtGui.QStandardItem(), QtGui.QStandardItem()])
        elif idx > 1:
            psetlabel = self.psetkeys[idx - 2]
            psetdef = "Pset_" + psetlabel.replace(" ", "")
            if psetdef in self.psetdefs:
                top = QtGui.QStandardItem(psetdef)
                top.setDragEnabled(False)
                top.setToolTip("PropertySet")
                self.propmodel.appendRow([top, QtGui.QStandardItem(), QtGui.QStandardItem()])
                for i in range(0, len(self.psetdefs[psetdef]), 2):
                    self.addProperty(
                        pset=top,
                        prop=self.psetdefs[psetdef][i],
                        ptype=self.psetdefs[psetdef][i + 1],
                    )
        if idx != 0:
            # span top levels
            idx = self.propmodel.invisibleRootItem().index()
            for i in range(self.propmodel.rowCount()):
                if self.propmodel.item(i, 0).hasChildren():
                    self.form.treeProperties.setFirstColumnSpanned(i, idx, True)
            self.form.treeProperties.expandAll()
            self.form.comboPset.setCurrentIndex(0)

    def removeProperty(self):
        from PySide import QtGui

        sel = self.form.treeProperties.selectedIndexes()
        remove = []
        if sel:
            item = self.propmodel.itemFromIndex(sel[0])
            if item.toolTip() == "PropertySet":
                for i in range(item.rowCount()):
                    remove.append(item.child(i, 0).text() + ";;" + item.text())
                self.propmodel.takeRow(sel[0].row())
            else:
                pset = self.propmodel.itemFromIndex(sel[0].parent())
                itemtext = item.text()
                if isinstance(pset, QtGui.QStandardItem):
                    itemtext += ";;" + pset.text()
                remove.append(itemtext)
                pset.takeRow(sel[0].row())
            self.updateDicts(remove=remove)

    def onSelected(self, index):
        PARAMS.SetInt("IfcPropertiesSelectedState", getattr(index, "value", index))
        self.objectslist, searchterms = self.rebuildObjectsList()
        self.form.searchField.clear()
        self.form.searchField.addItems(searchterms)
        self.update()

    def onVisible(self, index):
        PARAMS.SetInt("IfcPropertiesVisibleState", getattr(index, "value", index))
        self.update()


if FreeCAD.GuiUp:
    from PySide import QtGui

    class propertiesDelegate(QtGui.QStyledItemDelegate):
        def __init__(self, parent=None, container=None, ptypes=[], plabels=[], *args):
            self.container = container
            QtGui.QStyledItemDelegate.__init__(self, parent, *args)
            self.ptypes = ptypes
            self.plabels = plabels

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

            if index.column() == 0:  # property name
                editor = QtGui.QLineEdit(parent)
            elif index.column() == 1:  # property type
                editor = QtGui.QComboBox(parent)
            else:  # property value
                ptype = index.sibling(index.row(), 1).data()
                if "Integer" in ptype:
                    editor = QtGui.QSpinBox(parent)
                elif "Real" in ptype:
                    editor = QtGui.QDoubleSpinBox(parent)
                    editor.setDecimals(
                        FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Units").GetInt(
                            "Decimals", 2
                        )
                    )
                elif ("Boolean" in ptype) or ("Logical" in ptype):
                    editor = QtGui.QComboBox(parent)
                    editor.addItems(["True", "False"])
                elif "Measure" in ptype:
                    editor = FreeCADGui.UiLoader().createWidget("Gui::InputField")
                    editor.setParent(parent)
                else:
                    editor = QtGui.QLineEdit(parent)
                editor.setObjectName("editor_" + ptype)
            return editor

        def setEditorData(self, editor, index):
            if index.column() == 0:
                editor.setText(index.data())
            elif index.column() == 1:
                editor.addItems(self.plabels)
                if index.data() in self.plabels:
                    idx = self.plabels.index(index.data())
                    editor.setCurrentIndex(idx)
            else:
                if "Integer" in editor.objectName():
                    try:
                        editor.setValue(int(index.data()))
                    except (TypeError, ValueError, AttributeError):
                        editor.setValue(0)
                elif "Real" in editor.objectName():
                    try:
                        editor.setValue(float(index.data()))
                    except (TypeError, ValueError, AttributeError):
                        editor.setValue(0)
                elif ("Boolean" in editor.objectName()) or ("Logical" in editor.objectName()):
                    try:
                        editor.setCurrentIndex(["true", "false"].index(index.data().lower()))
                    except (ValueError, AttributeError):
                        editor.setCurrentIndex(1)
                elif "Measure" in editor.objectName():
                    try:
                        editor.setText(index.data())
                    except (ValueError, AttributeError):
                        editor.setValue(0)
                else:
                    editor.setText(index.data())

        def setModelData(self, editor, model, index):
            remove = []
            if index.column() == 0:
                oldtext = index.data()
                if oldtext != editor.text():
                    pset = index.parent().data()
                    remove.append(oldtext + ";;" + pset)
                basename = editor.text()
                # check for duplicate name
                names = []
                for i in range(model.rowCount()):
                    topitem = model.item(i, 0)
                    names.append(topitem.text())
                    if topitem.hasChildren():
                        for j in range(topitem.rowCount()):
                            childitem = topitem.child(j, 0)
                            names.append(childitem.text())
                suffix = 1
                newname = basename
                while newname in names:
                    newname = basename + str(suffix).zfill(3)
                    suffix += 1
                model.setData(index, newname)
            elif index.column() == 1:
                if editor.currentIndex() > -1:
                    idx = editor.currentIndex()
                    data = self.plabels[idx]
                    model.setData(index, data)
            else:
                if ("Integer" in editor.objectName()) or ("Real" in editor.objectName()):
                    model.setData(index, str(editor.value()))
                elif ("Boolean" in editor.objectName()) or ("Logical" in editor.objectName()):
                    model.setData(index, editor.currentText())
                elif "Measure" in editor.objectName():
                    model.setData(index, editor.property("text"))
                else:
                    model.setData(index, editor.text())
            self.container.updateDicts(remove)


FreeCADGui.addCommand("BIM_IfcProperties", BIM_IfcProperties())
