from ngSkinTools2 import api, signal
from ngSkinTools2.api import python_compatibility
from ngSkinTools2.api.log import getLogger
from ngSkinTools2.api.pyside import QtCore, QtWidgets
from ngSkinTools2.api.session import session
from ngSkinTools2.ui import qt
from ngSkinTools2.ui.layout import scale_multiplier

if python_compatibility.PY3:
    from typing import Union


log = getLogger("layersView")


def build_view(parent, actions):
    from ngSkinTools2.operations import layers

    layer_icon_size = 20
    visibility_icon_size = 13

    icon_layer = qt.scaled_icon(":/layeredTexture.svg", layer_icon_size, layer_icon_size)
    icon_layer_disabled = qt.scaled_icon(":/layerEditor.png", layer_icon_size, layer_icon_size)
    icon_visible = qt.scaled_icon("eye-fill.svg", visibility_icon_size, visibility_icon_size)
    icon_hidden = qt.scaled_icon("eye-slash-fill.svg", visibility_icon_size, visibility_icon_size)

    layer_data_role = QtCore.Qt.UserRole + 1

    def item_to_layer(item):
        # type: (QtWidgets.QTreeWidgetItem) -> Union[api.Layer, None]
        if item is None:
            return None
        return item.data(0, layer_data_role)

    # noinspection PyShadowingNames
    def sync_layer_parents_to_widget_items(view):
        """
        after drag/drop tree reordering, just brute-force check
        that rearranged items match layers parents
        :return:
        """

        def sync_item(tree_item, parent_layer_id):
            for i in range(tree_item.childCount()):
                child = tree_item.child(i)
                rebuild_buttons(child)

                child_layer = item_to_layer(child)

                if child_layer.parent_id != parent_layer_id:
                    log.info("changing layer parent: %r->%r (was %r)", parent_layer_id, child_layer, child_layer.parent_id)
                    child_layer.parent = parent_layer_id

                new_index = tree_item.childCount() - i - 1
                if child_layer.index != new_index:
                    log.info("changing layer index: %r->%r (was %r)", child_layer, new_index, child_layer.index)
                    child_layer.index = new_index

                sync_item(child, child_layer.id)

        with qt.signals_blocked(view):
            sync_item(view.invisibleRootItem(), None)

    # noinspection PyPep8Naming
    class LayersWidget(QtWidgets.QTreeWidget):
        def dropEvent(self, event):
            QtWidgets.QTreeWidget.dropEvent(self, event)
            sync_layer_parents_to_widget_items(self)

    view = LayersWidget(parent)
    view.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
    view.setUniformRowHeights(True)
    view.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)

    # enable drag/drop
    view.setDragEnabled(True)
    view.viewport().setAcceptDrops(True)
    view.setDragDropMode(QtWidgets.QAbstractItemView.InternalMove)
    view.setDropIndicatorShown(True)

    # add context menu
    view.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)
    actions.addLayersActions(view)

    view.setHeaderLabels(["Layers", ""])
    # view.setHeaderHidden(True)
    view.header().setMinimumSectionSize(1)
    view.header().setStretchLastSection(False)
    view.header().swapSections(0, 1)
    view.header().setSectionResizeMode(0, QtWidgets.QHeaderView.Stretch)
    view.header().setSectionResizeMode(1, QtWidgets.QHeaderView.Fixed)
    view.setColumnWidth(1, 25 * scale_multiplier)
    view.setIndentation(15 * scale_multiplier)
    view.setIconSize(QtCore.QSize(layer_icon_size * scale_multiplier, layer_icon_size * scale_multiplier))

    tree_items = {}

    def rebuild_buttons(item):
        layer = item_to_layer(item)
        bar = QtWidgets.QToolBar(parent=parent)
        bar.setMovable(False)
        bar.setIconSize(QtCore.QSize(visibility_icon_size * scale_multiplier, visibility_icon_size * scale_multiplier))
        a = bar.addAction(icon_visible if layer is None or layer.enabled else icon_hidden, "Toggle enabled/disabled")

        @qt.on(a.triggered)
        def handler():
            layer.enabled = not layer.enabled
            session.events.layerListChanged.emitIfChanged()

        view.setItemWidget(item, 1, bar)

    def build_items(layer_infos):
        """
        sync items in view with provided layer values, trying to delete as little items on the view as possible
        :type layer_infos: list[api.Layer]
        """

        # build map "parent id->list of children "

        log.info("syncing items...")

        # save selected layers IDs to restore item selection later
        selected_layer_ids = {item_to_layer(item).id for item in view.selectedItems()}
        log.info("selected layer IDs: %r", selected_layer_ids)
        current_item_id = None if view.currentItem() is None else item_to_layer(view.currentItem()).id

        hierarchy = {}
        for child in layer_infos:
            if child.parent_id not in hierarchy:
                hierarchy[child.parent_id] = []
            hierarchy[child.parent_id].append(child)

        def sync(parent_tree_item, children_list):
            while parent_tree_item.childCount() > len(children_list):
                parent_tree_item.removeChild(parent_tree_item.child(len(children_list)))

            for index, child in enumerate(reversed(children_list)):
                if index >= parent_tree_item.childCount():
                    item = QtWidgets.QTreeWidgetItem()
                    item.setSizeHint(1, QtCore.QSize(1 * scale_multiplier, 25 * scale_multiplier))
                    item.setFlags(item.flags() | QtCore.Qt.ItemIsEditable)
                    parent_tree_item.addChild(item)
                else:
                    item = parent_tree_item.child(index)

                tree_items[child.id] = item

                item.setData(0, layer_data_role, child)
                item.setText(0, child.name)
                item.setIcon(0, icon_layer if child.enabled else icon_layer_disabled)
                rebuild_buttons(item)

                sync(item, hierarchy.get(child.id, []))

        with qt.signals_blocked(view):
            tree_items.clear()
            sync(view.invisibleRootItem(), hierarchy.get(None, []))

            current_item = tree_items.get(current_item_id, None)
            if current_item is not None:
                view.setCurrentItem(current_item, 0, QtCore.QItemSelectionModel.NoUpdate)

            for i in selected_layer_ids:
                item = tree_items.get(i, None)
                if item is not None:
                    item.setSelected(True)

    @signal.on(session.events.layerListChanged, qtParent=view)
    def refresh_layer_list():
        log.info("event handler for layer list changed")
        if not session.state.layersAvailable:
            build_items([])
        else:
            build_items(session.state.all_layers)

        update_selected_items()

    @signal.on(session.events.currentLayerChanged, qtParent=view)
    def current_layer_changed():
        log.info("event handler for currentLayerChanged")
        layer = session.state.currentLayer.layer
        current_item = view.currentItem()
        if layer is None:
            view.setCurrentItem(None)
            return

        prev_layer = None if current_item is None else item_to_layer(current_item)

        if prev_layer is None or prev_layer.id != layer.id:
            item = tree_items.get(layer.id, None)
            if item is not None:
                log.info("setting current item to " + item.text(0))
                view.setCurrentItem(item, 0, QtCore.QItemSelectionModel.SelectCurrent | QtCore.QItemSelectionModel.ClearAndSelect)

                item.setSelected(True)

    @qt.on(view.currentItemChanged)
    def current_item_changed(curr, _):
        log.info("current item changed")
        if curr is None:
            return

        selected_layer = item_to_layer(curr)

        if layers.getCurrentLayer() == selected_layer:
            return

        layers.setCurrentLayer(selected_layer)

    @qt.on(view.itemChanged)
    def item_changed(item, column):
        log.info("item changed")
        layers.renameLayer(item_to_layer(item), item.text(column))

    @qt.on(view.itemSelectionChanged)
    def update_selected_items():
        selection = [item_to_layer(item) for item in view.selectedItems()]

        if selection != session.context.selected_layers(default=[]):
            log.info("new selected layers: %r", selection)
            session.context.selected_layers.set(selection)

    refresh_layer_list()

    return view
