# -*- coding: utf-8 -*-
"""
/***************************************************************************
 SocialActivity
                                 A QGIS plugin
 Plot Social Activity around a supplied point
 Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/
                              -------------------
        begin                : 2019-01-03
        git sha              : $Format:%H$
        copyright            : (C) 2019 by Vincenzo Lanzaro - SimplySoftware.co
        email                : vl@simplysoftware.co
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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 sys
import time
import traceback
import configparser
#from gi.repository import Gtk

from qgis.core import *
from qgis.gui import QgsMapToolEmitPoint
from PyQt5.QtCore import Qt, QSettings, QTranslator, qVersion, QCoreApplication, QThread, QObject, QEvent, pyqtSignal
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QAction, QMessageBox

from .utilities import *
from .twitter import *
#from .config import * # load API credentials

u = utilities()
loaded_geopy = u.importModule("geopy", "/geopy/__init__.py")

# Initialize Qt resources from file resources.py
from .resources import *
# Import the code for the dialog
from .social_activity_dialog_base import SocialActivityDialogBase
from .social_activity_dialog_settings import SocialActivityDialogSettings


class SocialActivity:
    """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
        # 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',
            'SocialActivity_{}.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)

        # Load config.ini file
        config = configparser.ConfigParser()
        config.read(os.path.join(self.plugin_dir,'config.ini'))
        self.twitter_consumer_key = config['twitter']['twitter_consumer_key']
        self.twitter_consumer_secret = config['twitter']['twitter_consumer_secret']
        self.twitter_access_key = config['twitter']['twitter_access_key']
        self.twitter_access_secret = config['twitter']['twitter_access_secret']

        # Declare instance attributes
        self.actions = []
        self.menu = self.tr(u'&Social Activity')

        # Check if plugin was started the first time in current QGIS session
        # Must be set in initGui() to survive plugin reloads
        self.firstStart_DialogBase = None
        self.start_latitude = None
        self.start_longitude = None
        #self.max_range = 300
        
        self.firstStart_DialogSettings = None
        
        # a reference to our map canvas 
        self.canvas = self.iface.mapCanvas() 
        # this QGIS tool emits as QgsPoint after each click on the map canvas
        self.pointTool = QgsMapToolEmitPoint(self.canvas)
        self.pointTool.canvasClicked.connect(self.display_point)


    # 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('SocialActivity', 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.addPluginToMenu(
                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/social_activity/icon.png'
        self.add_action(
            icon_path,
            text=self.tr(u'Plot Social Activity'),
            callback=self.run,
            parent=self.iface.mainWindow())
        # will be set False in run()
        self.firstStart_DialogBase = True
        self.firstStart_DialogSettings = True


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


    def clickable(self, widget):

        class Filter(QObject):

            clicked = pyqtSignal()

            def eventFilter(self, obj, event):
                if obj == widget:
                    if event.type() == QEvent.MouseButtonRelease:
                        if obj.rect().contains(event.pos()):
                            self.clicked.emit()
                            # The developer can opt for .emit(obj) to get the object within the slot.
                            return True

                return False

        filter = Filter(widget)
        widget.installEventFilter(filter)
        return filter.clicked


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

        # 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.firstStart_DialogBase == True:
            self.firstStart_DialogBase = False
            self.dlgBase = SocialActivityDialogBase()
            self.dlgBase.button_box.accepted.connect(self.dlgBase_accept)
            self.dlgBase.horizontalSlide_maxRange.valueChanged.connect(self.horizontalSlide_maxRange_OnValueChanged)
            self.dlgBase.setWindowFlags(Qt.WindowStaysOnTopHint)
            self.clickable(self.dlgBase.lineEdit_Coordinates).connect(self.lineEdit_Coordinates_OnClick)
            self.dlgBase.pushButton_Settings.clicked.connect(self.pushButton_Settings_OnClick)
        # make our clickTool the tool that we'll use for now
        self.canvas.setMapTool(self.pointTool)
        # show the dialog
        self.dlgBase.show()


    def horizontalSlide_maxRange_OnValueChanged(self):
        self.dlgBase.label_maxRange.setText("Max Range: %dKm" % (self.dlgBase.horizontalSlide_maxRange.value()))


    def lineEdit_Coordinates_OnClick(self):
        #print("lineEdit clicked")
        self.canvas.setMapTool(self.pointTool)


    def pushButton_Settings_OnClick(self):
        if self.firstStart_DialogSettings == True:
            self.firstStart_DialogSettings = False
            self.dlgSettings = SocialActivityDialogSettings()
            self.dlgSettings.button_box.accepted.connect(self.dlgSettings_accept)

        config = configparser.ConfigParser()
        config.read(os.path.join(self.plugin_dir,'config.ini'))
        self.dlgSettings.lineEdit_twitter_consumer_key.setText(config['twitter']['twitter_consumer_key'])
        self.dlgSettings.lineEdit_twitter_consumer_secret.setText(config['twitter']['twitter_consumer_secret'])
        self.dlgSettings.lineEdit_twitter_access_key.setText(config['twitter']['twitter_access_key'])
        self.dlgSettings.lineEdit_twitter_access_secret.setText(config['twitter']['twitter_access_secret'])
        # show the dialog
        self.dlgBase.close()
        result = self.dlgSettings.exec_()
        self.dlgBase.show()


    def dlgBase_accept(self):
        if self.start_latitude is not None and self.start_longitude is not None:
            if self.dlgBase.checkBox_realPoints.isChecked() or self.dlgBase.checkBox_decodedPoints.isChecked():
                self.dlgBase.textEdit_Log.setText("")

                self.TwitterTask = Threaded_TwitterTask()
                self.TwitterTask.twitter_consumer_key = self.twitter_consumer_key
                self.TwitterTask.twitter_consumer_secret = self.twitter_consumer_secret
                self.TwitterTask.twitter_access_key = self.twitter_access_key
                self.TwitterTask.twitter_access_secret = self.twitter_access_secret
                self.TwitterTask.Log.connect(self.onLogUpdate)
                self.TwitterTask.Points.connect(self.onPointsUpdate)
                self.TwitterTask.Busy.connect(self.onBusyUpdate)
                self.TwitterTask.Fail.connect(self.onFailUpdate)
                self.TwitterTask.keyword = self.dlgBase.lineEdit_Keyword.text()
                self.TwitterTask.latitude = self.start_latitude
                self.TwitterTask.longitude = self.start_longitude
                self.TwitterTask.max_range = self.dlgBase.horizontalSlide_maxRange.value() #self.max_range
                self.TwitterTask.num_points = self.dlgBase.spinBox_numPoints.value()
                self.TwitterTask.checkBox_realPoints = self.dlgBase.checkBox_realPoints.isChecked()
                self.TwitterTask.checkBox_decodedPoints = self.dlgBase.checkBox_decodedPoints.isChecked()
                self.TwitterTask.start()
                #self.twitterQuery(latitude, longitude, max_range)
            else:
                self.dlgBase.close()
                QMessageBox.about(None, "Select a points set", "Please select at least one points set to find")
                self.dlgBase.show()
                #dialog = Gtk.MessageDialog(None, 0, Gtk.MessageType.INFO, Gtk.ButtonsType.OK, "Select a points set")
                #dialog.format_secondary_text("Please select at least one points set before to run the finding process")
                #dialog.run()
                #dialog.destroy()
        else:
            self.dlgBase.close()
            QMessageBox.about(None, "Select a starting point", "Please select a starting point clicking on the map")
            self.dlgBase.show()


    def dlgSettings_accept(self):
        config = configparser.ConfigParser()
        config['twitter'] = {}
        config['twitter']['twitter_consumer_key'] = self.dlgSettings.lineEdit_twitter_consumer_key.text()
        config['twitter']['twitter_consumer_secret'] = self.dlgSettings.lineEdit_twitter_consumer_secret.text()
        config['twitter']['twitter_access_key'] = self.dlgSettings.lineEdit_twitter_access_key.text()
        config['twitter']['twitter_access_secret'] = self.dlgSettings.lineEdit_twitter_access_secret.text()
        with open(os.path.join(self.plugin_dir,'config.ini'), 'w') as configfile:
            config.write(configfile)
        
        self.twitter_consumer_key = self.dlgSettings.lineEdit_twitter_consumer_key.text()
        self.twitter_consumer_secret = self.dlgSettings.lineEdit_twitter_consumer_secret.text()
        self.twitter_access_key = self.dlgSettings.lineEdit_twitter_access_key.text()
        self.twitter_access_secret = self.dlgSettings.lineEdit_twitter_access_secret.text()
        QMessageBox.about(None, "Configuration file saved", "Your configuration file has been successfully saved")


    def display_point(self, point):
        #source crs
        crsSrc = QgsCoordinateReferenceSystem(3857)
        #target crs
        crsDest = QgsCoordinateReferenceSystem(4326)
        xform = QgsCoordinateTransform(crsSrc, crsDest, QgsProject.instance())
        transformed_point = xform.transform(point)
        #QgsPointXY(longitude, latitude) ex. point = QgsPointXY(14.619352,40.952270)
        self.start_latitude = transformed_point.y()
        self.start_longitude = transformed_point.x()
        #print('({:f}, {:f})'.format(self.start_longitude, self.start_latitude))
        self.dlgBase.lineEdit_Coordinates.setText('({:f}, {:f})'.format(self.start_longitude, self.start_latitude)) #{:.04f}, {:.04f}


    def onBusyUpdate(self, status):
        #print(not status)
        self.dlgBase.lineEdit_Keyword.setEnabled(not status)
        self.dlgBase.horizontalSlide_maxRange.setEnabled(not status)
        self.dlgBase.spinBox_numPoints.setEnabled(not status)
        self.dlgBase.checkBox_realPoints.setEnabled(not status)
        self.dlgBase.checkBox_decodedPoints.setEnabled(not status)
        self.dlgBase.button_box.setEnabled(not status)
        self.dlgBase.pushButton_Settings.setEnabled(not status)


    def onLogUpdate(self, text):
        self.dlgBase.textEdit_Log.setText(text)


    def onPointsUpdate(self, points):
        if self.dlgBase.checkBox_decodedPoints.isChecked():
            self.plotPoints("epsg:4326", "Decoded Points", "memory", points[1])
        if self.dlgBase.checkBox_realPoints.isChecked():
            self.plotPoints("epsg:4326", "Real Points", "memory", points[0])


    def onFailUpdate(self, status):
        if status:
            self.dlgBase.close()
            QMessageBox.about(None, "Error", status)
            self.dlgBase.show()


    def plotPoints(self, crs, baseName, providerType, points):
        #layer = QgsVectorLayer('Point?crs=epsg:4326&field=id:string&field=type:string','ModelPoints',"memory")
        layer = QgsVectorLayer('Point?crs='+crs, baseName, providerType) # create a new memory layer
        dp = layer.dataProvider()
        qf = QgsFeature()
        qf.setGeometry(QgsGeometry.fromMultiPointXY(points))
        dp.addFeatures([qf])
        layer.commitChanges()
        layer.updateFields()  # update fields of the layer
        layer.updateExtents() # update extent
        #QgsMapLayerRegistry.instance().addMapLayers([layer])
        QgsProject.instance().addMapLayers([layer]) # add the layer to the canvas



class Threaded_TwitterTask(QThread):
    """Run a QThread task"""
    Log = pyqtSignal(str)
    Points = pyqtSignal(list)
    Busy = pyqtSignal(bool)
    Fail = pyqtSignal(str)
    twitter_consumer_key = None
    twitter_consumer_secret = None
    twitter_access_key = None
    twitter_access_secret = None
    keyword = ""
    latitude = None
    longitude = None
    max_range = None
    num_points = 10        # minimum results to obtain
    checkBox_realPoints = None
    checkBox_decodedPoints = None


    def run(self):
        #print("%f,%f,%dkm" % (self.latitude, self.longitude, self.max_range))
        try:
            self.twitterQuery()
        except Exception as e:
            (type, value, traceback) = sys.exc_info()
            #print(value)
            sys.excepthook(type, value, traceback)
            self.Busy.emit(False)
            self.Fail.emit(value)


    def twitterQuery(self):
        realPoints = []
        decodedPoints = []
        totalPoints = [realPoints, decodedPoints]
        last_id = None
        realPoints_count = 0
        decodedPoints_count = 0
        totalPoints_count = 0
        max_results = False

        #-----------------------------------------------------------------------
        # create twitter API object
        #-----------------------------------------------------------------------
        # Ensure variable is defined
        try:
            twitter
        except NameError:
            twitter = None
            
        if twitter is None:
            twitter = Twitter(auth = OAuth(self.twitter_access_key, self.twitter_access_secret, self.twitter_consumer_key, self.twitter_consumer_secret))

        geolocator = loaded_geopy.Nominatim(user_agent="QGIS_SocialActivity")
        self.Busy.emit(True)

        while not max_results: #totalPoints_count < self.num_points:
            #-----------------------------------------------------------------------
            # perform a search based on latitude and longitude
            # twitter API docs: https://dev.twitter.com/rest/reference/get/search/tweets
            #-----------------------------------------------------------------------
            try:
                query = twitter.search.tweets(q = "%s" % (self.keyword), geocode = "%f,%f,%dkm" % (self.latitude, self.longitude, self.max_range), count = 100, max_id = last_id)
                #print(query)
            except Exception as e:
                (type, value, traceback) = sys.exc_info()
                #print(value)
                if ("'code': 89" in str(value)) or ("'code': 32" in str(value)):
                    sys.excepthook(type, value, traceback)
                    self.Busy.emit(False)
                    self.Fail.emit("Please verify that your Twitter API Credentials are correctly wrote in settings panel")
                else:
                    pass

            for result in query["statuses"]:
                #-----------------------------
                # result with a geolocation
                #-----------------------------
                if result["geo"] and self.checkBox_realPoints:
                    result_lat = result["geo"]["coordinates"][0]
                    result_lng = result["geo"]["coordinates"][1]
                    #QgsPointXY(longitude, latitude) ex. point = QgsPointXY(14.619352,40.952270)
                    totalPoints[0].append(QgsPointXY(result_lng,result_lat))
                    realPoints_count += 1

                if result["user"]["location"] and self.checkBox_decodedPoints:
                    #location = geolocator.geocode("175 5th Avenue NYC")
                    result_location = geolocator.geocode(result["user"]["location"])
                    if result_location is not None:
                        #print(result_location)
                        #print((result_location.latitude, result_location.longitude))
                        totalPoints[1].append(QgsPointXY(result_location.longitude, result_location.latitude))
                        decodedPoints_count += 1
                    time.sleep(1)

                last_id = result["id"]
                totalPoints_count = realPoints_count + decodedPoints_count
                #print("got %d real points\n    %d decoded points\n    ------------------\n    %d total points\n" % (realPoints_count, decodedPoints_count, totalPoints_count))
                self.Log.emit("got %d real points\n      %d decoded points\n      ---------------------------\n      %d total points\n" % (realPoints_count, decodedPoints_count, totalPoints_count))
                
                if totalPoints_count >= self.num_points:
                    max_results = True
                    break

            time.sleep(1)

        self.Busy.emit(False)
        self.Points.emit(totalPoints)
