# Copyright (C) 2021 The Qt Company Ltd.
# Copyright (C) 2018 Unified Automation GmbH
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

from PySide6.QtGui import QPixmap
from PySide6.QtCore import Qt, QObject, qWarning
from PySide6.QtOpcUa import QOpcUa

# Columns: 0: NodeId, 1: Value, 2: NodeClass, 3: DataType, 4: BrowseName,
#          5: DisplayName, 6: Description
_numberOfDisplayColumns = 7
_object_pixmap = None
_variable_pixmap = None
_method_pixmap = None
_default_pixmap = None

_DESIRED_ATTRIBUTES = (QOpcUa.NodeAttribute.Value
                       | QOpcUa.NodeAttribute.NodeClass
                       | QOpcUa.NodeAttribute.Description
                       | QOpcUa.NodeAttribute.DataType
                       | QOpcUa.NodeAttribute.BrowseName
                       | QOpcUa.NodeAttribute.DisplayName)

_NODE_CLASSES = {QOpcUa.NodeClass.Undefined: 'Undefined',
                 QOpcUa.NodeClass.Object: 'Object',
                 QOpcUa.NodeClass.Variable: 'Variable',
                 QOpcUa.NodeClass.Method: 'Method',
                 QOpcUa.NodeClass.ObjectType: 'ObjectType',
                 QOpcUa.NodeClass.VariableType: 'VariableType',
                 QOpcUa.NodeClass.ReferenceType: 'ReferenceType',
                 QOpcUa.NodeClass.DataType: 'DataType',
                 QOpcUa.NodeClass.View: 'View'}


def create_pixmap(color):
    p = QPixmap(10, 10)
    p.fill(color)
    return p


class TreeItem(QObject):

    def __init__(self, node, model):
        super(TreeItem, self).__init__(None)

        self._opc_node = node
        self._model = model
        self._attributes_ready = False
        self._browse_started = False
        self._child_items = []
        self._child_node_ids = []
        self._parent_item = None
        self._node_browse_name = ''
        self._node_id = ''
        self._node_display_name = ''
        self._node_class = QOpcUa.NodeClass.Undefined

        self._opc_node.attributeRead.connect(self.handleAttributes)
        self._opc_node.browseFinished.connect(self.browseFinished)

        if not self._opc_node.readAttributes(_DESIRED_ATTRIBUTES):
            qWarning("Reading attributes {} failed".format(self._opc_node.nodeId))

    @staticmethod
    def create_from_browsing_data(node, model, browsingData, parent):
        result = TreeItem(node, model)
        result._parent_item = parent
        result._node_browse_name = browsingData.browseName().name()
        result._node_class = browsingData.nodeClass()
        result._node_id = browsingData.targetNodeId().nodeId()
        result._node_display_name = browsingData.displayName().text()
        return result

    def child(self, row):
        return self._child_items[row]

    def childIndex(self, child):
        return self._child_items.index(child)

    def childCount(self):
        self.startBrowsing()
        return len(self._child_items)

    def columnCount(self):
        return _numberOfDisplayColumns

    def data(self, column):
        if column == 0:
            return self._node_browse_name
        if column == 1:
            if not self._attributes_ready:
                return "Loading ..."
            attribute = self._opc_node.attribute(QOpcUa.NodeAttribute.DataType)
            value = self._opc_node.attribute(QOpcUa.NodeAttribute.Value)
            return str(value) if value else ''
        if column == 2:
            name = _NODE_CLASSES.get(self._node_class)
            return "{} ({})".format(name, self._node_class) if name else str(self._node_class)
        if column == 3:
            if not self._attributes_ready:
                return "Loading ..."
            attribute = self._opc_node.attribute(QOpcUa.NodeAttribute.DataType)
            typeId = attribute if attribute else ''
            enumEntry = QOpcUa.namespace0IdFromNodeId(typeId)
            if enumEntry == QOpcUa.NodeIds.Namespace0.Unknown:
                return typeId
            return "{} ({})".format(QOpcUa.namespace0IdName(enumEntry), typeId)
        if column == 4:
            return self._node_id
        if column == 5:
            return self._node_display_name
        if column == 6:
            if not self._attributes_ready:
                return "Loading ..."
            description = self._opc_node.attribute(QOpcUa.NodeAttribute.Description)
            return description.text() if description else ''
        return None

    def row(self):
        return self._parent_item.childIndex(self) if self._parent_item else 0

    def parentItem(self):
        return self._parent_item

    def appendChild(self, child):
        if not child:
            return
        if not self.hasChildNodeItem(child._node_id):
            self._child_items.append(child)
            self._child_node_ids.append(child._node_id)

    def icon(self, column):
        global _object_pixmap, _variable_pixmap, _method_pixmap, _default_pixmap

        if column != 0 or not self._opc_node:
            return QPixmap()
        if self._node_class == QOpcUa.NodeClass.Object:
            if not _object_pixmap:
                _object_pixmap = create_pixmap(Qt.darkGreen)
            return _object_pixmap
        if self._node_class == QOpcUa.NodeClass.Variable:
            if not _variable_pixmap:
                _variable_pixmap = create_pixmap(Qt.darkBlue)
            return _variable_pixmap
        if self._node_class == QOpcUa.NodeClass.Method:
            if not _method_pixmap:
                _method_pixmap = create_pixmap(Qt.darkRed)
            return _method_pixmap
        if not _default_pixmap:
            _default_pixmap = create_pixmap(Qt.gray)
        return _default_pixmap

    def hasChildNodeItem(self, nodeId):
        return nodeId in self._child_node_ids

    def startBrowsing(self):
        if self._browse_started:
            return

        if not self._opc_node.browseChildren():
            qWarning("Browsing node {} failed".format(self._opc_node.nodeId()))
        else:
            self._browse_started = True

    def handleAttributes(self, attr):
        if attr & QOpcUa.NodeAttribute.NodeClass:
            self._node_class = self._opc_node.attribute(QOpcUa.NodeAttribute.NodeClass)
        if attr & QOpcUa.NodeAttribute.BrowseName:
            name_attr = self._opc_node.attribute(QOpcUa.NodeAttribute.BrowseName)
            self._node_browse_name = name_attr.name()
        if attr & QOpcUa.NodeAttribute.DisplayName:
            display_name_attr = self._opc_node.attribute(QOpcUa.NodeAttribute.DisplayName)
            self._node_display_name = display_name_attr.text()

        self._attributes_ready = True

        row = self.row()
        start_index = self._model.createIndex(row, 0, self)
        end_index = self._model.createIndex(row, _numberOfDisplayColumns - 1, self)
        self._model.dataChanged.emit(start_index, end_index)

    def browseFinished(self, children, status_code):
        if status_code != QOpcUa.Good:
            qWarning("Browsing node {} finally failed: {}".format(
                self._opc_node.nodeId(), status_code))
            return
        row = self.row()
        start_index = self._model.createIndex(row, 0, self)
        for item in children:
            node_id = item.targetNodeId()
            if self.hasChildNodeItem(node_id.nodeId()):
                continue
            node = self._model.opcUaClient().node(node_id)
            if not node:
                qWarning("Failed to instantiate node: {}".format(node_id.nodeId()))
                continue

            child_item_count = len(self._child_items)
            self._model.beginInsertRows(start_index, child_item_count,
                                        child_item_count + 1)
            self.appendChild(TreeItem.create_from_browsing_data(node, self._model, item, self))
            self._model.endInsertRows()

        end_index = self._model.createIndex(row, _numberOfDisplayColumns - 1, self)
        self._model.dataChanged.emit(start_index, end_index)
