# -*- coding: utf-8 -*-
"""
/***************************************************************************
InspectFeatures
                                 A QGIS plugin
Builds a temp rubberband with a given size and shape.
                             -------------------
        begin                : 2016-08-02
        git sha              : $Format:%H$
        copyright            : (C) 2016 by  Jossan Costa - Surveying Technician @ Brazilian Army
        email                : jossan.costa@eb.mil.br
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/
"""
import os
from qgis.PyQt.QtWidgets import QMessageBox, QSpinBox, QAction, QWidget
from qgis.PyQt.QtGui import QIcon
from qgis.PyQt.QtCore import QSettings, pyqtSignal, pyqtSlot, QObject, Qt
from qgis.PyQt import QtGui, uic, QtCore

from qgis.core import (
    QgsMapLayer,
    Qgis,
    QgsVectorLayer,
    QgsCoordinateReferenceSystem,
    QgsCoordinateTransform,
    QgsFeatureRequest,
    QgsWkbTypes,
    QgsProject,
)
from qgis.gui import QgsMessageBar

from .inspectFeatures_ui import Ui_Form

# FORM_CLASS, _ = uic.loadUiType(os.path.join(
#     os.path.dirname(__file__), 'inspectFeatures.ui'))


class InspectFeatures(QWidget, Ui_Form):
    # idxChanged = pyqtSignal(int)
    def __init__(self, iface, parent=None):
        """
        Constructor
        """
        super(InspectFeatures, self).__init__(parent)
        self.setupUi(self)
        self.parent = parent
        self.splitter.hide()
        self.splitter2.hide()
        self.iface = iface
        # self.iface.currentLayerChanged.connect(self.enableScale)
        self.mMapLayerComboBox.layerChanged.connect(self.enableScale)
        self.mMapLayerComboBox.layerChanged.connect(
            self.mFieldExpressionWidget.setLayer
        )
        self.mMapLayerComboBox.layerChanged.connect(self.mFieldComboBox.setLayer)
        if not self.iface.activeLayer():
            self.enableTool(False)
        # self.iface.currentLayerChanged.connect(self.enableTool)
        self.mMapLayerComboBox.layerChanged.connect(self.enableTool)
        self.zoomPercentageSpinBox.setMinimum(0)
        self.zoomPercentageSpinBox.setMaximum(100)
        self.zoomPercentageSpinBox.setDecimals(3)
        self.zoomPercentageSpinBox.setSingleStep(1)
        self.zoomPercentageSpinBox.setSuffix("%")
        self.zoomPercentageSpinBox.setValue(100)
        self.zoomPercentageSpinBox.setEnabled(False)
        self.zoomPercentageSpinBox.hide()
        self.mScaleWidget.setScaleString("1:40000")
        self.mScaleWidget.setEnabled(False)
        self.mScaleWidget.hide()
        self.enableScale()
        self.canvas = self.iface.mapCanvas()
        self.allLayers = {}
        # self.idxChanged.connect(self.setNewId)
        self.setToolTip("")
        icon_path = ":/plugins/DsgTools/icons/inspectFeatures.png"
        text = self.tr("DSGTools: Inspect Features")
        self.activateToolAction = self.add_action(
            icon_path, text, self.inspectPushButton.toggle, parent=self.parent
        )
        self.iface.registerMainWindowAction(self.activateToolAction, "")
        icon_path = ":/plugins/DsgTools/icons/backInspect.png"
        text = self.tr("DSGTools: Back Inspect")
        self.backButtonAction = self.add_action(
            icon_path, text, self.backInspectButton.click, parent=self.parent
        )
        self.iface.registerMainWindowAction(self.backButtonAction, "")
        icon_path = ":/plugins/DsgTools/icons/nextInspect.png"
        text = self.tr("DSGTools: Next Inspect")
        self.nextButtonAction = self.add_action(
            icon_path, text, self.nextInspectButton.click, parent=self.parent
        )
        self.iface.registerMainWindowAction(self.nextButtonAction, "")
        icon_path = ":/plugins/DsgTools/icons/reload.png"
        text = self.tr("DSGTools: Set Active Layer on Feature Inspector")
        self.refreshPushButtonAction = self.add_action(
            icon_path, text, self.refreshPushButton.click, parent=self.parent
        )
        self.iface.registerMainWindowAction(self.refreshPushButtonAction, "")
        self.refreshPushButton.setToolTip(
            self.tr("Set current layer as selected layer on inspect tool")
        )
        self.sortPushButton.setToolTip(self.tr("Sort by attribute"))
        self.mFieldExpressionWidget.fieldChanged.connect(self.resetCurrentIndex)

    def add_action(self, icon_path, text, callback, parent=None):
        icon = QIcon(icon_path)
        action = QAction(icon, text, parent)
        action.triggered.connect(callback)
        if parent:
            parent.addAction(action)
        return action

    def getIterateLayer(self):
        return self.mMapLayerComboBox.currentLayer()

    def enableTool(self, enabled=True):
        allowed = (
            False
            if enabled == None or not isinstance(enabled, QgsVectorLayer)
            else True
        )
        toggled = self.inspectPushButton.isChecked()
        enabled = allowed and toggled
        self.backInspectButton.setEnabled(enabled)
        self.nextInspectButton.setEnabled(enabled)
        self.idSpinBox.setEnabled(enabled)
        self.sortPushButton.setEnabled(enabled)

    def enableScale(self):
        """
        The scale combo should only be enabled for point layers
        """
        currentLayer = self.getIterateLayer()
        if not currentLayer:
            return
        if currentLayer.type() != QgsMapLayer.LayerType.VectorLayer:
            return
        if currentLayer.geometryType() == QgsWkbTypes.GeometryType.PointGeometry:
            self.mScaleWidget.setEnabled(True)
            self.mScaleWidget.show()
            self.zoomPercentageSpinBox.setEnabled(False)
            self.zoomPercentageSpinBox.hide()
        else:
            self.mScaleWidget.setEnabled(False)
            self.mScaleWidget.hide()
            self.zoomPercentageSpinBox.setEnabled(True)
            self.zoomPercentageSpinBox.show()

    @pyqtSlot(bool)
    def on_nextInspectButton_clicked(self):
        """
        Inspects the next feature
        """
        if self.nextInspectButton.isEnabled():
            method = getattr(self, "testIndexFoward")
            self.iterateFeature(method)

    def testIndexFoward(self, index, maxIndex, minIndex):
        """
        Gets the next index
        """
        index += 1
        if index > maxIndex:
            index = minIndex
        return index

    def testIndexBackwards(self, index, maxIndex, minIndex):
        """
        gets the previous index
        """
        index -= 1
        if index < minIndex:
            index = maxIndex
        return index

    @pyqtSlot(bool)
    def on_backInspectButton_clicked(self):
        """
        Inspects the previous feature
        """
        if self.backInspectButton.isEnabled():
            method = getattr(self, "testIndexBackwards")
            self.iterateFeature(method)

    @pyqtSlot(int, name="on_idSpinBox_valueChanged")
    def setNewId(self, newId):
        if not isinstance(self.sender(), QSpinBox):
            self.idSpinBox.setValue(newId)
        else:
            currentLayer = self.getIterateLayer()
            lyrId = currentLayer.id()
            if lyrId not in list(self.allLayers.keys()):
                self.allLayers[lyrId] = 0
                return
            oldIndex = self.allLayers[lyrId]
            if oldIndex == 0:
                return
            featIdList = self.getFeatIdList(currentLayer)
            if oldIndex not in featIdList:
                oldIndex = 0
            zoom = (
                self.mScaleWidget.scale()
                if currentLayer.geometryType() == QgsWkbTypes.GeometryType.PointGeometry
                else self.zoomPercentageSpinBox.value()
            )
            if oldIndex == newId:
                # self.iface.messageBar().pushMessage(self.tr('Warning!'), self.tr('Selected id does not exist in layer {0}. Returned to previous id.').format(lyrName), level=Qgis.Warning, duration=2)
                return
            try:
                index = featIdList.index(newId)
                self.allLayers[lyrId] = index
                self.makeZoom(zoom, currentLayer, newId)
                self.idSpinBox.setSuffix(f" ({index + 1}/{len(featIdList)})")
            except:
                # self.iface.messageBar().pushMessage(self.tr('Warning!'), self.tr('Selected id does not exist in layer {0}. Returned to previous id.').format(lyrName), level=Qgis.Warning, duration=2)
                self.idSpinBox.setValue(oldIndex)
                self.makeZoom(zoom, currentLayer, oldIndex)

    def getFeatIdList(self, currentLayer):
        # getting all features ids
        if (
            self.mFieldExpressionWidget.currentText() != ""
            and not self.mFieldExpressionWidget.isValidExpression()
        ):
            self.iface.messageBar().pushMessage(
                self.tr("Warning!"),
                self.tr("Invalid attribute filter!"),
                level=Qgis.MessageLevel.Warning,
                duration=2,
            )
            return []
        request = QgsFeatureRequest()
        request.setFlags(QgsFeatureRequest.NoGeometry)
        if self.mFieldExpressionWidget.currentText() != "":
            request.setFilterExpression(self.mFieldExpressionWidget.asExpression())
        clauseList = []
        if self.sortPushButton.isChecked():
            # order by some attribute
            clause = QgsFeatureRequest.OrderByClause(
                self.mFieldComboBox.currentField(),
                self.ascRadioButton.isChecked(),
                False,
            )
            clauseList.append(clause)
        clauseId = QgsFeatureRequest.OrderByClause(
            "$id", ascending=self.ascRadioButton.isChecked()
        )
        clauseList.append(clauseId)
        orderby = QgsFeatureRequest.OrderBy(clauseList)
        request.setOrderBy(orderby)
        featIdList = [i.id() for i in currentLayer.getFeatures(request)]
        # sort is faster than sorted (but sort is just available for lists)
        return featIdList

    def iterateFeature(self, method):
        """
        Iterates over the features selecting and zooming to the desired one
        method: method used to determine the desired feature index
        """
        currentLayer = self.getIterateLayer()
        lyrName = currentLayer.name()

        zoom = (
            self.mScaleWidget.scale()
            if currentLayer.geometryType() == QgsWkbTypes.GeometryType.PointGeometry
            else self.zoomPercentageSpinBox.value()
        )

        featIdList = self.getFeatIdList(currentLayer)

        if not currentLayer or len(featIdList) == 0:
            self.errorMessage()
            return

        # checking if this is the first time for this layer (currentLayer)
        first = False
        if lyrName not in list(self.allLayers.keys()):
            self.allLayers[lyrName] = 0
            first = True

        # getting the current index
        index = self.allLayers[lyrName]

        # getting max and min ids
        # this was made because the list is already sorted, there's no need to calculate max and min
        maxIndex = len(featIdList) - 1
        minIndex = 0

        if index > maxIndex:
            index = minIndex
            first = True

        self.idSpinBox.setMaximum(maxIndex)
        self.idSpinBox.setMinimum(minIndex)

        # getting the new index
        if not first:
            index = method(index, maxIndex, minIndex)
        self.idSpinBox.setSuffix(" ({0}/{1})".format(index + 1, len(featIdList)))
        self.allLayers[lyrName] = index

        try:
            id = featIdList[index]
        except IndexError:
            index = 0
            self.allLayers[lyrName] = index
            if len(featIdList) > 0:
                id = featIdList[index]
            else:
                self.errorMessage()
                return

        self.makeZoom(zoom, currentLayer, id)
        self.selectLayer(id, currentLayer)

    def errorMessage(self):
        """
        Shows am error message
        """
        QMessageBox.warning(
            self.iface.mainWindow(),
            self.tr("ERROR:"),
            self.tr(
                "<font color=red>There are no features in the current layer:<br></font><font color=blue>Add features and try again!</font>"
            ),
            QMessageBox.StandardButton.Close,
        )

    def selectLayer(self, index, currentLayer):
        """
        Remove current layer feature selection
        currentLayer: layer that will have the feature selection removed
        """
        if currentLayer:
            currentLayer.removeSelection()
            currentLayer.select(index)

    def zoomToLayer(self, layer, zoom=None):
        box = layer.boundingBoxOfSelected()
        if zoom is not None:
            box.grow(min(box.width(), box.height()) * (100 - zoom) / 100)
        # Defining the crs from src and destiny
        epsg = self.iface.mapCanvas().mapSettings().destinationCrs().authid()
        crsDest = QgsCoordinateReferenceSystem(epsg)
        # getting srid from something like 'EPSG:31983'
        if not layer:
            layer = self.iface.mapCanvas().currentLayer()
        srid = layer.crs().authid()
        crsSrc = QgsCoordinateReferenceSystem(
            srid
        )  # here we have to put authid, not srid
        # Creating a transformer
        coordinateTransformer = QgsCoordinateTransform(
            crsSrc, crsDest, QgsProject.instance()
        )
        newBox = coordinateTransformer.transform(box)

        self.iface.mapCanvas().setExtent(newBox)
        self.iface.mapCanvas().refresh()

    def zoomFeature(self, zoom, idDict=None):
        """
        Zooms to current layer selected features according to a specific zoom
        zoom: zoom to be applied
        """
        idDict = dict() if idDict is None else idDict
        currentLayer = self.getIterateLayer()
        if idDict == {}:
            self.zoomToLayer(currentLayer, zoom=float(zoom))
        else:
            id = idDict["id"]
            lyr = idDict["lyr"]
            selectIdList = lyr.selectedFeatureIds()
            lyr.removeSelection()
            lyr.selectByIds([id])
            self.zoomToLayer(layer=lyr, zoom=float(zoom))
            lyr.selectByIds(selectIdList)

        if (
            self.getIterateLayer().geometryType()
            == QgsWkbTypes.GeometryType.PointGeometry
        ):
            self.iface.mapCanvas().zoomScale(float(zoom))

    @pyqtSlot(bool, name="on_inspectPushButton_toggled")
    def toggleBar(self, toggled=None):
        """
        Shows/Hides the tool bar
        """
        if toggled is None:
            toggled = self.inspectPushButton.isChecked()
        if toggled:
            self.splitter.show()
            self.enableTool(self.mMapLayerComboBox.currentLayer())
            self.setToolTip(self.tr("Select a vector layer to enable tool"))
        else:
            self.splitter.hide()
            self.enableTool(False)
            self.setToolTip("")

    @pyqtSlot(bool, name="on_sortPushButton_toggled")
    def toggleSort(self, toggled=None):
        """
        Shows/hides the sort options
        """
        if toggled is None:
            toggled = self.sortPushButton.isChecked()
        if toggled:
            self.splitter2.show()
            self.resetCurrentIndex()
        else:
            self.splitter2.hide()

    def resetCurrentIndex(self, indexName=None):
        lyr = self.mMapLayerComboBox.currentLayer()
        if lyr is not None:
            lyrId = lyr.id()
            self.allLayers[lyrId] = 0

    def setValues(self, featIdList, currentLayer):
        lyrId = currentLayer.id()
        featIdList.sort()
        self.allLayers[lyrId] = 0

        maxIndex = len(featIdList) - 1
        minIndex = 0

        self.idSpinBox.setMaximum(featIdList[maxIndex])
        self.idSpinBox.setMinimum(featIdList[minIndex])

        # getting the new feature id
        id = featIdList[0]

        # adjustin the spin box value
        # self.idxChanged.emit(id)
        # self.idSpinBox.setValue(id)

        zoom = self.mScaleWidget.scale()
        self.makeZoom(zoom, currentLayer, id)

    def makeZoom(self, zoom, currentLayer, id):
        # selecting and zooming to the feature
        # if not self.onlySelectedRadioButton.isChecked():
        #     self.selectLayer(id, currentLayer)
        #     self.zoomFeature(zoom)
        # else:
        if self.usePanCkb.isChecked():
            currentLayer.select(id)
            self.iface.mapCanvas().panToFeatureIds(currentLayer, [id])
            return
        self.zoomFeature(zoom, idDict={"id": id, "lyr": currentLayer})

    @pyqtSlot(bool)
    def on_onlySelectedRadioButton_toggled(self, toggled):
        currentLayer = self.getIterateLayer()
        if toggled:
            featIdList = currentLayer.selectedFeatureIds()
            self.setValues(featIdList, currentLayer)
            self.idSpinBox.setEnabled(False)
        else:
            featIdList = currentLayer.allFeatureIds()
            self.setValues(featIdList, currentLayer)
            self.idSpinBox.setEnabled(True)

    @pyqtSlot(bool)
    def on_refreshPushButton_clicked(self):
        activeLayer = self.iface.activeLayer()
        if isinstance(activeLayer, QgsVectorLayer):
            self.mMapLayerComboBox.setLayer(activeLayer)
        else:
            self.iface.messageBar().pushMessage(
                self.tr("Warning!"),
                self.tr("Active layer is not valid to be used in this tool."),
                level=Qgis.MessageLevel.Warning,
                duration=2,
            )
        self.mFieldExpressionWidget.setExpression("")

    def unload(self):
        try:
            self.iface.unregisterMainWindowAction(self.activateToolAction)
        except:
            pass
        try:
            self.iface.unregisterMainWindowAction(self.backButtonAction)
        except:
            pass
        try:
            self.iface.unregisterMainWindowAction(self.nextButtonAction)
        except:
            pass
        try:
            self.iface.unregisterMainWindowAction(self.refreshPushButtonAction)
        except:
            pass

    def getToolState(self) -> dict:
        currentLayer = self.mMapLayerComboBox.currentLayer()
        return {
            "bar_is_toggled": self.inspectPushButton.isChecked(),
            "current_layer": currentLayer.id(),
            "current_feature_state_dict": self.allLayers,
            "current_zoom": self.mScaleWidget.scale()
            if currentLayer.geometryType() == QgsWkbTypes.GeometryType.PointGeometry
            else self.zoomPercentageSpinBox.value(),
            "use_pan": self.usePanCkb.isChecked(),
            "current_filter_expression": self.mFieldExpressionWidget.currentText(),
            "sort_is_toggled": self.sortPushButton.isChecked(),
            "current_sort_field": self.mFieldComboBox.currentField(),
            "is_ascending": self.ascRadioButton.isChecked(),
        }

    def setToolState(self, stateDict: dict) -> bool:
        isBarToggled = stateDict.get("bar_is_toggled", None)
        if isBarToggled is None:
            return False
        if isBarToggled:
            if not self.inspectPushButton.isChecked():
                self.inspectPushButton.click()  # sim, é intencional clicar duas vezes
        currentLayerId = stateDict.get("current_layer", None)
        if currentLayerId is None:
            return False
        currentLayer = QgsProject.instance().mapLayers().get(currentLayerId, None)
        if currentLayer is None:
            return False
        self.mMapLayerComboBox.setLayer(currentLayer)
        currentStateDict = stateDict.get("current_feature_state_dict", None)
        if currentStateDict is None:
            return False
        self.allLayers.update(currentStateDict)
        currentZoom = stateDict.get("current_zoom", None)
        if currentLayer.geometryType() == QgsWkbTypes.GeometryType.PointGeometry:
            self.mScaleWidget.setScale(currentZoom)
        else:
            self.zoomPercentageSpinBox.setValue(currentZoom)
        usePan = stateDict.get("use_pan", None)
        if usePan is None:
            return False
        self.usePanCkb.setChecked(usePan)
        currentFilterExpression = stateDict.get("current_filter_expression", None)
        if currentFilterExpression is None:
            return False
        self.mFieldExpressionWidget.setExpression(currentFilterExpression)
        isSortToggled = stateDict.get("sort_is_toggled", None)
        if not isSortToggled:
            return True
        self.sortPushButton.click()
        currentSortField = stateDict.get("current_sort_field", None)
        if currentSortField is None:
            return False
        self.mFieldComboBox.setField(currentSortField)
        isAscending = stateDict.get("is_ascending", None)
        if isAscending is None:
            return False
        if not isAscending:
            self.descRadioButton.click()
        return True
