# -*- coding: utf-8 -*-
"""
/***************************************************************************
 Aderyn
                                 A QGIS plugin
 This plugin searches the LERC Wales Aderyn wildlife database.
 Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/
                              -------------------
        begin                : 2019-01-09
        git sha              : $Format:%H$
        copyright            : (C) 2019 by Biodiversity Information Service
        email                : enquiry@bis.org.uk
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/
"""
from PyQt5.QtCore import Qt, QSettings, QTranslator, qVersion, QCoreApplication, QVariant
from PyQt5.QtGui import *
from PyQt5.QtWidgets import QAction, QMessageBox, QFileDialog, QProgressBar
from PyQt5.QtSql import *  # http://pyqgis.org/blog/2013/04/11/creating-a-postgresql-connection-from-a-qgis-layer-datasource/
import qgis
from qgis.core import *
from qgis.gui import *
from qgis.utils import *
from . import osgr

# Initialize Qt resources from file resources.py
from .resources import *
# Import the code for the dialog
from .aderyn_dialog import AderynDialog
from .settings_dialog import SettingsDialog
import os.path
import random
import json
import aderyn_query


class Aderyn:
    """QGIS Plugin Implementation."""

    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
        """
        # Save reference to the QGIS interface
        self.iface = iface
        self.canvas = iface.mapCanvas()
        # 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',
            'Aderyn_{}.qm'.format(locale))

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

            if qVersion() > '4.3.3':
                QCoreApplication.installTranslator(self.translator)

        # Create the dialog (after translation) and keep reference
        self.dlg = AderynDialog()
        self.dlg_settings = SettingsDialog()

        # Declare instance attributes
        self.actions = []
        self.menu = self.tr(u'&Aderyn Data Search')
        # TODO: We are going to let the user set this up in a future iteration
        self.toolbar = self.iface.addToolBar(u'Aderyn')
        self.toolbar.setObjectName(u'Aderyn')

        # Get the current database connection settings.
        s = QSettings()
        # s.remove("aderyn")
        self.dbhost = s.value("aderyn/dbhost", "")
        self.dbname = s.value("aderyn/dbname", "")
        self.dbuser = s.value("aderyn/dbuser", "")
        self.dbpassword = s.value("aderyn/dbpassword", "")
        self.dbport = s.value("aderyn/dbport", 0)

        # Get a reference to an osgr object and an osgrLayer object
        self.osgr = osgr.osgr()

        # Make a coordinate translator. Also need global references to OSGB and canvas CRSs since
        # they cannot be retrieved from a translator object.
        self.canvasCrs = iface.mapCanvas().mapSettings().destinationCrs().authid()
        self.osgbCrs = QgsCoordinateReferenceSystem("EPSG:27700").authid()
        self.transformCrs = QgsCoordinateTransform(self.canvas.mapSettings().destinationCrs(),
                                                   QgsCoordinateReferenceSystem("EPSG:27700"), QgsProject.instance())

        # Store any gridsquare.
        self.gridSquareCentre = NULL  # QgsPointXY
        self.gridSquareRectangle = NULL  # QgsRectangle
        self.gridSquareRubberBand = NULL  # QgsRubberBand
        self.gridSquareRubberBandMaxBuffer = 0  # QgsRubberBand - store the largest buffer.

        self.SearchLocation = NULL
        self.SearchName = NULL
        self.SearchOutputFolder = NULL
        self.SearchCategories = []
        self.Cat1Select = NULL
        self.Cat1Buffer = NULL
        self.Cat2Select = NULL
        self.Cat2Buffer = NULL
        self.Cat3Select = NULL
        self.Cat3Buffer = NULL
        self.Cat4Select = NULL
        self.Cat4Buffer = NULL
        self.BatsSelect = NULL
        self.BatsBuffer = NULL
        self.CsvSelect = NULL

    # noinspection PyMethodMayBeStatic
    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('Aderyn', 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:
            self.toolbar.addAction(action)

        if add_to_menu:
            self.iface.addPluginToDatabaseMenu(
                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/aderyn/icon.png'
        self.add_action(
            icon_path,
            text=self.tr(u'Aderyn Data Search'),
            callback=self.run,
            parent=self.iface.mainWindow())

        # Settings option.
        icon_path_settings = ':/plugins/aderyn/icon_settings.png'
        self.add_action(
            icon_path_settings,
            text=self.tr(u'Settings'),
            callback=self.runSettings,
            add_to_menu=True,
            add_to_toolbar=False,
            parent=self.iface.mainWindow())

        # Database tester.
        self.dlg.pb_test_database.clicked.connect(self.testDatabaseConnection)

        # Locate GR.
        self.dlg.pb_locate.clicked.connect(self.locateGridref)

        # Open settings dialogue. Just open it - rather than call a function in this file.
        self.dlg.pb_settings.clicked.connect(self.runSettings)

        # Set output folder button.
        self.dlg.tb_output_folder.clicked.connect(self.outputFolder)

    def setVariables(self):
        """Get all the variables from the GUI and load them into the class."""
        self.SearchLocation = self.dlg.le_location.text()
        self.SearchName = self.dlg.le_search_name.text()
        self.SearchOutputFolder = self.dlg.le_ouput_folder.text()
        self.Cat1Select = True if self.dlg.cb_cat1.isChecked() == True else False
        self.Cat1Buffer = self.dlg.sb_cat1_buffer.value()
        self.Cat2Select = True if self.dlg.cb_cat2.isChecked() == True else False
        self.Cat2Buffer = self.dlg.sb_cat2_buffer.value()
        self.Cat3Select = True if self.dlg.cb_cat3.isChecked() == True else False
        self.Cat3Buffer = self.dlg.sb_cat3_buffer.value()
        self.Cat4Select = True if self.dlg.cb_cat4.isChecked() == True else False
        self.Cat4Buffer = self.dlg.sb_cat4_buffer.value()
        self.BatsSelect = True if self.dlg.cb_bats.isChecked() == True else False
        self.BatsBuffer = self.dlg.sb_bats_buffer.value()
        self.CsvSelect = True if self.dlg.cb_csv.isChecked() == True else False

        # Output.
        # QMessageBox.information(None, "Success!", str(
        #     'Variables:\n' +
        #     'Location: ' + self.SearchLocation + '\n'
        #     'Search name: ' + self.SearchName + '\n'
        #     'Output folder: ' + self.SearchOutputFolder + '\n'
        #     'Cat1: ' + str(self.Cat1Select) + ' (' + str(self.Cat1Buffer) + ')' + '\n'
        #     'Cat2: ' + str(self.Cat2Select) + ' (' + str(self.Cat2Buffer) + ')' + '\n'
        #     'Cat3: ' + str(self.Cat3Select) + ' (' + str(self.Cat3Buffer) + ')' + '\n'
        #     'Cat4: ' + str(self.Cat4Select) + ' (' + str(self.Cat4Buffer) + ')' + '\n'
        #     'Bats: ' + str(self.BatsSelect) + ' (' + str(self.BatsBuffer) + ')' + '\n'
        #     'CSV: ' + str(self.CsvSelect) + '\n'
        #     ))

    def validateVariables(self):
        """ Validate that the categories - if a cat is selected then there should be a buffer. """
        if len(self.SearchName) < 2:
            # Search name - bit short!
            # QMessageBox.information(None, "Error!", str('Invalid invalid search name!'))
            self.iface.messageBar().pushMessage("Warning", "Invalid invalid search name!", level=Qgis.Warning)
            self.run()
        elif len(self.SearchOutputFolder) < 3:
            # Search output folder - bit short!
            # QMessageBox.information(None, "Error!", str('Invalid output folder!'))
            self.iface.messageBar().pushMessage("Warning", "Invalid output folder!", level=Qgis.Warning)
            self.run()
        elif self.Cat1Select is True and self.Cat1Buffer < 1:
            # QMessageBox.information(None, "Error!", str('No buffer specified for category 1!'))
            self.iface.messageBar().pushMessage("Warning", "No buffer specified for category 1!", level=Qgis.Warning)
            self.run()
        elif self.Cat2Select is True and self.Cat2Buffer < 1:
            # QMessageBox.information(None, "Error!", str('No buffer specified for category 2!'))
            self.iface.messageBar().pushMessage("Warning", "No buffer specified for category 2!", level=Qgis.Warning)
            self.run()
        elif self.Cat3Select is True and self.Cat3Buffer < 1:
            # QMessageBox.information(None, "Error!", str('No buffer specified for category 3!'))
            self.iface.messageBar().pushMessage("Warning", "No buffer specified for category 3!", level=Qgis.Warning)
            self.run()
        elif self.Cat4Select is True and self.Cat4Buffer < 1:
            # QMessageBox.information(None, "Error!", str('No buffer specified for category 4!'))
            self.iface.messageBar().pushMessage("Warning", "No buffer specified for category 4!", level=Qgis.Warning)
            self.run()
        elif self.BatsSelect is True and self.BatsBuffer < 1:
            # QMessageBox.information(None, "Error!", str('No buffer specified for bats!'))
            self.iface.messageBar().pushMessage("Warning", "No buffer specified for bats!", level=Qgis.Warning)
            self.run()
        else:
            return True

    def validateCategories(self):
        """ Add the categories to array if they are true and there is s buffer. """
        self.SearchCategories = []
        if self.Cat3Select is True and self.Cat3Buffer >= 1:
            cat3 = ['CAT3', self.Cat3Buffer, 'yellow']
            self.SearchCategories.append(cat3)
        if self.Cat2Select is True and self.Cat2Buffer >= 1:
            cat2 = ['CAT2', self.Cat2Buffer, 'orange']
            self.SearchCategories.append(cat2)
        if self.Cat1Select is True and self.Cat1Buffer >= 1:
            cat1 = ['CAT1', self.Cat1Buffer, 'red']
            self.SearchCategories.append(cat1)
        if self.Cat4Select is True and self.Cat4Buffer >= 1:
            cat4 = ['CAT4', self.Cat4Buffer, 'black']
            self.SearchCategories.append(cat4)
        if self.BatsSelect is True and self.BatsBuffer >= 1:
            bats = ['BATS', self.BatsBuffer, 'grey']
            self.SearchCategories.append(bats)

        if len(self.SearchCategories) > 0:
            return True
        else:
            self.iface.messageBar().pushMessage("Warning", "No categories selected", level=Qgis.Warning)
            self.run()

    def validateLocation(self):
        """Validate that the search location is a valid grid ref."""
        if len(self.SearchLocation) % 2 != 0:
            # Fine - number is even.
            # QMessageBox.information(None, "Error!", str('Invalid grid reference (not even)!'))
            self.iface.messageBar().pushMessage("Warning", "Invalid grid reference (not even)!", level=Qgis.Warning)
        elif len(self.SearchLocation) < 4:
            # Min is 2 digits plus prefix.
            # QMessageBox.information(None, "Error!", str('Invalid grid reference (too short)!'))
            self.iface.messageBar().pushMessage("Warning", "Invalid grid reference (too short)!", level=Qgis.Warning)
        elif len(self.SearchLocation) > 12:
            # Max is 10 nubmers plus prefix.
            # QMessageBox.information(None, "Error!", str('Invalid grid reference (too long)!'))
            self.iface.messageBar().pushMessage("Warning", "Invalid grid reference (too long)!", level=Qgis.Warning)
        else:
            return True

    def locateGridref(self):
        """Locate the gridref the user has specified. Pan and zoom the map. """
        # Get the variables.
        self.setVariables()
        validation = self.validateLocation()
        if validation == True:
            res = self.osgr.enFromGR(self.SearchLocation)  # returns easting, northing, precision, retCheck[1]

            if res[0] == 0:
                self.iface.messageBar().pushMessage("Warning", res[3],
                                                    level=Qgis.Warning)  # Show verification warning if conversion has failed.
            else:
                QgsMessageLog.logMessage('GR converted successfully.', 'Aderyn')
                QgsMessageLog.logMessage('Res 0: ' + str(res[0]), 'Aderyn')  # Easting
                QgsMessageLog.logMessage('Res 1: ' + str(res[1]), 'Aderyn')  # Northing
                QgsMessageLog.logMessage('Res 2: ' + str(res[2]), 'Aderyn')  # Precision - i.e.100/1000
                QgsMessageLog.logMessage('Res 3: ' + str(res[3]), 'Aderyn')  # retCheck - verification??

                precision = res[2]  # i.e. 1000 for 1k square.
                x0 = (res[
                          0] - precision / 2)  # Easting and northing are the centre - so divide precision by 2 to move to the edge of the square.
                x1 = (res[0] + precision / 2)
                y0 = (res[1] - precision / 2)
                y1 = (res[1] + precision / 2)

                centre = QgsPointXY(res[0], res[1])
                ll = QgsPointXY(x0, y0)
                ur = QgsPointXY(x1, y1)
                rect = QgsRectangle(ll, ur)
                # centre = QgsPointXY(res[0], res[1])

                # Strore the centre and square.
                self.gridSquareCentre = centre
                self.gridSquareRectangle = rect

                # Display the gridref.
                self.displayGridref()

        else:
            self.iface.messageBar().pushMessage("Warning", "Grid Ref. Validation failed!", level=Qgis.Warning)
            # QMessageBox.information(None, "Error!", str("Grid Ref. Validation failed!"))

    def displayGridref(self):

        # Create the gridref.
        # gr = self.osgr.grFromEN(point.x(), point.y(), precision) #This just returns the gr string - i.e. SO2242
        # self.iface.messageBar().pushMessage("Warning", gr, level=Qgis.Warning)
        rect = self.gridSquareRectangle
        geometry = QgsGeometry()
        geometry = geometry.fromRect(rect)

        # Get existing RubberBands
        rbs = [i for i in iface.mapCanvas().scene().items()
               if issubclass(type(i), qgis._gui.QgsRubberBand)]

        # Remove existing RubberBands
        for rb in rbs:
            if rb in iface.mapCanvas().scene().items():
                iface.mapCanvas().scene().removeItem(rb)

        # Only do this for canvas in OSGB or if there's a user-defined grid size. Firstly update the canvas CRS.
        self.canvasCrs = iface.mapCanvas().mapSettings().destinationCrs().authid()
        if self.canvasCrs == self.osgbCrs:

            r = QgsRubberBand(self.canvas, False)  # False = a polyline
            color = QColor(255, 0, 255)
            transparent = QColor(0, 0, 0, 0)
            # points = [QgsPoint(rect.xMinimum(), rect.yMinimum()),
            #           QgsPoint(rect.xMinimum(), rect.yMaximum()),
            #           QgsPoint(rect.xMaximum(), rect.yMaximum()),
            #           QgsPoint(rect.xMaximum(), rect.yMinimum()),
            #           QgsPoint(rect.xMinimum(), rect.yMinimum())]
            # r.setToGeometry(QgsGeometry.fromPolyline(points), None)
            r.setToGeometry(geometry, None)
            r.setColor(color)
            r.setFillColor(transparent)
            r.setWidth(2)

            # Store the Qgs RubberBand.
            self.gridSquareRubberBand = r

            #Pan and zoom to the gridref.
            # box = rect.boundingBoxOfSelected()
            self.canvas.setExtent(rect)
            self.canvas.refresh()
            self.canvas.zoomOut()  # Zoom out 1 level - to give a bit of context.

        else:
            self.iface.messageBar().pushMessage("Warning", "Incorrect map CRS (" + str(self.canvasCrs) + ")! Map view must be in OSGB36 (" + self.osgbCrs + ").", level=Qgis.Warning)
            # QMessageBox.information(None, "Error!", str("Incorrect map CRS (" + str(self.canvasCrs) + ")! Map view must be in OSGB36 (" + self.osgbCrs + ")."))

    def displayBuffer(self, buffer, colour):

        #Convert buffer to int.
        buffer = int(buffer)

        # Buffer the rectangle. https://docs.qgis.org/testing/en/docs/pyqgis_developer_cookbook/geometry.html
        rect = self.gridSquareRectangle
        geometry = QgsGeometry()
        geometry = geometry.fromRect(rect)
        buffered = geometry.buffer(buffer, 10)

        # Only do this for canvas in OSGB or if there's a user-defined grid size. Firstly update the canvas CRS.
        self.canvasCrs = iface.mapCanvas().mapSettings().destinationCrs().authid()
        if self.canvasCrs == self.osgbCrs:

            r = QgsRubberBand(self.canvas, False)  # False = a polyline
            color = QColor(colour)
            transparent = QColor(0, 0, 0, 0)
            r.setToGeometry(buffered, None)
            r.setColor(color)
            r.setFillColor(transparent)
            r.setWidth(2)

            # Store the buffer - if it's larger than the existing buffer.
            if buffer > self.gridSquareRubberBandMaxBuffer:
                self.gridSquareRubberBandMaxBuffer = buffer
                boundingbox = buffered.boundingBox()
                self.canvas.setExtent(boundingbox)
                # self.canvas.setExtent(buffered)
                self.canvas.refresh()
                self.canvas.zoomOut()  # Zoom out 1 level - to give a bit of context.

        else:
            self.iface.messageBar().pushMessage("Warning", "Incorrect map CRS (" + str(self.canvasCrs) + ")! Map view must be in OSGB36 (" + self.osgbCrs + ").", level=Qgis.Warning)
            # QMessageBox.information(None, "Error!", str("Incorrect map CRS (" + str(self.canvasCrs) + ")! Map view must be in OSGB36 (" + self.osgbCrs + ")."))

    def loadSetings(self):
        """Get the current db settings and load them into the form fields."""
        self.dlg_settings.le_dbhost.setText(self.dbhost)
        self.dlg_settings.le_dbuser.setText(self.dbuser)
        self.dlg_settings.le_dbpassword.setText(self.dbpassword)

    def saveSettings(self):
        """Get the settings from the form fields and update the internal variables."""
        self.dbhost = self.dlg_settings.le_dbhost.text()
        self.dbuser = self.dlg_settings.le_dbuser.text()
        self.dbpassword = self.dlg_settings.le_dbpassword.text()
        # Save the supplied settings.
        s = QSettings()
        s.setValue("aderyn/dbhost", self.dbhost)
        s.setValue("aderyn/dbname", "lrc_wales_data")
        s.setValue("aderyn/dbuser", self.dbuser)
        s.setValue("aderyn/dbpassword", self.dbpassword)
        s.setValue("aderyn/dbport", 5432)
        QMessageBox.information(None, "Success!", str('Settings saved!'))

        #Now load the setting to refresh the setting being used by the plugin.
        self.loadSetings()

    def testDatabaseConnection(self):
        """Test the database connection to LERC Wales merged database."""
        # http://pyqgis.org/blog/2013/04/11/creating-a-postgresql-connection-from-a-qgis-layer-datasource/
        # https://gis.stackexchange.com/questions/29721/how-to-connect-a-pyqgis-plugin-with-postgres
        db = QSqlDatabase.addDatabase('QPSQL')
        if db.isValid():
            db.setHostName(self.dbhost)
            db.setDatabaseName(self.dbname)
            db.setUserName(self.dbuser)
            db.setPassword(self.dbpassword)
            db.setPort(int(self.dbport))
            if db.open():
                # QMessageBox.information(None, "Success!", str('Database opened!'))
                QgsMessageLog.logMessage('Database opened successfully.', 'Aderyn')
                offset = str(random.randint(1, 100000))
                AderynQueryObj = aderyn_query.AderynQuery()
                queryString = AderynQueryObj.sqlQueryTest(offset)
                # query = db.exec_("SELECT * FROM lrc_wales_data.records LIMIT 1 OFFSET " + offset)
                query = db.exec_(queryString)
                # iterate over the rows
                while query.next():
                    record = query.record()
                    # print the value of the first column
                    # print record.value(0)
                    QMessageBox.information(None, "Success!", str(
                        'Random record: \nTOK: ' + record.field('tok').value() + '\nSpecies: ' + record.field(
                            'actual_name').value() + '\nLERC: ' + record.field('lrc').value()))
                # Close the connection.
                db.removeDatabase('QPSQL')
            else:
                QMessageBox.information(None, "Error!", str('Unable to open database!'))
        else:
            QMessageBox.information(None, "Error!", str('Invalid database specification!'))

    def outputFolder(self):
        """ Specify the output folder """
        outputFolder = str(
            QFileDialog.getExistingDirectory(caption="Set output folder", options=QFileDialog.ShowDirsOnly))
        self.setOutputFolderLine(outputFolder)

    def setOutputFolderLine(self, text):
        """ Ser the folder name in the output line edit."""
        self.dlg.le_ouput_folder.setText(text)

    def runSearch(self, category, buffer):
        """Run the search using the parameters suplied."""
        # QMessageBox.information(None, "Success!", str('You clicked ok - searching ' + category + '!'))
        db = QSqlDatabase.addDatabase('QPSQL')
        if db.isValid():
            db.setHostName(self.dbhost)
            db.setDatabaseName(self.dbname)
            db.setUserName(self.dbuser)
            db.setPassword(self.dbpassword)
            db.setPort(int(self.dbport))
            if db.open():
                # QMessageBox.information(None, "Success!", str('Database opened!'))
                QgsMessageLog.logMessage('Database opened successfully.', 'Aderyn')
                QgsMessageLog.logMessage('Searching ' + category + ', buffer ' + buffer, 'Aderyn')

                # Build the centre WKT. self.gridSquareCentre = centre ~ centre = QgsPointXY(res[0], res[1])
                wkt_centre = 'POINT(' \
                             + str(self.gridSquareCentre.x()) + ' ' + str(self.gridSquareCentre.y()) + ')'

                # Build the WKT.
                wkt = 'POLYGON((' \
                      + str(self.gridSquareRectangle.xMinimum()) + ' ' + str(self.gridSquareRectangle.yMinimum()) + ',' \
                      + str(self.gridSquareRectangle.xMinimum()) + ' ' + str(self.gridSquareRectangle.yMaximum()) + ',' \
                      + str(self.gridSquareRectangle.xMaximum()) + ' ' + str(self.gridSquareRectangle.yMaximum()) + ',' \
                      + str(self.gridSquareRectangle.xMaximum()) + ' ' + str(self.gridSquareRectangle.yMinimum()) + ',' \
                      + str(self.gridSquareRectangle.xMinimum()) + ' ' + str(self.gridSquareRectangle.yMinimum()) + '))'
                QgsMessageLog.logMessage('WKT: ' + wkt + '.', 'Aderyn')
                QgsMessageLog.logMessage('WKT Centre: ' + wkt_centre + '.', 'Aderyn')
                # Build the query string.
                AderynQueryObj = aderyn_query.AderynQuery()
                queryString = AderynQueryObj.sqlQuery(category, wkt, wkt_centre, buffer)
                # QgsMessageLog.logMessage('SQL: ' + queryString + '', 'Aderyn')
                query = db.exec_(queryString)
                QgsMessageLog.logMessage(category + ' query returned ' + str(query.size()) + ' rows.', 'Aderyn')

                # Did we get any data?
                if query.size() > 0:

                    # Create the shapefile and get the name of the file.
                    shapeFile = self.createShapefile(category, buffer, query)

                    # Get the file name - for adding it to the interface.
                    fileName = shapeFile + '.shp';
                    newFile = os.path.join(self.SearchOutputFolder, fileName)

                    # Add the shapefile to QGIS'
                    layer = iface.addVectorLayer(newFile, category + ' - ' + self.SearchName, "ogr")
                    if not layer:
                        self.iface.messageBar().pushMessage("Warning", 'Failed to load layer into interface (' + newFile + ')!', level=Qgis.Warning)
                        # QMessageBox.information(None, "Error!", str('Failed to load layer into interface (' + newFile + ')!'))
                    else:
                        # Style the layer.
                        self.styleShapefile(layer, category)

                        # If selected, convert the layer to a CSV.
                        if self.CsvSelect:
                            self.createCsv(layer, category)

                else:
                    self.iface.messageBar().pushMessage("Warning", category + ' did not return any records! (' + str(query.size()) + ')', level=Qgis.Warning)

                # Close the connection.
                db.removeDatabase('QPSQL')
            else:
                QMessageBox.information(None, "Error!", str('Unable to open database!'))
        else:
            QMessageBox.information(None, "Error!", str('Invalid database specification!'))

    def createShapefile(self, category, buffer, query):
        """ Take query object and create shapefile. """
        # Create a writer.
        writerCrs = QgsCoordinateReferenceSystem(27700)
        shapefileType = QgsWkbTypes.Point  # Point - could be polygon.
        fileName = category.lower() + '_' + self.SearchName.replace(" ", "_").lower() + '_' + buffer
        outputFile = os.path.join(self.SearchOutputFolder, fileName)
        QgsMessageLog.logMessage('Output file: ' + outputFile + '.', 'Aderyn')
        fields = QgsFields()
        fields.append(QgsField("id", QVariant.Int))
        fields.append(QgsField("sensitive", QVariant.String))
        fields.append(QgsField("grid_ref", QVariant.String))
        fields.append(QgsField("distance", QVariant.Int))
        fields.append(QgsField("actual_name", QVariant.String))
        fields.append(QgsField("common_name", QVariant.String))
        fields.append(QgsField("date_formatted", QVariant.String))
        fields.append(QgsField("recorder", QVariant.String))
        fields.append(QgsField("abundance", QVariant.String))
        fields.append(QgsField("location", QVariant.String))
        fields.append(QgsField("comments", QVariant.String))
        fields.append(QgsField("full_status", QVariant.String))
        fields.append(QgsField("source", QVariant.String))
        fields.append(QgsField("determiner", QVariant.String))
        fields.append(QgsField("verification_level", QVariant.String))
        fields.append(QgsField("grid_ref_public", QVariant.String))
        fields.append(QgsField("taxon_group", QVariant.String))  # name_taxon_nbn_group
        fields.append(QgsField("super_group", QVariant.String))  # name_taxon_super_group
        writer = QgsVectorFileWriter(outputFile, "CP1250", fields, shapefileType, writerCrs, "ESRI Shapefile")

        # Errors?
        if writer.hasError() != QgsVectorFileWriter.NoError:
            print("Error when creating shapefile: ", writer.errorMessage())

        # Loop through and write features.
        while query.next():
            record = query.record()
            geom = record.field('geom_point').value()
            # QgsMessageLog.logMessage('Geom point GeoJSON: ' + str(geom) + '.', 'Aderyn')
            geom_array = json.loads(str(geom))
            x = geom_array['coordinates'][0]  # Easting
            y = geom_array['coordinates'][1]  # Northing
            # QgsMessageLog.logMessage('Easting: ' + str(x) + ', northing: ' + str(y) + '.', 'Aderyn')

            fet = QgsFeature()
            fet.setGeometry(QgsPoint(x, y))
            # Add in one line.
            fet.setAttributes([
                record.field('id').value(),
                record.field('sensitive').value(),
                record.field('grid_ref').value(),
                record.field('distance').value(),
                record.field('actual_name').value(),
                record.field('common_name').value(),
                record.field('date_formatted').value(),
                record.field('recorder').value(),
                record.field('abundance').value(),
                record.field('location').value(),
                record.field('comments').value(),
                record.field('full_status').value(),
                record.field('source').value(),
                record.field('determiner').value(),
                record.field('verification_level').value(),
                record.field('grid_ref_public').value(),
                record.field('name_taxon_nbn_group').value(),  # name_taxon_nbn_group
                record.field('name_taxon_super_group').value(),  # name_taxon_super_group
            ])

            # Add the feature.
            writer.addFeature(fet)

        # Delete the writer.
        del writer

        # Return the name of the shapefile.
        return fileName

    def createCsv(self, layer, category):
        """ Create CSV file from the layer. """
        fileName = layer.source()

        #Get filename for CSV.
        base = os.path.splitext(fileName)[0]
        fileNameCsv = base + '.csv'
        QgsMessageLog.logMessage('Creating CSV file: ' + fileNameCsv, 'Aderyn')

        #Set the CSR.
        writerCrs = QgsCoordinateReferenceSystem(27700)  # Not sure if we need this.
        error = QgsVectorFileWriter.writeAsVectorFormat(layer, fileNameCsv, "utf-8", writerCrs, "CSV", layerOptions=['GEOMETRY=AS_WKT'])
        if error == QgsVectorFileWriter.NoError:
            QgsMessageLog.logMessage('Successfully creating CSV file: ' + fileNameCsv, 'Aderyn')

    def styleShapefile(self, layer, category):
        """ Style the layer - and save the resulting QML file. """
        renderer = layer.renderer()
        if category == 'CAT1':
            symbol = QgsMarkerSymbol.createSimple({'name': 'circle', 'color': 'red', 'size': '2', })
            renderer.setSymbol(symbol)
        elif category == 'CAT2':
            symbol = QgsMarkerSymbol.createSimple({'name': 'circle', 'color': 'orange', 'size': '3', })
            renderer.setSymbol(symbol)
        elif category == 'CAT3':
            symbol = QgsMarkerSymbol.createSimple({'name': 'circle', 'color': 'yellow', 'size': '4', })
            renderer.setSymbol(symbol)
        elif category == 'CAT4':
            symbol = QgsMarkerSymbol.createSimple({'name': 'star', 'color': 'black', 'size': '2', })
            renderer.setSymbol(symbol)
        elif category == 'BATS':
            symbol = QgsMarkerSymbol.createSimple({'name': 'triangle', 'color': 'black', 'size': '2', })
            renderer.setSymbol(symbol)

        # Update the symbolgy on the layer tree (refresh)
        iface.layerTreeView().refreshLayerSymbology(iface.activeLayer().id())

        # Save the style file for future use in QGIS. Get the layer source and change the file extension.
        base = os.path.splitext(layer.source())[0]
        newFileQml = base + '.qml'
        layer.saveNamedStyle(newFileQml)

    def unload(self):
        """Removes the plugin menu item and icon from QGIS GUI."""
        for action in self.actions:
            self.iface.removePluginDatabaseMenu(
                self.tr(u'&Aderyn Data Search'),
                action)
            self.iface.removeToolBarIcon(action)
        # remove the toolbar
        del self.toolbar

    def run(self):
        """Run method that performs all the real work"""
        # show the dialog
        self.dlg.show()
        # Run the dialog event loop
        result = self.dlg.exec_()
        # See if OK was pressed
        if result:
            # Let's go!
            self.setVariables()
            validationLocation = self.validateLocation()
            self.locateGridref()
            validationVariables = self.validateVariables()
            validateCategories = self.validateCategories()
            if validationVariables == True and validationLocation == True and validateCategories == True:

                #Set up progress bar. https://docs.qgis.org/testing/en/docs/pyqgis_developer_cookbook/communicating.html - showing progress??
                progressMessageBar = iface.messageBar().createMessage("Running search...")
                progress = QProgressBar()
                progress.setMaximum(len(self.SearchCategories))
                progress.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
                progressMessageBar.layout().addWidget(progress)
                iface.messageBar().pushWidget(progressMessageBar, Qgis.Info)
                couter = 0

                self.locateGridref()  # This will validate the location (again) and display it on the map.

                #Add all the buffers.
                for category in self.SearchCategories:

                    QgsMessageLog.logMessage('Looping - display buffer ' + category[0] + ', buffer ' + str(category[1]) + ', colour ' + category[2], 'Aderyn')
                    self.displayBuffer(str(category[1]), category[2])

                # Run each search in turn.
                for category in self.SearchCategories:

                    QgsMessageLog.logMessage('Looping - search ' + category[0] + ', buffer ' + str(category[1]), 'Aderyn')
                    self.runSearch(category[0], str(category[1]))

                    couter += 1 # Increment.
                    progress.setValue(couter)

                # if self.Cat3Select == True:
                #     self.runSearch("CAT3", str(self.Cat1Buffer))
                # if self.Cat2Select == True:
                #     self.runSearch("CAT2", str(self.Cat2Buffer))
                # if self.Cat1Select == True:
                #     self.runSearch("CAT1", str(self.Cat3Buffer))
                # if self.Cat4Select == True:
                #     self.runSearch("CAT4", str(self.Cat4Buffer))
                # if self.BatsSelect == True:
                #     self.runSearch("BATS", str(self.BatsBuffer))

                iface.messageBar().clearWidgets()

            # pass

    def runSettings(self):
        """Run method that performs all the real work"""
        self.loadSetings()
        # Load the current settings into the form.
        # self.dlg_settings.le_dbhost.setText(self.dbhost)
        # self.dlg_settings.le_dbuser.setText(self.dbuser)
        # self.dlg_settings.le_dbpassword.setText(self.dbpassword)
        # s = QSettings()
        # dbport_type_int = "int" if type(self.dbport) is int else "str?"
        # dbport_type_str = "str" if type(self.dbport) is str else "int?"
        # QMessageBox.information(None, "Current Settings", str(
        #     'dbhost: ' + s.value("aderyn/dbhost", "") + '\n'
        #     'dbhost: ' + self.dbhost + '\n'
        #     'dbname: ' + s.value("aderyn/dbname", "") + '\n'
        #     'dbname: ' + self.dbname + '\n'
        #     'dbuser: ' + s.value("aderyn/dbuser", "") + '\n'
        #     'dbuser: ' + self.dbuser + '\n'
        #     'dbpassword: ' + s.value("aderyn/dbpassword", "") + '\n'
        #     'dbpassword: ' + self.dbpassword + '\n'
        #     'dbport_type_int: ' + dbport_type_int + '\n'
        #     'dbport_type_str: ' + dbport_type_str + '\n'
        #     'dbport: ' + str(s.value("aderyn/dbport", 0)) + '\n'
        #     'dbport: ' + str(self.dbport) + '\n'
        # ))
        # show the dialog
        self.dlg_settings.show()
        # Run the dialog event loop
        result = self.dlg_settings.exec_()
        # See if OK was pressed
        if result:
            # Do something useful here - delete the line containing pass and
            # substitute with your code.
            self.saveSettings()
            pass
