class DmsHandler(QObject):

    inputDidChange = pyqtSignal()

    def __init__(self, degField, minField, secField, maxDegrees):
        super(DmsHandler, self).__init__()

        # init validators:
        self.intDegValidator = QIntValidator(0, maxDegrees, parent=self)
        self.minValidator = QIntValidator(0, 59, parent=self)

        self.secValidator = QDoubleValidator(0.0, 59.999, 3, parent=self)
        self.secValidator.setNotation(QDoubleValidator.StandardNotation)

        self._degField = degField
        self._minField = minField
        self._secField = secField

        self._maxDegrees = maxDegrees

        self._degFieldIncrementor = ValueIncrementor(self._degField,
                                                     maxDegrees,
                                                     doOverflow=False)
        self._degFieldIncrementor.fieldDidOverflow.connect(
            self.fieldDidOverflow)
        self._degField.setValidator(self.intDegValidator)
        self._degField.textEdited.connect(self.inputDidChange.emit)

        self._minFieldIncrementor = ValueIncrementor(
            self._minField, 59, wrapCallback=self.isWrapAllowedFor)
        self._minFieldIncrementor.fieldDidOverflow.connect(
            self.fieldDidOverflow)
        self._minField.setValidator(self.minValidator)
        self._minField.textChanged.connect(
            partial(self.minorFieldDidChange, self._minField))

        self._secFieldIncrementor = ValueIncrementor(
            self._secField, 59, wrapCallback=self.isWrapAllowedFor)
        self._secFieldIncrementor.fieldDidOverflow.connect(
            self.fieldDidOverflow)
        self._secField.setValidator(self.secValidator)
        self._secField.textChanged.connect(
            partial(self.minorFieldDidChange, self._secField))

    def fieldDidOverflow(self, field, overflow):
        if field == self._minField:
            self._degFieldIncrementor.doStepwiseIncrement(overflow)
        elif field == self._secField:
            self._minFieldIncrementor.doStepwiseIncrement(overflow)

    def minorFieldDidChange(self, field):
        degrees = QLocale().toInt(self._degField.text())[0]
        if degrees == self._maxDegrees:
            field.setText("0")

        self.inputDidChange.emit()

    def isWrapAllowedFor(self, field, direction):
        if (field == self._minField):
            degrees = QLocale().toInt(self._degField.text())[0]
            res = degrees + direction
            return 0 <= res <= self._maxDegrees
        elif (field == self._secField):
            minutes = QLocale().toInt(self._minField.text())[0]
            res = minutes + direction
            if not (0 <= res < 60):
                return self.isWrapAllowedFor(self._minField, direction)
            else:
                return True
        elif (field == self._degField):
            degrees = QLocale().toInt(self._degField.text())[0]
            res = degrees + direction
            return 0 <= res <= self._maxDegrees

        return True
class CoordinatorDockWidget(QtWidgets.QDockWidget, FORM_CLASS):

    SectionInput = 1
    SectionOutput = 2
    SectionBoth = 3

    SideRight = 1
    SideLeft = 2
    SideBoth = 3

    closingPlugin = pyqtSignal()

    inputChanged = pyqtSignal()
    mapConnectionChanged = pyqtSignal(int, bool)

    def __init__(self, parent=None):
        """Constructor."""
        super(CoordinatorDockWidget, self).__init__(parent)
        # Set up the user interface from Designer.
        # After setupUI you can access any designer object by doing
        # self.<objectname>, and you can use autoconnect slots - see
        # http://qt-project.org/doc/qt-4.8/designer-using-a-ui-file.html
        # #widgets-and-dialogs-with-auto-connect

        self.decDegreeValidator = QDoubleValidator(0.0, 180.0, 8, parent=self)
        self.decDegreeValidator.setNotation(QDoubleValidator.StandardNotation)

        self.projectedValidator = QDoubleValidator(-999999999.0,
                                                   999999999.0,
                                                   4,
                                                   parent=self)

        self._eastingLeftNorthingRight = True

        self._eastingInverted = False
        self._northingInverted = False

        self.sectionIsGeographic = {
            CoordinatorDockWidget.SectionInput: True,
            CoordinatorDockWidget.SectionOutput: True
        }

        self.setupUi(self)

        # setup some lists of the input for easier access later
        self._widgetlistInputDms = [
            self.inLeft, self.inRight, self.inLeftSec, self.inRightSec,
            self.inLeftMin, self.inRightMin
        ]
        self._widgetlistInput = self._widgetlistInputDms + [
            self.inLeftDec, self.inRightDec
        ]

        self.resetInterface()
        self.setupInternal()

        # need to init that after setupUi because of message icon label size
        messageIconWidgetSize = self.messageIcon.size()
        messageIconSize = QSize(messageIconWidgetSize.width() - 2,
                                messageIconWidgetSize.height() - 2)
        self.warningPixmap = QIcon(
            ':/plugins/coordinator/icons/warning.svg').pixmap(messageIconSize)

        self.messageHideTimer = QTimer(parent=self)
        self.messageHideTimer.setSingleShot(True)
        self.messageHideTimer.timeout.connect(self.hideMessages)

    def setupInternal(self):
        self.leftDecimal.hide()
        self.rightDecimal.hide()

        self.leftDmsDisplay = DmsHandler(self.inLeft, self.inLeftMin,
                                         self.inLeftSec, 180)
        self.leftDmsDisplay.inputDidChange.connect(
            self.__inputFieldsChangedInternal)
        self.rightDmsDisplay = DmsHandler(self.inRight, self.inRightMin,
                                          self.inRightSec, 90)
        self.rightDmsDisplay.inputDidChange.connect(
            self.__inputFieldsChangedInternal)

        for dirButton in [self.leftDirButton, self.rightDirButton]:
            # see https://stackoverflow.com/questions/4578861/connecting-slots-and-signals-in-pyqt4-in-a-loop
            dirButton.clicked.connect(
                partial(self.toggleCardinalDirectionButton, dirButton))

        for inputField in self._widgetlistInputDms:
            inputField.installEventFilter(self)

        for inputField in [self.inLeftDec, self.inRightDec]:
            inputField.textEdited.connect(self.__inputFieldsChangedInternal)
            inputField.setValidator(self.decDegreeValidator)
            inputField.installEventFilter(self)

        self.leftDecIncrementor = ValueIncrementor(self.inLeftDec, 180, -180)
        self.rightDecIncrementor = ValueIncrementor(self.inLeftDec, 90, -90)

        self.outputCrsConn.clicked.connect(
            partial(self.toggledMapConnection,
                    CoordinatorDockWidget.SectionOutput))

        self.copyLeft.clicked.connect(
            partial(self.copyResultToClipBoard,
                    CoordinatorDockWidget.SideLeft))
        self.copyRight.clicked.connect(
            partial(self.copyResultToClipBoard,
                    CoordinatorDockWidget.SideRight))
        self.copyResultComplete.clicked.connect(
            partial(self.copyResultToClipBoard,
                    CoordinatorDockWidget.SideBoth))
        self.clearInput.clicked.connect(
            partial(self.resetInterface, self.SectionBoth))
        self.showHelp.clicked.connect(self.showHelpButtonClicked)

    def _setToolsEnabled(self, enabled, section=None):
        if (section == None): section = self.SectionBoth

        if (section & self.SectionInput):
            self.moveCanvas.setEnabled(enabled)

        if (section & self.SectionOutput):
            for widget in [
                    self.copyResultComplete, self.copyLeft, self.copyRight
            ]:
                widget.setEnabled(enabled)

    def resetInterface(self, section=None):
        if section == None: section = self.SectionBoth
        self.clearSection(section)
        self._setToolsEnabled(False, section)

    def eventFilter(self, obj, event):
        if (type(event) == QKeyEvent and event.type() == QEvent.KeyRelease):
            key = event.key()
            if (self.sectionIsGeographic[self.SectionInput]
                    and event.text() == "-"):
                # maybe we should switch the hemisphere?
                if (obj in [
                        self.inLeft, self.inLeftDec, self.inLeftMin,
                        self.inLeftSec
                ]):
                    self.toggleCardinalDirectionButton(self.leftDirButton)
                else:
                    self.toggleCardinalDirectionButton(self.rightDirButton)
            elif (key in [Qt.Key_Return, Qt.Key_Enter]):
                if (self.addFeatureButton.isEnabled()):
                    #coordinatorLog("Clicking add Feature Button")
                    self.addFeatureButton.click()

            #coordinatorLog("Yakub Key Event (Filter): %s (%s)" % (event.key(), event.text()) )

        return super(CoordinatorDockWidget, self).eventFilter(obj, event)

    def __styleInputStyleSelectorForGeographicCrs(self, isGeographic):

        for widget in [self.geoLabel, self.inputAsDMS, self.inputAsDec]:
            widget.setEnabled(isGeographic)

    def setSectionIsGeographic(self, section, isGeographic):
        if (section & self.SectionInput):
            # switch validators for the decimal field, as we may support different ranges
            # switch to DMS view accordingly
            if isGeographic:
                self.inLeftDec.setValidator(self.decDegreeValidator)
                self.inRightDec.setValidator(self.decDegreeValidator)
                # switch to DMS view if it is selected
                self.setInputToDMS(self.inputAsDMS.isChecked())
            else:
                self.inLeftDec.setValidator(self.projectedValidator)
                self.inRightDec.setValidator(self.projectedValidator)

                self.setInputToDMS(False)

            for widget in [
                    self.labelDecDegreeLeft, self.labelDecDegreeRight,
                    self.leftDirButton, self.rightDirButton
            ]:
                widget.show() if isGeographic else widget.hide()

            self.__styleInputStyleSelectorForGeographicCrs(isGeographic)

        if (section & self.SectionOutput):
            for widget in [self.resultAsDMS, self.resultAsDec]:
                widget.setEnabled(isGeographic)

        self.sectionIsGeographic[section] = isGeographic

    def setSectionCrs(self, section, crs):
        #QgsMessageLog.logMessage("set SectionCrs, is Geographic: %s" % crs.isGeographic(), "Coordinator")

        self.setSectionIsGeographic(section, crs.isGeographic())

        if (section == self.SectionInput):
            self.selectCrsButton.setText(crs.authid())
        elif (section == self.SectionOutput):
            self.outputCrs.setText(crs.authid())

    def setInputToDMS(self, isDms):
        if isDms:
            self.leftDMS.show()
            self.rightDMS.show()
            self.leftDecimal.hide()
            self.rightDecimal.hide()
        else:
            self.leftDMS.hide()
            self.rightDMS.hide()
            self.leftDecimal.show()
            self.rightDecimal.show()

    def setInputPoint(self, point):
        #QgsMessageLog.logMessage("%s/%s" % (point.x(), point.y()))

        if self.sectionIsGeographic[self.SectionInput]:
            precision = 9
            # remove leading minus when setting decimal fields. The correct state for
            # the hemisphere buttons is applied in setDmsInputFromDecimal() later.
            xDec = abs(point.x())
            yDec = abs(point.y())
        else:
            precision = 3
            xDec = point.x()
            yDec = point.y()

        self.inLeftDec.setText(QLocale().toString(
            xDec if self._eastingLeftNorthingRight else yDec, "f", precision))
        self.inRightDec.setText(QLocale().toString(
            yDec if self._eastingLeftNorthingRight else xDec, "f", precision))

        self.setDmsInputFromDecimal(point.x(), point.y())

        self.__inputFieldsChangedInternal()

    def setDmsInputFromDecimal(self, x, y, setEastingNorthingInversion=True):
        xDMS = coordinatorDecimalToDms(x)
        yDMS = coordinatorDecimalToDms(y)

        #QgsMessageLog.logMessage(str(xDMS))

        if (setEastingNorthingInversion):
            self.setEastingInverted(xDMS[0])
            self.setNorthingInverted(yDMS[0])

        leftDMS = xDMS if self._eastingLeftNorthingRight else yDMS
        rightDMS = yDMS if self._eastingLeftNorthingRight else xDMS

        self.inLeft.setText(QLocale().toString(leftDMS[1]))
        self.inLeftMin.setText(QLocale().toString(leftDMS[2]))
        self.inLeftSec.setText(QLocale().toString(leftDMS[3], "f", 2))

        self.inRight.setText(QLocale().toString(rightDMS[1]))
        self.inRightMin.setText(QLocale().toString(rightDMS[2]))
        self.inRightSec.setText(QLocale().toString(rightDMS[3], "f", 2))

    def setResultPoint(self, point):
        if self.sectionIsGeographic[CoordinatorDockWidget.SectionOutput]:
            if (self.resultAsDec.isChecked()):
                f = QgsCoordinateFormatter.FormatDecimalDegrees
                precision = 9
            else:
                f = QgsCoordinateFormatter.FormatDegreesMinutesSeconds
                precision = 3

            transformedX = QgsCoordinateFormatter.formatX(
                point.x(), f, precision)
            transformedY = QgsCoordinateFormatter.formatY(
                point.y(), f, precision)
            # if we use FormatDecimalDegrees QgsCoordinateFormatter produces
            # a string just with a QString::number(). Therefore the string is NOT
            # localized. But QgsCoordinateFormatter provides coordinate wrapping already
            # so we just hack the correct decimal sign into the string and should be
            # fine... juuust fine!
            decPoint = QLocale().decimalPoint()
            #coordinatorLog("Decimal Point: %s" % decPoint)
            if (decPoint != "."):
                #coordinatorLog("replace!!")
                transformedX = transformedX.replace(".", decPoint)
                transformedY = transformedY.replace(".", decPoint)

        else:
            #self.log(" -> using decimal output")
            transformedX = QLocale().toString(point.x(), 'f', 2)
            transformedY = QLocale().toString(point.y(), 'f', 2)

        self.setResult(transformedX, transformedY)

    def setResult(self, x, y):
        if (self._eastingLeftNorthingRight):
            self.resultLeft.setText(x)
            self.resultRight.setText(y)
        else:
            self.resultLeft.setText(y)
            self.resultRight.setText(x)

    def setWarningMessage(self, message):
        if (message and len(message) > 0):
            self.messageIcon.setPixmap(self.warningPixmap)
            self.messageIcon.show()
            self.messageText.setText(message)
        else:
            self.hideMessages()

    def showInfoMessage(self, message, hideAfterMilliseconds=None):
        self.messageIcon.clear()
        self.messageText.setText(message)
        if (hideAfterMilliseconds):
            self.messageHideTimer.setInterval(hideAfterMilliseconds)
            self.messageHideTimer.start()

    def hideMessages(self):
        self.messageIcon.clear()
        self.messageIcon.hide()
        self.messageText.clear()

    def clearFieldsInSection(self, section):
        if (section & self.SectionInput):
            for inputField in self._widgetlistInput:
                inputField.setText("")
        if (section & self.SectionOutput):
            for inputField in [
                    self.resultRight,
                    self.resultLeft,
            ]:
                inputField.setText("")

    def clearSection(self, section):
        self.clearFieldsInSection(section)

        # the following code will also reset
        # the buttons designating the hemisphere:
        #if (section & self.SectionInput ) :
        #    self.setEastingInverted(False)
        #    self.setNorthingInverted(False)

        self.addFeatureButton.setEnabled(False)

        self.inputChanged.emit()

    def calculateDecimalDegreesFromDMS(self):
        #QgsMessageLog.logMessage("calc DMS", "Coordinator")
        valueLeft = coordinatorDmsStringsToDecimal(self.inLeft.text(),
                                                   self.inLeftMin.text(),
                                                   self.inLeftSec.text())
        valueRight = coordinatorDmsStringsToDecimal(self.inRight.text(),
                                                    self.inRightMin.text(),
                                                    self.inRightSec.text())
        return (valueLeft, valueRight)

    def hasInput(self):
        if self.leftDMS.isVisible():
            hasInput = False
            for field in self._widgetlistInputDms:
                if len(field.text()) > 0:
                    hasInput = True
                    break
        else:
            hasInput = (len(self.inLeftDec.text()) > 0
                        or len(self.inRightDec.text()) > 0)
        return hasInput

    def __rawInputCoordinates(self):
        if (self.leftDMS.isVisible()):
            return (self.calculateDecimalDegreesFromDMS())
        else:
            valueLeft = 0.0
            valueRight = 0.0
            if (len(self.inLeftDec.text())):
                valueLeft = QLocale().toFloat(self.inLeftDec.text())[0]
            if (len(self.inRightDec.text())):
                valueRight = QLocale().toFloat(self.inRightDec.text())[0]

            return (valueLeft, valueRight)

    def inputCoordinates(self):
        (valueLeft, valueRight) = self.__rawInputCoordinates()

        if (self._eastingLeftNorthingRight):
            inX = valueLeft
            inY = valueRight
        else:
            inX = valueRight
            inY = valueLeft

        if (self.sectionIsGeographic[self.SectionInput]
                and self._eastingInverted):
            inX *= -1
        if (self.sectionIsGeographic[self.SectionInput]
                and self._northingInverted):
            inY *= -1

        return (inX, inY)

    def setNorthingInverted(self, northingInverted):
        self._northingInverted = northingInverted
        label = self.tr("S", "lblSouth") if northingInverted else self.tr(
            "N", "lblNorth")
        if (self._eastingLeftNorthingRight):
            self.rightDirButton.setText(label)
        else:
            self.leftDirButton.setText(label)

    def setEastingInverted(self, eastingInverted):
        self._eastingInverted = eastingInverted
        label = self.tr("W", "lblWest") if eastingInverted else self.tr(
            "E", "lblEast")
        if (self._eastingLeftNorthingRight):
            self.leftDirButton.setText(label)
        else:
            self.rightDirButton.setText(label)

    def toggleCardinalDirectionButton(self, button):
        if (self._eastingLeftNorthingRight):
            toggleEasting = (button == self.leftDirButton)
        else:
            toggleEasting = (button == self.rightDirButton)

        if (toggleEasting):
            self.setEastingInverted(not self._eastingInverted)
        else:
            self.setNorthingInverted(not self._northingInverted)

        self.inputChanged.emit()

    def toggledMapConnection(self, section, isConnected):
        if section == CoordinatorDockWidget.SectionOutput:
            self.outputCrs.setEnabled(not isConnected)

        self.mapConnectionChanged.emit(section, isConnected)

    def __inputFieldsChangedInternal(self):
        #coordinatorLog("fields changed internal triggered")

        self._setToolsEnabled(self.hasInput())

        if (self.hasInput()):

            if (self.leftDMS.isVisible()):
                (leftValue, rightValue) = self.calculateDecimalDegreesFromDMS()
                self.inLeftDec.setText(QLocale().toString(leftValue, "f", 9))
                self.inRightDec.setText(QLocale().toString(rightValue, "f", 9))
            else:
                # calculate DMS Values

                leftValue = QLocale().toFloat(self.inLeftDec.text())[0]
                rightValue = QLocale().toFloat(self.inRightDec.text())[0]

                x = leftValue if self._eastingLeftNorthingRight else rightValue
                y = rightValue if self._eastingLeftNorthingRight else leftValue

                self.setDmsInputFromDecimal(x, y, False)
        else:
            if (self.leftDMS.isVisible()):
                self.inLeftDec.setText(None)
                self.inRightDec.setText(None)
            else:
                for f in self._widgetlistInputDms:
                    f.setText(None)

        self.inputChanged.emit()

    def copyResultToClipBoard(self, which):
        cb = QApplication.clipboard()
        cb.clear(mode=cb.Clipboard)
        if which == CoordinatorDockWidget.SideLeft:
            content = self.resultLeft.text()
        elif which == CoordinatorDockWidget.SideRight:
            content = self.resultRight.text()
        else:
            content = "%s\t%s" % (self.resultLeft.text(),
                                  self.resultRight.text())

        cb.setText(content, mode=cb.Clipboard)

    def showHelpButtonClicked(self):

        if not hasattr(self, "helpview"):
            helpBasepath = "%s/help" % os.path.normpath(
                os.path.dirname(__file__))
            helpUrl = QUrl(
                pathlib.Path("%s/index.html" % helpBasepath).as_uri())
            cssUrl = QUrl(pathlib.Path("%s/help.css" % helpBasepath).as_uri())
            self.helpview = QWebView()
            self.helpview.settings().setUserStyleSheetUrl(cssUrl)

            self.helpview.load(helpUrl)

        self.helpview.show()

    def closeEvent(self, event):
        self.closingPlugin.emit()
        event.accept()