class SimilarityPlugin:
    """
    Similarity Plugin parent class
    
    ..

    Attributes
    -----------

    dlg : SimilarityPluginDialog
        MainPluginDialog
    simpleDialog : SimpleWarningDialog
        Show simple warning
    similarLayer : list=[]
        The result of calculation process
    previewLayer: int=0
        Current index similarLayer that previewed in canvas widget  
    calcThread : QThread(self.iface)
        Thread for data processing
    calcTask : CalculationModule
        Calculation module for checking similarity
    iface : QgsInterface
        An interface instance that will be passed to this class
        which provides the hook by which you can manipulate the QGIS
        application at run time.

    """

    layer: QgsVectorLayer  #: First layer
    layer2: QgsVectorLayer  #: Second layer
    simpleDialog: SimpleWarnDialog  #: Simple warning dialog

    def __init__(self, iface):
        """Constructor.

        :param iface: An interface instance that will be passed to this class
            which provides the hook by which you can manipulate the QGIS
            application at run time.
        :type iface: QgsInterface
        """
        self.similarLayer = []
        """  """
        self.previewLayer = 0
        self.calcTask = CalculationModule()
        # Save reference to the QGIS interface
        self.iface = iface

        # Registering worker calculation
        self.calcThread = QThread(self.iface)
        self.calcTask.moveToThread(self.calcThread)
        self.calcThread.started.connect(self.calcTask.run)
        self.calcThread.setTerminationEnabled(True)

        # multithreading signal calculation
        self.calcTask.progress.connect(self.updateCalcProgress)
        self.calcTask.progressSim.connect(self.updateSimList)
        self.calcTask.finished.connect(self.finishedCalcThread)
        self.calcTask.error.connect(self.errorCalcThread)
        self.calcTask.eventTask.connect(self.eventCalcThread)

        # pan event
        self.actionPan = QAction("Pan", self.iface)

        # initialize plugin directory
        self.plugin_dir = os.path.dirname(__file__)
        # initialize locale
        locale = QSettings().value('locale/userLocale')[0:2]
        locale_path = os.path.join(self.plugin_dir, 'i18n',
                                   'SimilarityPlugin_{}.qm'.format(locale))

        if os.path.exists(locale_path):
            self.translator = QTranslator()
            self.translator.load(locale_path)
            QCoreApplication.installTranslator(self.translator)

        # Declare instance attributes
        self.actions = []
        self.menu = self.tr(u'&Calculate Similarity Map')

        # Check if plugin was started the first time in current QGIS session
        # Must be set in initGui() to survive plugin reloads
        self.first_start = None

    def tr(self, message):
        """Get the translation for a string using Qt translation API.

        We implement this ourselves since we do not inherit QObject.

        :param message: String for translation.
        :type message: str, QString

        :returns: Translated version of message.
        :rtype: QString
        """
        # noinspection PyTypeChecker,PyArgumentList,PyCallByClass
        return QCoreApplication.translate('SimilarityPlugin', message)

    def add_action(self,
                   icon_path,
                   text,
                   callback,
                   enabled_flag=True,
                   add_to_menu=True,
                   add_to_toolbar=True,
                   status_tip=None,
                   whats_this=None,
                   parent=None):
        """Add a toolbar icon to the toolbar.

        :param icon_path: Path to the icon for this action. Can be a resource
            path (e.g. ':/plugins/foo/bar.png') or a normal file system path.
        :type icon_path: str

        :param text: Text that should be shown in menu items for this action.
        :type text: str

        :param callback: Function to be called when the action is triggered.
        :type callback: function

        :param enabled_flag: A flag indicating if the action should be enabled
            by default. Defaults to True.
        :type enabled_flag: bool

        :param add_to_menu: Flag indicating whether the action should also
            be added to the menu. Defaults to True.
        :type add_to_menu: bool

        :param add_to_toolbar: Flag indicating whether the action should also
            be added to the toolbar. Defaults to True.
        :type add_to_toolbar: bool

        :param status_tip: Optional text to show in a popup when mouse pointer
            hovers over the action.
        :type status_tip: str

        :param parent: Parent widget for the new action. Defaults None.
        :type parent: QWidget

        :param whats_this: Optional text to show in the status bar when the
            mouse pointer hovers over the action.

        :returns: The action that was created. Note that the action is also
            added to self.actions list.
        :rtype: QAction
        """

        icon = QIcon(icon_path)
        action = QAction(icon, text, parent)
        action.triggered.connect(callback)
        action.setEnabled(enabled_flag)

        if status_tip is not None:
            action.setStatusTip(status_tip)

        if whats_this is not None:
            action.setWhatsThis(whats_this)

        if add_to_toolbar:
            # Adds plugin icon to Plugins toolbar
            self.iface.addToolBarIcon(action)

        if add_to_menu:
            self.iface.addPluginToVectorMenu(self.menu, action)

        self.actions.append(action)

        return action

    def initGui(self):
        """Create the menu entries and toolbar icons inside the QGIS GUI."""

        icon_path = ':/plugins/similarity_plugin/icon-24.png'
        self.add_action(icon_path,
                        text=self.tr(u'Check Similarity ...'),
                        callback=self.run,
                        parent=self.iface.mainWindow())

        # will be set False in run()
        self.first_start = True

    def unload(self):
        """Removes the plugin menu item and icon from QGIS GUI."""
        for action in self.actions:
            self.iface.removePluginVectorMenu(
                self.tr(u'&Calculate Similarity Map'), action)
            self.iface.removeToolBarIcon(action)

    def methodChange(self):
        """Signal when method changed"""
        if self.dlg.methodComboBox.currentIndex() == 2:
            # self.dlg.mergeCenterCheck.setChecked(False)
            # self.dlg.setPKBtn.setVisible(True)
            self.dlg.mergeCenterCheck.setEnabled(True)
            self.dlg.lineEditTreshold.setEnabled(False)
            self.dlg.nnRadiusEdit.setEnabled(False)
            # self.pkSelector.layerListWidget.clear()
            # self.pkSelector.layerListWidget.addItems(
            #     self.dlg.layerSel1.currentLayer().fields().names()
            # )
            # self.pkSelector.layer2ListWidget.clear()
            # self.pkSelector.layer2ListWidget.addItems(
            #     self.dlg.layerSel2.currentLayer().fields().names()
            # )
            # self.pkSelector.open()
        elif self.dlg.methodComboBox.currentIndex() == 0:
            self.dlg.mergeCenterCheck.setChecked(False)
            self.dlg.mergeCenterCheck.setEnabled(False)
            # self.dlg.setPKBtn.setVisible(False)
            self.dlg.lineEditTreshold.setEnabled(True)
            self.dlg.nnRadiusEdit.setEnabled(False)
        elif self.dlg.methodComboBox.currentIndex() == 1:
            self.dlg.mergeCenterCheck.setChecked(True)
            self.dlg.mergeCenterCheck.setEnabled(False)
            # self.dlg.setPKBtn.setVisible(False)
            self.dlg.lineEditTreshold.setEnabled(True)
            self.dlg.nnRadiusEdit.setEnabled(True)

    def resultPreview(self):
        """Activate preview section

        This method will called if calculation process is finished

        See Also
        ----------
            refreshPreview()
            SimilarityPluginDialog.widgetCanvas
            SimilarityPluginDialog.nextBtn
            SimilarityPluginDialog.previousBtn
            SimilarityPluginDialog.removeBtn

        """
        self.previewLayer = 0
        self.refreshPreview()

        self.dlg.widgetCanvas.enableAntiAliasing(True)

        self.dlg.nextBtn.setEnabled(True)
        self.dlg.previousBtn.setEnabled(True)
        self.dlg.removeBtn.setEnabled(True)

    def attrPrinter(self, fieldsList: object, feature: QgsFeature,
                    place: QTextEdit):
        """print the attribute table on preview panel

        :param fieldsList object: List the attribute value of feature
        :param feature QgsFeature: The feature will be printed
        :param place QTextEdit: The place for editing text
        
        """
        temp = ''

        for f in fieldsList:
            temp += f.name()
            temp += ' : '
            temp += str(feature.attribute(f.name()))
            temp += '\n'
        # print(place)
        place.setText(temp)

    def refreshPreview(self):
        """refreshing canvas on preview"""
        if len(self.similarLayer) > 0:
            # set the layer
            self.layerCanvas = QgsVectorLayer("Polygon?crs=ESPG:4326",
                                              'SimilarityLayer', 'memory')
            self.layer2Canvas = QgsVectorLayer("Polygon?crs=ESPG:4326",
                                               'SimilarityLayer', 'memory')

            # set the feature
            previewLayerFeature = self.calcTask.getLayersDup()[0].getFeature(
                self.similarLayer[self.previewLayer][0])
            previewLayerFeature2 = self.calcTask.getLayersDup()[1].getFeature(
                self.similarLayer[self.previewLayer][1])

            # set the label score on preview
            scoreLabel = "Score : " + str(
                round(self.similarLayer[self.previewLayer][2], 3))

            # set cumulative score on preview
            if (not self.calcTask.getTranslate()):
                scoreLabel += " - Cumulative score : " + str(
                    self.calcTask.getCumulative(
                        self.similarLayer[self.previewLayer]))

            # show distance if the layer merge centered (NN and WK only)
            if (self.dlg.methodComboBox.currentIndex() == 1
                    or self.dlg.methodComboBox.currentIndex() == 2):
                distance = QgsGeometry.distance(
                    previewLayerFeature.geometry().centroid(),
                    previewLayerFeature2.geometry().centroid())
                if distance < 0.00001:
                    distance = 0
                self.dlg.labelScore.setText(scoreLabel + " - Distance : " +
                                            str(round(distance, 3)))
            else:
                self.dlg.labelScore.setText(scoreLabel)

            self.attrPrinter(
                self.calcTask.getLayersDup()
                [0].dataProvider().fields().toList(), previewLayerFeature,
                self.dlg.previewAttr)
            self.attrPrinter(
                self.calcTask.getLayersDup()
                [1].dataProvider().fields().toList(), previewLayerFeature2,
                self.dlg.previewAttr_2)

            self.layerCanvas.dataProvider().addFeature(previewLayerFeature)

            # translating preview
            if self.calcTask.getTranslate():
                tGeom = self.calcTask.translateCenterGeom(
                    previewLayerFeature2.geometry(),
                    previewLayerFeature.geometry())
                nFeat = QgsFeature(previewLayerFeature2)
                nFeat.setGeometry(tGeom)
                self.layer2Canvas.dataProvider().addFeature(nFeat)
            else:
                self.layer2Canvas.dataProvider().addFeature(
                    previewLayerFeature2)

            # set canvas to preview feature layer
            self.dlg.widgetCanvas.setExtent(
                previewLayerFeature.geometry().boundingBox(), True)

            self.dlg.widgetCanvas.setDestinationCrs(
                self.layerCanvas.sourceCrs())

            symbol = self.layerCanvas.renderer().symbol()
            symbol.setColor(QColor(0, 147, 221, 127))

            symbol2 = self.layer2Canvas.renderer().symbol()
            symbol2.setColor(QColor(231, 120, 23, 127))

            self.dlg.widgetCanvas.setLayers(
                [self.layer2Canvas, self.layerCanvas])

            # redraw the canvas
            self.dlg.widgetCanvas.refresh()

    def nextPreview(self):
        """Next preview signal for next button in preview section"""
        # f2 = open("engine/f2.txt", "w")
        if (self.previewLayer < len(self.similarLayer) - 1):
            self.previewLayer = int(self.previewLayer) + 1
        # self.dlg.consoleTextEdit.setText(self.dlg.consoleTextEdit.toPlainText()+"\n\n Current Similar Layer Index : \n  "+str([self.similarLayer[self.previewLayer], self.previewLayer]))
        self.refreshPreview()

    def previousPreview(self):
        """Previous preview signal"""
        if (self.previewLayer > 0):
            self.previewLayer = int(self.previewLayer) - 1
        # self.dlg.consoleTextEdit.setText(self.dlg.consoleTextEdit.toPlainText()+"\n\n Current Similar Layer Index : \n  "+str([self.similarLayer[self.previewLayer], self.previewLayer]))
        self.refreshPreview()

    def rmFeatResult(self):
        """Removing similarity info current result"""
        self.similarLayer.pop(self.previewLayer)
        if (self.previewLayer > len(self.similarLayer)):
            self.previewLayer = len(self.similarLayer - 1)
        self.refreshPreview()
        self.warnDlg.close()

    def rmWarn(self):
        """prevention remove item preview"""
        self.warnDlg = self.warnDialogInit(
            'Are you sure to delete this feature ?')
        self.warnDlg.yesBtn.clicked.connect(self.rmFeatResult)
        self.warnDlg.noBtn.clicked.connect(self.warnDlg.close)
        self.warnDlg.show()

    def updateCalcProgress(self, value):
        """Progress signal for calcTask"""
        self.dlg.progressBar.setValue(int(round(value, 1)))

    def updateSimList(self, simList: list):
        """Updating similiarity result signal"""
        self.similarLayer.append(simList)
        cText = "Number of Result: " + str(len(self.similarLayer))
        self.dlg.counterLabel.setText(cText)

    # thread signal
    def errorCalcThread(self, value: str):
        """Signal when an error occured"""
        print("error : ", value)
        self.dlg.consoleTextEdit.append("error : " + value + "\n\n")
        self.simpleWarnDialogInit(value)
        self.dlg.calcBtn.setEnabled(True)
        self.dlg.stopBtn.setEnabled(False)

    def finishedCalcThread(self, itemVal: list):
        """signal when calcTask calculation is finished

        :param itemVal list: the returned value emit
        
        """
        # print("finished returned : ", itemVal)
        # self.similarLayer = itemVal
        # self.setLayers(self.calcTask.getLayersDup())
        self.calcThread.exit()
        self.calcTask.kill()
        cText = "Number of Result: " + str(len(self.similarLayer))
        self.dlg.consoleTextEdit.append(cText + "\n\n")
        if len(self.similarLayer) > 0:
            # self.addScoreItem()
            self.dlg.consoleTextEdit.append(
                "The 2 vector layer has been checked\n\n")
            self.previewLayer = 0
            self.dlg.saveBtn.setEnabled(True)
            self.dlg.counterLabel.setText(cText)
            self.resultPreview()
        else:
            self.previewLayer = 0
            self.dlg.counterLabel.setText(cText)
        self.dlg.calcBtn.setEnabled(True)
        self.dlg.stopBtn.setEnabled(False)

    def stopCalcThread(self):
        """Signal when calcTask is stopped """
        self.calcThread.exit()
        self.dlg.eventLabel.setText("Event: Stopped")
        self.calcTask.kill()
        if (self.calcTask.getLayersDup()[0].featureCount() > 0
                and self.calcTask.getLayersDup()[1].featureCount() > 0):
            cText = "Number of Result: " + str(len(self.similarLayer))
            self.dlg.consoleTextEdit.append(cText + "\n\n")
            if len(self.similarLayer) > 0:
                # self.addScoreItem()
                self.previewLayer = 0
                self.dlg.saveBtn.setEnabled(True)
                self.dlg.counterLabel.setText(cText)
                self.resultPreview()
            else:
                self.previewLayer = 0
                self.dlg.counterLabel.setText(cText)
            self.dlg.calcBtn.setEnabled(True)
            self.dlg.stopBtn.setEnabled(False)

    def eventCalcThread(self, value: str):
        """Receiving signal event
        
        :param value str: the returned value emit
        
        """
        self.dlg.eventLabel.setText("Event: " + value)

    # executing calculation
    def calculateScore(self):
        """Signal for executing calculation for cheking maps"""
        if (isinstance(self.dlg.layerSel1.currentLayer(), QgsVectorLayer) and
                isinstance(self.dlg.layerSel1.currentLayer(), QgsVectorLayer)):
            # set plugin to initial condition
            self.dlg.progressBar.setValue(0)
            self.dlg.saveBtn.setEnabled(False)
            self.dlg.nextBtn.setEnabled(False)
            self.dlg.previousBtn.setEnabled(False)
            self.dlg.removeBtn.setEnabled(False)
            self.dlg.widgetCanvas.setLayers([
                QgsVectorLayer("Polygon?crs=ESPG:4326", 'SimilarityLayer',
                               'memory')
            ])
            self.dlg.previewAttr.setText("")
            self.dlg.previewAttr_2.setText("")
            self.dlg.widgetCanvas.refresh()
            scoreLabel = "Score : 0"
            self.dlg.counterLabel.setText("Number of Result: 0")
            self.dlg.labelScore.setText(scoreLabel)
            self.similarLayer = []

            # set input-output option
            self.calcTask.setLayers(self.dlg.layerSel1.currentLayer(),
                                    self.dlg.layerSel2.currentLayer())
            self.calcTask.setTreshold(self.dlg.lineEditTreshold.value())
            self.calcTask.setMethod(int(
                self.dlg.methodComboBox.currentIndex()))
            self.calcTask.setTranslate(self.dlg.mergeCenterCheck.isChecked())
            self.calcTask.setRadius(self.dlg.nnRadiusEdit.value())
            self.calcTask.setSuffix(str(self.dlg.sufLineEdit.text()))
            self.calcTask.setScoreName(str(self.dlg.attrOutLineEdit.text()))
            # print("input option set")
            # activating task
            self.calcTask.alive()
            # print("task alive")
            self.calcThread.start()
            # print("thread started")

            # set button
            self.dlg.calcBtn.setEnabled(False)
            self.dlg.stopBtn.setEnabled(True)
        else:
            # prevention on QgsVectorLayer only
            self.simpleWarnDialogInit("This plugin support Vector Layer only")

    # signal when saveBtn clicked
    def registerToProject(self):
        """Signal to registering project"""
        QgsProject.instance().addMapLayers(self.calcTask.getLayersResult())

    # warning dialog for error or prevention
    def warnDialogInit(self, msg: str):
        """This dialog have Yes and No button.
        :param msg: str Display the warning message 
        """

        dialog = WarnDialog()
        #set the message
        dialog.msgLabel.setText(msg)
        return dialog

    # initializing simple warning dialog
    def simpleWarnDialogInit(self, msg: str):
        """ This dialog have ok button only
        
        :param: msg str: Display the warning message 
        
        """

        # Set the message
        self.simpleDialog.msgLabel.setText(msg)
        self.simpleDialog.show()

    # def pkSelectorAccepted(self):
    #     if( len(self.pkSelector.layerListWidget.selectedItems()) > 0 and len(self.pkSelector.layer2ListWidget.selectedItems()) > 0 and
    #             (len(self.pkSelector.layerListWidget.selectedItems()) == len(self.pkSelector.layer2ListWidget.selectedItems()))
    #         ):
    #         names = [j.text() for j in self.pkSelector.layerListWidget.selectedItems()]
    #         names.sort()
    #         names2 = [j.text() for j in self.pkSelector.layer2ListWidget.selectedItems()]
    #         names2.sort()
    #         print(names)
    #         print(names2)
    #         self.pkSelector.accept()
    #     else:
    #         self.simpleWarnDialogInit("Primary Key must be same in length as key or not null")

    def run(self):
        """Run method that performs all the real work"""

        # print("Multithreading with maximum %d threads" % self.threadpool.maxThreadCount())

        # init run variable
        # self.canvas = QgsMapCanvas()

        # Create the dialog with elements (after translation) and keep reference
        # Only create GUI ONCE in callback, so that it will only load when the plugin is started
        if self.first_start == True:
            self.previewLayer = 0
            self.currentCheckLayer = [0, 0]
            self.first_start = False
            self.dlg = SimilarityPluginDialog()
            # self.dlg.setPKBtn.setVisible(False)
            self.simpleDialog = SimpleWarnDialog()
            # self.pkSelector = PkSelector()
            # set help documentation
            self.dlg.helpTextBrowser.load(
                QUrl(
                    'https://github.com/panickspa/SimilarityPlugin/wiki/User-Guide'
                ))
            self.dlg.nextHelpBtn.clicked.connect(
                self.dlg.helpTextBrowser.forward)
            self.dlg.previousHelpBtn.clicked.connect(
                self.dlg.helpTextBrowser.back)
            # filtering selection layer (empty layer not allowed)
            self.dlg.layerSel1.setAllowEmptyLayer(False)
            self.dlg.layerSel1.setAllowEmptyLayer(False)

            # self.pkSelector.okPushButton.clicked.connect(self.pkSelectorAccepted)
            # self.dlg.setPKBtn.clicked.connect(self.pkSelector.open)
            # method combobox initialiazation
            self.dlg.methodComboBox.clear()
            self.dlg.methodComboBox.addItems(
                ['Squential', 'Nearest Neightbour', 'Wilkerstat BPS'])

            # registering signal

            self.dlg.methodComboBox.currentIndexChanged.connect(
                self.methodChange)
            self.dlg.nextBtn.clicked.connect(self.nextPreview)
            self.dlg.previousBtn.clicked.connect(self.previousPreview)
            self.dlg.calcBtn.clicked.connect(self.calculateScore)
            self.dlg.saveBtn.clicked.connect(self.registerToProject)
            self.dlg.removeBtn.clicked.connect(self.rmWarn)
            self.dlg.stopBtn.clicked.connect(self.stopCalcThread)

            # intialize pan tool
            panTool = QgsMapToolPan(self.dlg.widgetCanvas)
            # set signal
            panTool.setAction(self.actionPan)
            # set map tool
            self.dlg.widgetCanvas.setMapTool(panTool)
            # set pan tool to be activate
            panTool.activate()

        # show the dialog
        self.dlg.show()
        # Run the dialog event loop
        result = self.dlg.exec_()
        # See if OK was pressed
        if result:
            self.similarLayer = []
            self.dlg.widgetCanvas.setLayers([
                QgsVectorLayer("Polygon?crs=ESPG:4326", 'SimilarityLayer',
                               'memory')
            ])
            self.dlg.previewAttr.setText("")
            self.dlg.previewAttr_2.setText("")
            self.dlg.widgetCanvas.refresh()
            scoreLabel = "Score : 0"
            self.dlg.labelScore.setText(scoreLabel)