# -*- coding: utf-8 -*-
"""
/***************************************************************************
 Riverscpaes Viewer
                                 A QGIS plugin
 Explore symbolized Riverscapes projects
 Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/
                              -------------------
        begin                : 2021-04-13
        git sha              : $Format:%H$
        copyright            : (C) 2021 by North Arrow Research
        email                : info@northarrowresearch.com
 ***************************************************************************/
"""
import sys
import os.path
from time import time
from functools import partial
from pathlib import Path
import requests

from qgis.utils import showPluginHelp
from qgis.core import QgsApplication, QgsProject, QgsMessageLog, Qgis

from qgis.PyQt.QtCore import QSettings, QTranslator, QCoreApplication, Qt, QUrl, pyqtSignal
from qgis.PyQt.QtGui import QIcon, QDesktopServices
from qgis.PyQt.QtWidgets import QAction, QFileDialog, QToolButton, QMenu, QMessageBox

from .classes.settings import Settings, CONSTANTS
from .classes.net_sync import NetSync
from .classes.basemaps import BaseMaps


# Initialize Qt resources from file resources.py
# Import the code for the dialog
from .options_dialog import OptionsDialog
from .about_dialog import AboutDialog
from .dock_widget import QRAVEDockWidget
from .meta_widget import QRAVEMetaWidget

# initialize Qt resources from file resources.py
from . import resources
from ..__version__ import __version__

RESOURCES_DIR = os.path.join(os.path.dirname(__file__), '..', 'resources')

# BASE is the name we want to use inside the settings keys
MESSAGE_CATEGORY = CONSTANTS['logCategory']


class QRAVE:
    """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.tm = QgsApplication.taskManager()
        self.qproject = QgsProject.instance()
        self.settings = Settings(iface=self.iface)

        self.settings.setValue('DEBUG', os.environ.get("RS_DEBUG", "False").lower() == "true")
        self.settings.setValue('Staging', os.environ.get("RS_STAGING", "False").lower() == "true")

        self.pluginIsActive = False

        self.dockwidget = QRAVEDockWidget()
        self.metawidget = QRAVEMetaWidget()

        # Populated on load from a URL
        self.acknowledgements = None

        # 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',
            'QRAVE_{}.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'&Riverscapes Viewer Plugin')

        # TODO: We are going to let the user set this up in a future iteration
        self.toolbar = self.iface.addToolBar(u'Riverscapes Viewer')
        self.toolbar.setObjectName(u'Riverscapes Viewer')

        self.debugpy = None
        self._enable_debug()

    def _enable_debug(self):
        debug_port = 5678
        debug_host = "localhost"
        DEBUG_ON = self.settings.getValue('DEBUG')
        if not DEBUG_ON:
            return

        if self.debugpy is None:
            try:
                import debugpy
                self.debugpy = debugpy
            except:
                self.settings.log("Need install debugpy: pip3 install debugpy", Qgis.Warning)
                pass

        if self.debugpy is None:
            return
        else:
            try:
                # if we are in OSX then this is the path
                if sys.platform == 'darwin':
                    python_path = os.path.join(Path(os.__file__).parents[2], 'bin', Path(os.__file__).parent.name)
                elif sys.platform == 'win32':
                    python_path = os.path.join(Path(os.__file__).parents[1], 'python.exe')
                self.settings.log(f"debugpy imported and attached to: {python_path}", Qgis.Success)
                debugpy.configure(python=python_path)
            except Exception as e:
                self.settings.log("Error initializing debugpy: {}".format(e), Qgis.Critical)
                raise e

        msgPort = f'"request": "attach", "Port": {debug_port}, "host": "{debug_host}"'
        if self.debugpy.is_client_connected():
            self.settings.log(f"ALREADY ACTIVE: Remote Debug for Visual Studio is active({debug_port})", Qgis.Warning)
            return
        else:
            try:
                t_, new_port = self.debugpy.listen((debug_host, debug_port))
            except Exception as e:
                self.settings.log(f"Error starting debugpy: {e}", Qgis.Critical)
                return
            msgPort = f'"request": "enable_attach", "Port": {new_port}, "host": "{debug_host}"'
            self.settings.log(f"Remote Debug for Visual Studio is running({msgPort})", Qgis.Success)

    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('Riverscapes Viewer', message)

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

        self.openAction = QAction(QIcon(':/plugins/qrave_toolbar/viewer-icon.svg'),
                                  self.tr(u'Riverscapes Viewer'), self.iface.mainWindow())
        self.openAction.triggered.connect(self.toggle_widget)

        self.openAction.setStatusTip('Toggle the project viewer')
        self.openAction.setWhatsThis('Toggle the project viewer')

        self.openProjectAction = QAction(QIcon(
            ':/plugins/qrave_toolbar/open.svg'), self.tr(u'Open Riverscapes Project'), self.iface.mainWindow())
        self.openProjectAction.triggered.connect(self.projectBrowserDlg)

        self.openProjectAction.setStatusTip('Open Riverscapes project')
        self.openProjectAction.setWhatsThis('Open Riverscapes project')

        self.closeAllProjectsAction = QAction(QIcon(':/plugins/qrave_toolbar/close.png'), self.tr(
            u'Close All Riverscapes Projects'), self.iface.mainWindow())
        self.closeAllProjectsAction.triggered.connect(self.closeAllProjects)

        self.closeAllProjectsAction.setStatusTip('Close all open Riverscapes projects')
        self.closeAllProjectsAction.setWhatsThis('Close all open Riverscapes projects')

        self.helpButton = QToolButton()
        self.helpButton.setToolButtonStyle(Qt.ToolButtonTextOnly)
        self.helpButton.setMenu(QMenu())
        self.helpButton.setPopupMode(QToolButton.MenuButtonPopup)

        m = self.helpButton.menu()

        # TODO: get the local help working
        # self.helpAction = QAction(
        #     QIcon(':/plugins/qrave_toolbar/Help.png'),
        #     self.tr('Help'),
        #     self.iface.mainWindow()
        # )
        # self.helpAction.triggered.connect(partial(showPluginHelp, None, filename=':/plugins/qrave_toolbar/help/build/html/index'))
        # self.websiteAction = QAction(
        #     QIcon(':/plugins/qrave_toolbar/RaveAddIn_16px.png'),
        #     self.tr('Website'),
        #     self.iface.mainWindow()
        # )
        # self.websiteAction.triggered.connect(lambda: QDesktopServices.openUrl(QUrl("https://viewer.riverscapes.net/software-help/help-qgis/")))

        self.helpAction = QAction(
            QIcon(':/plugins/qrave_toolbar/Help.png'),
            self.tr('Help'),
            self.iface.mainWindow()
        )
        self.helpAction.triggered.connect(lambda: QDesktopServices.openUrl(QUrl("https://viewer.riverscapes.net/software-help/help-qgis/")))

        self.raveOptionsAction = QAction(
            QIcon(':/plugins/qrave_toolbar/Options.png'),
            self.tr('Settings'),
            self.iface.mainWindow()
        )
        self.raveOptionsAction.triggered.connect(self.options_load)

        self.net_sync_action = QAction(
            QIcon(':/plugins/qrave_toolbar/refresh.png'),
            self.tr('Update resources'),
            self.iface.mainWindow()
        )
        self.net_sync_action.triggered.connect(
            lambda: self.net_sync_load(force=True))

        self.find_resources_action = QAction(
            QIcon(':/plugins/qrave_toolbar/BrowseFolder.png'),
            self.tr('Find Resources folder'),
            self.iface.mainWindow()
        )
        self.find_resources_action.triggered.connect(self.locateResources)

        self.about_action = QAction(
            QIcon(':/plugins/qrave_toolbar/viewer-icon.svg'),
            self.tr('About Riverscapes Viewer'),
            self.iface.mainWindow()
        )
        self.about_action.triggered.connect(self.about_load)

        m.addAction(self.helpAction)
        # m.addAction(self.websiteAction)
        m.addAction(self.raveOptionsAction)
        m.addAction(self.net_sync_action)
        m.addSeparator()
        m.addAction(self.find_resources_action)
        m.addAction(self.about_action)
        self.helpButton.setDefaultAction(self.helpAction)

        self.toolbar.addAction(self.openAction)
        self.toolbar.addAction(self.openProjectAction)
        self.toolbar.addAction(self.closeAllProjectsAction)
        self.toolbar.addWidget(self.helpButton)

        # Do a check to see if the stored version is different than the current version
        lastVersion = self.settings.getValue('pluginVersion')

        # This does a lazy netsync (i.e. it will run it if it feels like it)
        versionChange = lastVersion != __version__
        self.net_sync_load(force=versionChange)

        if versionChange:
            QgsMessageLog.logMessage("Version change detected: {} ==> {}".format(
                lastVersion, __version__), MESSAGE_CATEGORY, level=Qgis.Info)
            self.settings.setValue('pluginVersion', __version__)

    def onProjectLoad(self, doc):
        # If the project has the plugin enabled then restore it.
        qrave_enabled, type_conversion_ok = self.qproject.readEntry(
            CONSTANTS['settingsCategory'],
            'enabled'
        )
        if type_conversion_ok and qrave_enabled == '1':
            self.toggle_widget(forceOn=True)
            if self.dockwidget is not None:
                self.dockwidget.reload_tree()

    def onClosePlugin(self):
        """Cleanup necessary items here when plugin dockwidget is closed"""
        if self.metawidget is not None:
            self.metawidget.hide()
        if self.dockwidget is not None:
            self.dockwidget.hide()

        # disconnects
        self.dockwidget.closingPlugin.disconnect(self.onClosePlugin)
        self.qproject.readProject.disconnect(self.onProjectLoad)

        # remove this statement if dockwidget is to remain
        self.pluginIsActive = False

    def unload(self):
        """Removes the plugin menu item and icon from QGIS GUI."""
        if self.metawidget is not None:
            self.metawidget.hide()
        if self.dockwidget is not None:
            self.dockwidget.hide()

        for action in self.actions:
            self.iface.removePluginMenu(
                self.tr(u'&Riverscapes Viewer Plugin'),
                action)
            self.iface.removeToolBarIcon(action)
        # remove the toolbar
        del self.toolbar

    def toggle_widget(self, forceOn=False):
        """Toggle the widget open and closed when clicking the toolbar"""
        if not self.pluginIsActive:
            self.pluginIsActive = True

            # Hook metadata changes up to the metawidget
            self.dockwidget.metaChange.connect(self.metawidget.load)

            # Run a network sync operation to get the latest stuff. Don't force it.
            #  This is just a quick check
            self.net_sync_load()

            # connect to provide cleanup on closing of dockwidget
            self.dockwidget.closingPlugin.connect(self.onClosePlugin)

            # show the dockwidget
            self.iface.addDockWidget(Qt.LeftDockWidgetArea, self.dockwidget)
            self.iface.addDockWidget(Qt.LeftDockWidgetArea, self.metawidget)
            self.dockwidget.show()

        else:
            if self.dockwidget is not None:
                if self.dockwidget.isHidden():
                    self.dockwidget.show()
                elif forceOn is False:
                    self.dockwidget.hide()

        # The metawidget always starts hidden
        if self.metawidget is not None:
            self.metawidget.hide()

        if self.dockwidget is not None and not self.dockwidget.isHidden():
            self.qproject.writeEntry(
                CONSTANTS['settingsCategory'], 'enabled', True)
        else:
            self.qproject.removeEntry(CONSTANTS['settingsCategory'], 'enabled')

    def net_sync_load(self, force=False):
        """
        Periodically check for new files
        """

        lastDigestSync = self.settings.getValue('lastDigestSync')
        lastVersion = self.settings.getValue('pluginVersion')

        currTime = int(time())  # timestamp in seconds
        plugin_init = self.settings.getValue('initialized')
        autoUpdate = self.settings.getValue('autoUpdate')

        self.netsync = NetSync('Sync Riverscapes resource files')

        perform_sync = False

        # setting the force flag overrides everything else
        if force:
            perform_sync = True

        # Otherwise you only get a sync if 'autoUpdate' is turned on
        elif autoUpdate:
            # If this is an old version or the plugin is uninitialized
            if not plugin_init or lastVersion != __version__ or self.netsync.need_sync:
                perform_sync = True
            # If we haven't checked for more than `digestSyncFreqHours` hours
            elif isinstance(lastDigestSync, int) \
                    and ((currTime - lastDigestSync) / 3600) > CONSTANTS['digestSyncFreqHours']:
                perform_sync = True

        if perform_sync is False:
            self.netsync = None
            return

        # Trigger the dockwidget to repaint after the netsync
        if self.dockwidget is not None:
            self.netsync.taskCompleted.connect(self.dockwidget.reload_tree)

        # FOR DEBUGGING ONLY. NEVER IN PRODUCTION
        # self.netsync.run()

        # COMMENT THIS OUT AND USE THE LINE ABOVE FOR SYNCHRONOUS DEBUGGING
        self.tm.addTask(self.netsync)

    def projectBrowserDlg(self):
        """
        Browse for a project directory
        :return:
        """
        last_browse_path = self.settings.getValue('lastBrowsePath')
        last_dir = os.path.dirname(
            last_browse_path) if last_browse_path is not None else None

        dialog_return = QFileDialog.getOpenFileName(
            self.dockwidget, "Open a Riverscapes project", last_dir, self.tr("Riverscapes Project files (project.rs.xml)"))
        if dialog_return is not None and len(dialog_return[0]) > 0:
            # Remember this path for next time
            self.settings.setValue('lastBrowsePath', dialog_return[0])
        if dialog_return is not None and dialog_return[0] != "" and os.path.isfile(dialog_return[0]):
            # We set the proect path in the project settings. This way it will be saved with the QgsProject file
            if self.dockwidget is None or self.dockwidget.isHidden() is True:
                self.toggle_widget(forceOn=True)
            self.dockwidget.add_project(dialog_return[0])

    def closeAllProjects(self):
        """Close all open projects"""
        if self.dockwidget is not None:
            if len(self.dockwidget.loaded_projects) > 0:
                msgBox = QMessageBox()
                msgBox.setWindowTitle("Close All Riverscapes Projects?")
                msgBox.setIcon(QMessageBox.Question)
                msgBox.setText(
                    "Are you sure that you want to close all Riverscapes projects? This will also remove the layers related to these projects from your current map document.")
                # msgBox.setInformativeText("Are you sure that you want to close all Riverscapes projects? This will also remove the layers related to these projects from your current map document.")
                msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
                msgBox.setDefaultButton(QMessageBox.No)
                response = msgBox.exec_()
                if response == QMessageBox.Yes:
                    self.dockwidget.close_all()

    def locateResources(self):
        """This the OS-agnostic "show in Finder" or "show in explorer" equivalent
        It should open the folder of the item in question

        Args:
            fpath (str): [description]
        """
        qurl = QUrl.fromLocalFile(RESOURCES_DIR)
        QDesktopServices.openUrl(qurl)

    def options_load(self):
        """
        Open the options/settings dialog
        """
        dialog = OptionsDialog()
        if self.dockwidget:
            dialog.dataChange.connect(self.dockwidget.dataChange)
        dialog.exec_()

    def about_load(self):
        """
        Open the About dialog
        """
        dialog = AboutDialog()
        if self.acknowledgements is None:
            self.acknowledgements = '<a href="{0}">{0}</a>'.format('https://viewer.riverscapes.net/about/acknowledgements')

        dialog.acknowledgements.setText(self.acknowledgements)
        dialog.exec_()
