__all__ = ["Canvas"]

import gc
import importlib
import weakref
import warnings

from ..graphicsItems.GridItem import GridItem
from ..graphicsItems.ROI import ROI
from ..graphicsItems.ViewBox import ViewBox
from ..Qt import QT_LIB, QtCore, QtGui, QtWidgets
from . import CanvasTemplate_generic as ui_template
from .CanvasItem import CanvasItem, GroupCanvasItem
from .CanvasManager import CanvasManager

translate = QtCore.QCoreApplication.translate

class Canvas(QtWidgets.QWidget):
    
    sigSelectionChanged = QtCore.Signal(object, object)
    sigItemTransformChanged = QtCore.Signal(object, object)
    sigItemTransformChangeFinished = QtCore.Signal(object, object)
    
    def __init__(self, parent=None, allowTransforms=True, hideCtrl=False, name=None):
        QtWidgets.QWidget.__init__(self, parent)
        warnings.warn(
            'pyqtgrapoh.cavas will be deprecated in pyqtgraph and migrate to '
            'acq4.  Removal will occur after September, 2023.',
            DeprecationWarning, stacklevel=2
        )

        self.ui = ui_template.Ui_Form()
        self.ui.setupUi(self)
        self.view = ViewBox()
        self.ui.view.setCentralItem(self.view)
        self.itemList = self.ui.itemList
        self.itemList.setSelectionMode(self.itemList.SelectionMode.ExtendedSelection)
        self.allowTransforms = allowTransforms
        self.multiSelectBox = SelectBox()
        self.view.addItem(self.multiSelectBox)
        self.multiSelectBox.hide()
        self.multiSelectBox.setZValue(1e6)
        self.ui.mirrorSelectionBtn.hide()
        self.ui.reflectSelectionBtn.hide()
        self.ui.resetTransformsBtn.hide()
        
        self.redirect = None  ## which canvas to redirect items to
        self.items = []
        
        self.view.setAspectLocked(True)
        
        grid = GridItem()
        self.grid = CanvasItem(grid, name='Grid', movable=False)
        self.addItem(self.grid)
        
        self.hideBtn = QtWidgets.QPushButton('>', self)
        self.hideBtn.setFixedWidth(20)
        self.hideBtn.setFixedHeight(20)
        self.ctrlSize = 200
        self.sizeApplied = False
        self.hideBtn.clicked.connect(self.hideBtnClicked)
        self.ui.splitter.splitterMoved.connect(self.splitterMoved)
        
        self.ui.itemList.itemChanged.connect(self.treeItemChanged)
        self.ui.itemList.sigItemMoved.connect(self.treeItemMoved)
        self.ui.itemList.itemSelectionChanged.connect(self.treeItemSelected)
        self.ui.autoRangeBtn.clicked.connect(self.autoRange)
        self.ui.redirectCheck.toggled.connect(self.updateRedirect)
        self.ui.redirectCombo.currentIndexChanged.connect(self.updateRedirect)
        self.multiSelectBox.sigRegionChanged.connect(self.multiSelectBoxChanged)
        self.multiSelectBox.sigRegionChangeFinished.connect(self.multiSelectBoxChangeFinished)
        self.ui.mirrorSelectionBtn.clicked.connect(self.mirrorSelectionClicked)
        self.ui.reflectSelectionBtn.clicked.connect(self.reflectSelectionClicked)
        self.ui.resetTransformsBtn.clicked.connect(self.resetTransformsClicked)
        
        self.resizeEvent()
        if hideCtrl:
            self.hideBtnClicked()
            
        if name is not None:
            self.registeredName = CanvasManager.instance().registerCanvas(self, name)
            self.ui.redirectCombo.setHostName(self.registeredName)
            
        self.menu = QtWidgets.QMenu()
        remAct = QtGui.QAction(translate("Context Menu", "Remove item"), self.menu)
        remAct.triggered.connect(self.removeClicked)
        self.menu.addAction(remAct)
        self.menu.remAct = remAct
        self.ui.itemList.contextMenuEvent = self.itemListContextMenuEvent

    def splitterMoved(self):
        self.resizeEvent()

    def hideBtnClicked(self):
        ctrlSize = self.ui.splitter.sizes()[1]
        if ctrlSize == 0:
            cs = self.ctrlSize
            w = self.ui.splitter.size().width()
            if cs > w:
                cs = w - 20
            self.ui.splitter.setSizes([w-cs, cs])
            self.hideBtn.setText('>')
        else:
            self.ctrlSize = ctrlSize
            self.ui.splitter.setSizes([100, 0])
            self.hideBtn.setText('<')
        self.resizeEvent()

    def autoRange(self):
        self.view.autoRange()

    def resizeEvent(self, ev=None):
        if ev is not None:
            super().resizeEvent(ev)
        self.hideBtn.move(self.ui.view.size().width() - self.hideBtn.width(), 0)
        
        if not self.sizeApplied:
            self.sizeApplied = True
            s = int( min(self.width(), max(100, min(200, self.width()//4))) )
            s2 = self.width()-s
            self.ui.splitter.setSizes([s2, s])
    
    def updateRedirect(self, *args):
        ### Decide whether/where to redirect items and make it so
        cname = str(self.ui.redirectCombo.currentText())
        man = CanvasManager.instance()
        if self.ui.redirectCheck.isChecked() and cname != '':
            redirect = man.getCanvas(cname)
        else:
            redirect = None
            
        if self.redirect is redirect:
            return
            
        self.redirect = redirect
        if redirect is None:
            self.reclaimItems()
        else:
            self.redirectItems(redirect)
    
    def redirectItems(self, canvas):
        for i in self.items:
            if i is self.grid:
                continue
            li = i.listItem
            parent = li.parent()
            if parent is None:
                tree = li.treeWidget()
                if tree is None:
                    print("Skipping item", i, i.name)
                    continue
                tree.removeTopLevelItem(li)
            else:
                parent.removeChild(li)
            canvas.addItem(i)

    def reclaimItems(self):
        items = self.items
        self.items = [self.grid]
        items.remove(self.grid)
        
        for i in items:
            i.canvas.removeItem(i)
            self.addItem(i)

    def treeItemChanged(self, item, col):
        try:
            citem = item.canvasItem()
        except AttributeError:
            return
        if item.checkState(0) == QtCore.Qt.CheckState.Checked:
            for i in range(item.childCount()):
                item.child(i).setCheckState(0, QtCore.Qt.CheckState.Checked)
            citem.show()
        else:
            for i in range(item.childCount()):
                item.child(i).setCheckState(0, QtCore.Qt.CheckState.Unchecked)
            citem.hide()

    def treeItemSelected(self):
        sel = self.selectedItems()

        if len(sel) == 0:
            return
            
        multi = len(sel) > 1
        for i in self.items:
            ## updated the selected state of every item
            i.selectionChanged(i in sel, multi)
            
        if len(sel)==1:
            self.multiSelectBox.hide()
            self.ui.mirrorSelectionBtn.hide()
            self.ui.reflectSelectionBtn.hide()
            self.ui.resetTransformsBtn.hide()
        elif len(sel) > 1:
            self.showMultiSelectBox()
        
        self.sigSelectionChanged.emit(self, sel)
        
    def selectedItems(self):
        """
        Return list of all selected canvasItems
        """
        return [item.canvasItem() for item in self.itemList.selectedItems() if item.canvasItem() is not None]
        
    def selectItem(self, item):
        li = item.listItem
        self.itemList.setCurrentItem(li)
        
    def showMultiSelectBox(self):
        ## Get list of selected canvas items
        items = self.selectedItems()
        
        rect = self.view.itemBoundingRect(items[0].graphicsItem())
        for i in items:
            if not i.isMovable():  ## all items in selection must be movable
                return
            br = self.view.itemBoundingRect(i.graphicsItem())
            rect = rect|br
            
        self.multiSelectBox.blockSignals(True)
        self.multiSelectBox.setPos([rect.x(), rect.y()])
        self.multiSelectBox.setSize(rect.size())
        self.multiSelectBox.setAngle(0)
        self.multiSelectBox.blockSignals(False)
        
        self.multiSelectBox.show()
        
        self.ui.mirrorSelectionBtn.show()
        self.ui.reflectSelectionBtn.show()
        self.ui.resetTransformsBtn.show()

    def mirrorSelectionClicked(self):
        for ci in self.selectedItems():
            ci.mirrorY()
        self.showMultiSelectBox()

    def reflectSelectionClicked(self):
        for ci in self.selectedItems():
            ci.mirrorXY()
        self.showMultiSelectBox()
            
    def resetTransformsClicked(self):
        for i in self.selectedItems():
            i.resetTransformClicked()
        self.showMultiSelectBox()

    def multiSelectBoxChanged(self):
        self.multiSelectBoxMoved()
        
    def multiSelectBoxChangeFinished(self):
        for ci in self.selectedItems():
            ci.applyTemporaryTransform()
            ci.sigTransformChangeFinished.emit(ci)
        
    def multiSelectBoxMoved(self):
        transform = self.multiSelectBox.getGlobalTransform()
        for ci in self.selectedItems():
            ci.setTemporaryTransform(transform)
            ci.sigTransformChanged.emit(ci)
        
    def addGraphicsItem(self, item, **opts):
        """Add a new GraphicsItem to the scene at pos.
        Common options are name, pos, scale, and z
        """
        citem = CanvasItem(item, **opts)
        item._canvasItem = citem
        self.addItem(citem)
        return citem

    def addGroup(self, name, **kargs):
        group = GroupCanvasItem(name=name)
        self.addItem(group, **kargs)
        return group

    def addItem(self, citem):
        """
        Add an item to the canvas. 
        """
        
        ## Check for redirections
        if self.redirect is not None:
            name = self.redirect.addItem(citem)
            self.items.append(citem)
            return name

        if not self.allowTransforms:
            citem.setMovable(False)

        citem.sigTransformChanged.connect(self.itemTransformChanged)
        citem.sigTransformChangeFinished.connect(self.itemTransformChangeFinished)
        citem.sigVisibilityChanged.connect(self.itemVisibilityChanged)

        
        ## Determine name to use in the item list
        name = citem.opts['name']
        if name is None:
            name = 'item'

        ## find parent and add item to tree
        insertLocation = 0
            
        ## determine parent list item where this item should be inserted
        parent = citem.parentItem()
        if parent in (None, self.view.childGroup):
            parent = self.itemList.invisibleRootItem()
        else:
            parent = parent.listItem
        
        ## set Z value above all other siblings if none was specified
        siblings = [parent.child(i).canvasItem() for i in range(parent.childCount())]
        z = citem.zValue()
        if z is None:
            zvals = [i.zValue() for i in siblings]
            if parent is self.itemList.invisibleRootItem():
                if len(zvals) == 0:
                    z = 0
                else:
                    z = max(zvals)+10
            else:
                if len(zvals) == 0:
                    z = parent.canvasItem().zValue()
                else:
                    z = max(zvals)+1
            citem.setZValue(z)
            
        ## determine location to insert item relative to its siblings
        for i in range(parent.childCount()):
            ch = parent.child(i)
            zval = ch.canvasItem().graphicsItem().zValue()  ## should we use CanvasItem.zValue here?
            if zval < z:
                insertLocation = i
                break
            else:
                insertLocation = i+1
                
        node = QtWidgets.QTreeWidgetItem([name])
        flags = node.flags() | QtCore.Qt.ItemFlag.ItemIsUserCheckable | QtCore.Qt.ItemFlag.ItemIsDragEnabled
        if not isinstance(citem, GroupCanvasItem):
            flags = flags & ~QtCore.Qt.ItemFlag.ItemIsDropEnabled
        node.setFlags(flags)
        if citem.opts['visible']:
            node.setCheckState(0, QtCore.Qt.CheckState.Checked)
        else:
            node.setCheckState(0, QtCore.Qt.CheckState.Unchecked)
        
        node.name = name
        parent.insertChild(insertLocation, node)
        
        citem.name = name
        citem.listItem = node
        node.canvasItem = weakref.ref(citem)
        self.items.append(citem)

        ctrl = citem.ctrlWidget()
        ctrl.hide()
        self.ui.ctrlLayout.addWidget(ctrl)
        
        ## inform the canvasItem that its parent canvas has changed
        citem.setCanvas(self)

        ## Autoscale to fit the first item added (not including the grid).
        if len(self.items) == 2:
            self.autoRange()
            
        return citem

    def treeItemMoved(self, item, parent, index):
        ##Item moved in tree; update Z values
        if parent is self.itemList.invisibleRootItem():
            item.canvasItem().setParentItem(self.view.childGroup)
        else:
            item.canvasItem().setParentItem(parent.canvasItem())
        siblings = [parent.child(i).canvasItem() for i in range(parent.childCount())]
        
        zvals = [i.zValue() for i in siblings]
        zvals.sort(reverse=True)
        
        for i in range(len(siblings)):
            item = siblings[i]
            item.setZValue(zvals[i])
        
    def itemVisibilityChanged(self, item):
        listItem = item.listItem
        checked = listItem.checkState(0) == QtCore.Qt.CheckState.Checked
        vis = item.isVisible()
        if vis != checked:
            if vis:
                listItem.setCheckState(0, QtCore.Qt.CheckState.Checked)
            else:
                listItem.setCheckState(0, QtCore.Qt.CheckState.Unchecked)

    def removeItem(self, item):
        if isinstance(item, QtWidgets.QTreeWidgetItem):
            item = item.canvasItem()
            
        if isinstance(item, CanvasItem):
            item.setCanvas(None)
            listItem = item.listItem
            listItem.canvasItem = None
            item.listItem = None
            self.itemList.removeTopLevelItem(listItem)
            self.items.remove(item)
            ctrl = item.ctrlWidget()
            ctrl.hide()
            self.ui.ctrlLayout.removeWidget(ctrl)
            ctrl.setParent(None)
        else:
            if hasattr(item, '_canvasItem'):
                self.removeItem(item._canvasItem)
            else:
                self.view.removeItem(item)
                
        gc.collect()
        
    def clear(self):
        while len(self.items) > 0:
            self.removeItem(self.items[0])

    def addToScene(self, item):
        self.view.addItem(item)
        
    def removeFromScene(self, item):
        self.view.removeItem(item)
    
    def listItems(self):
        """Return a dictionary of name:item pairs"""
        return self.items
        
    def getListItem(self, name):
        return self.items[name]
        
    def itemTransformChanged(self, item):
        self.sigItemTransformChanged.emit(self, item)
    
    def itemTransformChangeFinished(self, item):
        self.sigItemTransformChangeFinished.emit(self, item)
        
    def itemListContextMenuEvent(self, ev):
        self.menuItem = self.itemList.itemAt(ev.pos())
        self.menu.popup(ev.globalPos())
        
    def removeClicked(self):
        for item in self.selectedItems():
            self.removeItem(item)
        self.menuItem = None
        import gc
        gc.collect()


class SelectBox(ROI):
    def __init__(self, scalable=False):
        #QtWidgets.QGraphicsRectItem.__init__(self, 0, 0, size[0], size[1])
        ROI.__init__(self, [0,0], [1,1])
        center = [0.5, 0.5]
            
        if scalable:
            self.addScaleHandle([1, 1], center, lockAspect=True)
            self.addScaleHandle([0, 0], center, lockAspect=True)
        self.addRotateHandle([0, 1], center)
        self.addRotateHandle([1, 0], center)
