from ...Qt import QtCore, QtGui, QtWidgets
from ...WidgetGroup import WidgetGroup

from . import axisCtrlTemplate_generic as ui_template

import weakref

translate = QtCore.QCoreApplication.translate
class ViewBoxMenu(QtWidgets.QMenu):
    def __init__(self, view):
        QtWidgets.QMenu.__init__(self)
        
        self.view = weakref.ref(view)  ## keep weakref to view to avoid circular reference (don't know why, but this prevents the ViewBox from being collected)
        self.valid = False  ## tells us whether the ui needs to be updated
        self.viewMap = weakref.WeakValueDictionary()  ## weakrefs to all views listed in the link combos

        self.setTitle(translate("ViewBox", "ViewBox options"))
        self.viewAll = QtGui.QAction(translate("ViewBox", "View All"), self)
        self.viewAll.triggered.connect(self.autoRange)
        self.addAction(self.viewAll)
        
        self.ctrl = []
        self.widgetGroups = []
        self.dv = QtGui.QDoubleValidator(self)
        for axis in 'XY':
            m = self.addMenu(f"{axis} {translate('ViewBox', 'axis')}")
            w = QtWidgets.QWidget()
            ui = ui_template.Ui_Form()
            ui.setupUi(w)
            a = QtWidgets.QWidgetAction(self)
            a.setDefaultWidget(w)
            m.addAction(a)
            self.ctrl.append(ui)
            wg = WidgetGroup(w)
            self.widgetGroups.append(wg)
            
            connects = [
                (ui.mouseCheck.toggled, 'MouseToggled'),
                (ui.manualRadio.clicked, 'ManualClicked'),
                (ui.minText.editingFinished, 'RangeTextChanged'),
                (ui.maxText.editingFinished, 'RangeTextChanged'),
                (ui.autoRadio.clicked, 'AutoClicked'),
                (ui.autoPercentSpin.valueChanged, 'AutoSpinChanged'),
                (ui.linkCombo.currentIndexChanged, 'LinkComboChanged'),
                (ui.autoPanCheck.toggled, 'AutoPanToggled'),
                (ui.visibleOnlyCheck.toggled, 'VisibleOnlyToggled')
            ]
            
            for sig, fn in connects:
                sig.connect(getattr(self, axis.lower()+fn))

        self.ctrl[0].invertCheck.toggled.connect(self.xInvertToggled)
        self.ctrl[1].invertCheck.toggled.connect(self.yInvertToggled)
        
        leftMenu = self.addMenu(translate("ViewBox", "Mouse Mode"))

        group = QtGui.QActionGroup(self)
        group.triggered.connect(self.setMouseMode)
        pan = QtGui.QAction(translate("ViewBox", "3 button"), group)
        zoom = QtGui.QAction(translate("ViewBox", "1 button"), group)
        pan.setCheckable(True)
        zoom.setCheckable(True)

        leftMenu.addActions(group.actions())

        self.mouseModes = [pan, zoom]
        
        self.view().sigStateChanged.connect(self.viewStateChanged)
        
        self.updateState()

    def viewStateChanged(self):
        self.valid = False
        if self.ctrl[0].minText.isVisible() or self.ctrl[1].minText.isVisible():
            self.updateState()
        
    def updateState(self):
        ## Something about the viewbox has changed; update the menu GUI
        
        state = self.view().getState(copy=False)
        if state['mouseMode'] == ViewBox.PanMode:
            self.mouseModes[0].setChecked(True)
        else:
            self.mouseModes[1].setChecked(True)
            
        for i in [0,1]:  # x, y
            tr = state['targetRange'][i]
            self.ctrl[i].minText.setText("%0.5g" % tr[0])
            self.ctrl[i].maxText.setText("%0.5g" % tr[1])
            if state['autoRange'][i] is not False:
                self.ctrl[i].autoRadio.setChecked(True)
                if state['autoRange'][i] is not True:
                    self.ctrl[i].autoPercentSpin.setValue(int(state['autoRange'][i] * 100))
            else:
                self.ctrl[i].manualRadio.setChecked(True)
            self.ctrl[i].mouseCheck.setChecked(state['mouseEnabled'][i])
            
            ## Update combo to show currently linked view
            c = self.ctrl[i].linkCombo
            c.blockSignals(True)
            try:
                view = state['linkedViews'][i]  ## will always be string or None
                if view is None:
                    view = ''
                    
                ind = c.findText(view)
                    
                if ind == -1:
                    ind = 0
                c.setCurrentIndex(ind)
            finally:
                c.blockSignals(False)
            
            self.ctrl[i].autoPanCheck.setChecked(state['autoPan'][i])
            self.ctrl[i].visibleOnlyCheck.setChecked(state['autoVisibleOnly'][i])
            xy = ['x', 'y'][i]
            self.ctrl[i].invertCheck.setChecked(state.get(xy+'Inverted', False))
        
        self.valid = True
        
    def popup(self, *args):
        if not self.valid:
            self.updateState()
        QtWidgets.QMenu.popup(self, *args)
        
    def autoRange(self):
        self.view().autoRange()  ## don't let signal call this directly--it'll add an unwanted argument

    def xMouseToggled(self, b):
        self.view().setMouseEnabled(x=b)

    def xManualClicked(self):
        self.view().enableAutoRange(ViewBox.XAxis, False)
        
    def xRangeTextChanged(self):
        self.ctrl[0].manualRadio.setChecked(True)
        self.view().setXRange(*self._validateRangeText(0), padding=0)

    def xAutoClicked(self):
        val = self.ctrl[0].autoPercentSpin.value() * 0.01
        self.view().enableAutoRange(ViewBox.XAxis, val)
        
    def xAutoSpinChanged(self, val):
        self.ctrl[0].autoRadio.setChecked(True)
        self.view().enableAutoRange(ViewBox.XAxis, val*0.01)

    def xLinkComboChanged(self, ind):
        self.view().setXLink(str(self.ctrl[0].linkCombo.currentText()))

    def xAutoPanToggled(self, b):
        self.view().setAutoPan(x=b)
    
    def xVisibleOnlyToggled(self, b):
        self.view().setAutoVisible(x=b)


    def yMouseToggled(self, b):
        self.view().setMouseEnabled(y=b)

    def yManualClicked(self):
        self.view().enableAutoRange(ViewBox.YAxis, False)
        
    def yRangeTextChanged(self):
        self.ctrl[1].manualRadio.setChecked(True)
        self.view().setYRange(*self._validateRangeText(1), padding=0)
        
    def yAutoClicked(self):
        val = self.ctrl[1].autoPercentSpin.value() * 0.01
        self.view().enableAutoRange(ViewBox.YAxis, val)
        
    def yAutoSpinChanged(self, val):
        self.ctrl[1].autoRadio.setChecked(True)
        self.view().enableAutoRange(ViewBox.YAxis, val*0.01)

    def yLinkComboChanged(self, ind):
        self.view().setYLink(str(self.ctrl[1].linkCombo.currentText()))

    def yAutoPanToggled(self, b):
        self.view().setAutoPan(y=b)
    
    def yVisibleOnlyToggled(self, b):
        self.view().setAutoVisible(y=b)

    def yInvertToggled(self, b):
        self.view().invertY(b)

    def xInvertToggled(self, b):
        self.view().invertX(b)

    def setMouseMode(self, action):
        mode = None
        if action == self.mouseModes[0]:
            mode = 'pan'
        elif action == self.mouseModes[1]:
            mode = 'rect'
        if mode is not None:
            self.view().setLeftButtonAction(mode)
        
    def setViewList(self, views):
        names = ['']
        self.viewMap.clear()
        
        ## generate list of views to show in the link combo
        for v in views:
            name = v.name
            if name is None:  ## unnamed views do not show up in the view list (although they are linkable)
                continue
            names.append(name)
            self.viewMap[name] = v
            
        for i in [0,1]:
            c = self.ctrl[i].linkCombo
            current = c.currentText()
            c.blockSignals(True)
            changed = True
            try:
                c.clear()
                for name in names:
                    c.addItem(name)
                    if name == current:
                        changed = False
                        c.setCurrentIndex(c.count()-1)
            finally:
                c.blockSignals(False)
                
            if changed:
                c.setCurrentIndex(0)
                c.currentIndexChanged.emit(c.currentIndex())

    def _validateRangeText(self, axis):
        """Validate range text inputs. Return current value(s) if invalid."""
        inputs = (self.ctrl[axis].minText.text(),
                  self.ctrl[axis].maxText.text())
        vals = self.view().viewRange()[axis]
        for i, text in enumerate(inputs):
            try:
                vals[i] = float(text)
            except ValueError:
                # could not convert string to float
                pass
        return vals

        
from .ViewBox import ViewBox
