# -*- coding: utf-8 -*-
# /***************************************************************************
# Irmt
#                                 A QGIS plugin
# OpenQuake Integrated Risk Modelling Toolkit
#                              -------------------
#        begin                : 2013-10-24
#        copyright            : (C) 2014 by GEM Foundation
#        email                : devops@openquake.org
# ***************************************************************************/
#
# OpenQuake is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# OpenQuake is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with OpenQuake.  If not, see <http://www.gnu.org/licenses/>.


import json
from qgis.PyQt.QtCore import pyqtSlot, QSettings, Qt
from qgis.PyQt.QtWidgets import QDialog, QColorDialog, QMessageBox
from qgis.PyQt.QtGui import QPalette

from qgis.core import (
    QgsGraduatedSymbolRenderer, QgsProject, Qgis, QgsApplication)
from qgis.gui import QgsMessageBar

from requests import Session
from svir.dialogs.connection_profile_dialog import ConnectionProfileDialog
from svir.utilities.utils import (
                                  get_ui_class,
                                  get_style,
                                  platform_login,
                                  engine_login,
                                  log_msg,
                                  WaitCursorManager,
                                  check_is_lockdown,
                                  )
from svir.utilities.shared import (
                                   PLATFORM_REGISTRATION_URL,
                                   DEFAULT_SETTINGS,
                                   DEFAULT_PLATFORM_PROFILES,
                                   DEFAULT_ENGINE_PROFILES,
                                   LOG_LEVELS,
                                   )

FORM_CLASS = get_ui_class('ui_settings.ui')


class SettingsDialog(QDialog, FORM_CLASS):
    """
    Dialog used to edit the plugin settings
    """
    def __init__(self, iface, irmt_main=None, parent=None):
        QDialog.__init__(self, parent)
        self.iface = iface
        self.parent = parent
        # in order to reset login when new credentials are saved
        self.irmt_main = irmt_main
        # Set up the user interface from Designer.
        self.setupUi(self)
        self.message_bar = QgsMessageBar(self)
        self.layout().insertWidget(0, self.message_bar)
        link_text = ('<a href="%s">Register to the OpenQuake Platform</a>'
                     % PLATFORM_REGISTRATION_URL)
        self.registration_link_lbl.setText(link_text)

        self.style_color_from.setFocusPolicy(Qt.NoFocus)
        self.style_color_to.setFocusPolicy(Qt.NoFocus)

        if Qgis.QGIS_VERSION_INT < 31000:
            modes = {
                QgsGraduatedSymbolRenderer.EqualInterval: self.tr(
                    'Equal Interval'),
                QgsGraduatedSymbolRenderer.Quantile: self.tr(
                    'Quantile (Equal Count)'),
                QgsGraduatedSymbolRenderer.Jenks: self.tr(
                    'Natural Breaks (Jenks)'),
                QgsGraduatedSymbolRenderer.StdDev: self.tr(
                    'Standard Deviation'),
                QgsGraduatedSymbolRenderer.Pretty: self.tr(
                    'Pretty Breaks'),
            }
            for key in modes:
                self.style_mode.addItem(modes[key], key)
        else:
            modes = QgsApplication.classificationMethodRegistry().methodNames()
            for mode_name, mode_id in modes.items():
                self.style_mode.addItem(mode_name, mode_id)

        for log_level in sorted(LOG_LEVELS):
            self.log_level_cbx.addItem(LOG_LEVELS[log_level], log_level)

        self.restore_state()
        self.initial_engine_hostname = QSettings().value(
            'irmt/engine_hostname')

    def restore_state(self, restore_defaults=False):
        """
        Reinstate the options based on the user's stored session info.

        :param restore_defaults: if True, settings will be reset to the default
            values, instead of reading them from the user's stored info
        """
        mySettings = QSettings()

        self.refresh_profile_cbxs('platform', restore_defaults)
        self.refresh_profile_cbxs('engine', restore_defaults)

        developer_mode = (DEFAULT_SETTINGS['developer_mode']
                          if restore_defaults
                          else mySettings.value(
                              'irmt/developer_mode', False, type=bool))
        experimental_enabled = (DEFAULT_SETTINGS['experimental_enabled']
                                if restore_defaults
                                else mySettings.value(
                                    'irmt/experimental_enabled',
                                    False, type=bool))

        log_level = (DEFAULT_SETTINGS['log_level']
                     if restore_defaults
                     else mySettings.value(
                         'irmt/log_level',
                         DEFAULT_SETTINGS['log_level']))
        self.log_level_cbx.setCurrentIndex(
            self.log_level_cbx.findData(log_level))

        style = get_style(
            self.iface.activeLayer(),
            self.iface.messageBar(),
            restore_defaults)

        self.set_button_color(self.style_color_from, style['color_from'])
        self.set_button_color(self.style_color_to, style['color_to'])

        mode_idx = self.style_mode.findData(style['style_mode'])
        self.style_mode.setCurrentIndex(mode_idx)

        self.style_classes.setValue(style['classes'])
        self.force_restyling_ckb.setChecked(style['force_restyling'])

        self.developer_mode_ckb.setChecked(developer_mode)
        self.enable_experimental_ckb.setChecked(experimental_enabled)

    def refresh_profile_cbxs(self, platform_or_engine, restore_defaults=False):
        assert platform_or_engine in ('platform', 'engine'), platform_or_engine
        if platform_or_engine == 'platform':
            self.platform_profile_cbx.blockSignals(True)
            self.platform_profile_cbx.clear()
            self.platform_profile_cbx.blockSignals(False)
        else:  # 'engine'
            self.engine_profile_cbx.blockSignals(True)
            self.engine_profile_cbx.clear()
            self.engine_profile_cbx.blockSignals(False)
        mySettings = QSettings()
        if restore_defaults:
            profiles = json.loads(
                DEFAULT_PLATFORM_PROFILES if platform_or_engine == 'platform'
                else DEFAULT_ENGINE_PROFILES)
            cur_profile = list(profiles.keys())[0]
        else:
            profiles = json.loads(
                mySettings.value(
                    'irmt/%s_profiles' % platform_or_engine,
                    (DEFAULT_PLATFORM_PROFILES
                     if platform_or_engine == 'platform'
                     else DEFAULT_ENGINE_PROFILES)))
            cur_profile = mySettings.value(
                'irmt/current_%s_profile' % platform_or_engine)
        for profile in sorted(profiles, key=str.lower):
            if platform_or_engine == 'platform':
                self.platform_profile_cbx.blockSignals(True)
                self.platform_profile_cbx.addItem(profile)
                self.platform_profile_cbx.blockSignals(False)
            else:  # engine
                self.engine_profile_cbx.blockSignals(True)
                self.engine_profile_cbx.addItem(profile)
                self.engine_profile_cbx.blockSignals(False)
        if cur_profile is None:
            cur_profile = list(profiles.keys())[0]
            mySettings.setValue(
                'irmt/current_%s_profile' % platform_or_engine,
                cur_profile)
        if platform_or_engine == 'platform':
            self.platform_profile_cbx.setCurrentIndex(
                self.platform_profile_cbx.findText(cur_profile))
        else:  # engine
            self.engine_profile_cbx.setCurrentIndex(
                self.engine_profile_cbx.findText(cur_profile))
        if platform_or_engine == 'platform':
            self.pla_remove_btn.setEnabled(
                self.platform_profile_cbx.count() > 1)
        else:  # engine
            self.eng_remove_btn.setEnabled(
                self.engine_profile_cbx.count() > 1)

    def set_button_color(self, button, color):
        button.setStyleSheet("background-color: %s" % color.name())

    def save_state(self):
        """
        Store the options into the user's stored session info.
        """
        mySettings = QSettings()

        mySettings.setValue('irmt/developer_mode',
                            self.developer_mode_ckb.isChecked())
        mySettings.setValue('irmt/experimental_enabled',
                            self.enable_experimental_ckb.isChecked())
        mySettings.setValue(
            'irmt/log_level',
            self.log_level_cbx.itemData(self.log_level_cbx.currentIndex()))

        cur_pla_profile = self.platform_profile_cbx.currentText()
        cur_eng_profile = self.engine_profile_cbx.currentText()

        platform_profiles = json.loads(mySettings.value(
            'irmt/platform_profiles', DEFAULT_PLATFORM_PROFILES))
        platform_profile = platform_profiles[cur_pla_profile]
        engine_profiles = json.loads(mySettings.value(
            'irmt/engine_profiles', DEFAULT_ENGINE_PROFILES))
        engine_profile = engine_profiles[cur_eng_profile]

        mySettings.setValue('irmt/platform_hostname',
                            platform_profile['hostname'])
        mySettings.setValue('irmt/platform_username',
                            platform_profile['username'])
        mySettings.setValue('irmt/platform_password',
                            platform_profile['password'])
        mySettings.setValue('irmt/engine_hostname',
                            engine_profile['hostname'])
        mySettings.setValue('irmt/engine_username',
                            engine_profile['username'])
        mySettings.setValue('irmt/engine_password',
                            engine_profile['password'])

        color_from = self.style_color_from.palette().color(QPalette.Button)
        mySettings.setValue(
            'irmt/style_color_from',
            color_from.rgba())
        color_to = self.style_color_to.palette().color(QPalette.Button)
        mySettings.setValue(
            'irmt/style_color_to',
            color_to.rgba())

        mySettings.setValue('irmt/style_mode', self.style_mode.itemData(
            self.style_mode.currentIndex()))

        mySettings.setValue('irmt/style_classes', self.style_classes.value())
        active_layer = self.iface.activeLayer()
        # at project level, save the setting associated to the layer if
        # available
        if active_layer is not None:
            # NOTE: We can't use %s/%s instead of %s_%s, because / is a special
            #       character
            QgsProject.instance().writeEntry(
                'irmt', '%s_%s' % (active_layer.id(), 'force_restyling'),
                self.force_restyling_ckb.isChecked())
        else:  # no layer is selected
            QgsProject.instance().writeEntry(
                'irmt', 'force_restyling',
                self.force_restyling_ckb.isChecked())
        # keep the latest setting saved also into the general settings
        mySettings.setValue('irmt/force_restyling',
                            self.force_restyling_ckb.isChecked())

    @pyqtSlot()
    def on_style_color_from_clicked(self):
        self.select_color(self.style_color_from)

    @pyqtSlot()
    def on_style_color_to_clicked(self):
        self.select_color(self.style_color_to)

    @pyqtSlot()
    def on_restore_default_settings_btn_clicked(self):
        msg = ("All current settings will be deleted and replaced by defaults."
               " Are you sure?")
        reply = QMessageBox.question(
            self, 'Warning', msg, QMessageBox.Yes, QMessageBox.No)
        if reply == QMessageBox.Yes:
            self.restore_state(restore_defaults=True)

    @pyqtSlot(int)
    def on_platform_profile_cbx_currentIndexChanged(self, idx):
        profile = self.platform_profile_cbx.itemText(idx)
        QSettings().setValue('irmt/current_platform_profile', profile)

    @pyqtSlot(int)
    def on_engine_profile_cbx_currentIndexChanged(self, idx):
        profile = self.engine_profile_cbx.itemText(idx)
        QSettings().setValue('irmt/current_engine_profile', profile)

    @pyqtSlot()
    def on_pla_edit_btn_clicked(self):
        profile_name = self.platform_profile_cbx.currentText()
        self.profile_dlg = ConnectionProfileDialog(
            'platform', profile_name, parent=self)
        if self.profile_dlg.exec_():
            self.refresh_profile_cbxs('platform')

    @pyqtSlot()
    def on_eng_edit_btn_clicked(self):
        profile_name = self.engine_profile_cbx.currentText()
        self.profile_dlg = ConnectionProfileDialog(
            'engine', profile_name, parent=self)
        if self.profile_dlg.exec_():
            self.refresh_profile_cbxs('engine')

    @pyqtSlot()
    def on_pla_new_btn_clicked(self):
        self.profile_dlg = ConnectionProfileDialog(
            'platform', parent=self)
        if self.profile_dlg.exec_():
            self.refresh_profile_cbxs('platform')

    @pyqtSlot()
    def on_pla_test_btn_clicked(self):
        server = 'platform'
        profile_name = self.platform_profile_cbx.currentText()
        with WaitCursorManager('Logging in...', self.message_bar):
            self._attempt_login(server, profile_name)

    @pyqtSlot()
    def on_eng_test_btn_clicked(self):
        server = 'engine'
        profile_name = self.engine_profile_cbx.currentText()
        with WaitCursorManager('Logging in...', self.message_bar):
            self._attempt_login(server, profile_name)

    def _attempt_login(self, server, profile_name):
        if server == 'platform':
            default_profiles = DEFAULT_PLATFORM_PROFILES
            login_func = platform_login
        elif server == 'engine':
            default_profiles = DEFAULT_ENGINE_PROFILES
            login_func = engine_login
        else:
            raise NotImplementedError(server)
        mySettings = QSettings()
        profiles = json.loads(mySettings.value(
            'irmt/%s_profiles' % server, default_profiles))
        profile = profiles[profile_name]
        session = Session()
        hostname, username, password = (profile['hostname'],
                                        profile['username'],
                                        profile['password'])
        if server == 'engine':
            try:
                is_lockdown = check_is_lockdown(hostname, session)
            except Exception as exc:
                err_msg = ("Unable to connect")
                log_msg(err_msg, level='C', message_bar=self.message_bar,
                        exception=exc)
                return
            else:
                if not is_lockdown:
                    msg = 'Able to connect'
                    log_msg(msg, level='S', message_bar=self.message_bar,
                            duration=3)
                    return
        try:
            login_func(hostname, username, password, session)
        except Exception as exc:
            err_msg = "Unable to connect (see Log Message Panel for details)"
            log_msg(err_msg, level='C', message_bar=self.message_bar,
                    exception=exc)
        else:
            msg = 'Able to connect'
            log_msg(msg, level='S', message_bar=self.message_bar, duration=3)

    @pyqtSlot()
    def on_eng_new_btn_clicked(self):
        self.profile_dlg = ConnectionProfileDialog(
            'engine', parent=self)
        if self.profile_dlg.exec_():
            self.refresh_profile_cbxs('engine')

    @pyqtSlot()
    def on_pla_remove_btn_clicked(self):
        self.remove_selected_profile('platform')

    @pyqtSlot()
    def on_eng_remove_btn_clicked(self):
        self.remove_selected_profile('engine')

    def remove_selected_profile(self, platform_or_engine):
        assert platform_or_engine in ('platform', 'engine'), platform_or_engine
        if QMessageBox.question(
                self,
                'Remove connection profile',
                ('If you continue, the selected profile will be permanently'
                 ' deleted. Are you sure?'),
                QMessageBox.Yes | QMessageBox.No) != QMessageBox.Yes:
            return
        profiles = json.loads(
            QSettings().value('irmt/%s_profiles' % platform_or_engine))
        if platform_or_engine == 'platform':
            cur_profile = self.platform_profile_cbx.currentText()
        else:  # engine
            cur_profile = self.engine_profile_cbx.currentText()
        del profiles[cur_profile]
        QSettings().remove('irmt/current_%s_profile' % platform_or_engine)
        self.save_profiles(platform_or_engine, profiles)
        self.refresh_profile_cbxs(platform_or_engine)

    def save_profiles(self, platform_or_engine, profiles):
        assert platform_or_engine in ('platform', 'engine'), platform_or_engine
        QSettings().setValue('irmt/%s_profiles' % platform_or_engine,
                             json.dumps(profiles))

    def select_color(self, button):
        initial = button.palette().color(QPalette.Button)
        color = QColorDialog.getColor(initial)
        if color.isValid():
            self.set_button_color(button, color)

    def accept(self):
        """
        Method invoked when OK button is clicked.
        """
        self.save_state()
        if self.irmt_main is not None:
            self.irmt_main.reset_drive_oq_engine_server_dlg()
        current_engine_hostname = QSettings().value('irmt/engine_hostname')
        # in case the engine hostname was modified, the embedded web apps that
        # were using the old hostname must be refreshed using the new one
        # (set_host is called in their __init__ when dialogs are created, and
        # it needs to be called again here if those dialogs are already
        # initialized and pointing to a previous engine server)
        if (current_engine_hostname != self.initial_engine_hostname
                and self.irmt_main is not None):
            for dlg in (self.irmt_main.ipt_dlg,
                        self.irmt_main.taxtweb_dlg,
                        self.irmt_main.taxonomy_dlg):
                if dlg is not None:
                    dlg.set_host()
                    dlg.load_homepage()
        super(SettingsDialog, self).accept()
