# Source code for svir.dialogs.download_layer_dialog

    # -*- 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
    import os
    import tempfile
    import shutil
    from xml.etree import ElementTree
    from qgis.PyQt.QtCore import pyqtSlot, Qt
    from qgis.PyQt.QtWidgets import (
                                     QDialog,
                                     QDialogButtonBox,
                                     QListWidgetItem,
                                     QMessageBox,
                                     )
    from qgis.core import QgsVectorLayer,  QgsProject
    from svir.thread_worker.abstract_worker import start_worker
    from svir.thread_worker.download_platform_project_worker import (
        DownloadPlatformProjectWorker)

    from svir.utilities.utils import (WaitCursorManager,
                                      SvNetworkError,
                                      ask_for_destination_full_path_name,
                                      files_exist_in_destination,
                                      confirm_overwrite,
                                      write_layer_suppl_info_to_qgs,
                                      get_ui_class,
                                      log_msg,
                                      )

    NS_NET_OPENGIS_WFS = '{http://www.opengis.net/wfs}'

    FORM_CLASS = get_ui_class('ui_download_layer.ui')

    class DownloadLayerDialog(QDialog, FORM_CLASS):
        """
        Modal dialog giving to the user the possibility to select and download one
        of the projects available on the OQ-Platform
        """
        def __init__(self, iface, downloader):
            QDialog.__init__(self)
            # Set up the user interface from Designer.
            self.setupUi(self)

            self.iface = iface
            # login to platform, to be able to retrieve sv indices
            self.sv_downloader = downloader

            self.ok_button = self.buttonBox.button(QDialogButtonBox.Ok)

            self.layer_lbl.setText('Project definition')

            self.layer_id = None  # needed after ok is pressed
            self.downloaded_layer_id = None  # the id of the layer created
            self.extra_infos = {}

            self.set_ok_button()
            with WaitCursorManager():
                self.get_capabilities()

        @pyqtSlot()
        def on_layers_lst_itemSelectionChanged(self):
            layer_id = self.layers_lst.currentItem().data(Qt.ToolTipRole)
            if layer_id is not None:
                self.layer_id = layer_id
                self.layer_lbl.setText('Project details for "%s"' % layer_id)
                layer_infos = self.extra_infos[layer_id]
                bb = layer_infos['Bounding Box']
                layer_infos_str = "Title:\n\t" + layer_infos['Title']
                layer_infos_str += "\nAbstract:\n\t" + layer_infos['Abstract']
                layer_infos_str += "\nBounding box:"
                layer_infos_str += "\n\tminx = " + bb['minx']
                layer_infos_str += "\n\tminy = " + bb['miny']
                layer_infos_str += "\n\tmaxx = " + bb['maxx']
                layer_infos_str += "\n\tmaxy = " + bb['maxy']
                layer_infos_str += "\nKeywords:\n\t" + layer_infos['Keywords']
                self.layer_detail.setText(layer_infos_str)
            self.set_ok_button()

        @pyqtSlot(QListWidgetItem)
        def on_layers_lst_itemDoubleClicked(self, item):
            self.accept()

        def set_ok_button(self):
            self.ok_button.setDisabled(
                len(self.layers_lst.selectedItems()) == 0)

        def get_capabilities(self):
            wfs = '/geoserver/wfs?'
            params = ('SERVICE=WFS'
                      '&VERSION=1.0.0'
                      '&REQUEST=GetCapabilities'
                      '&SRSNAME=EPSG:4326')
            url = '%s%s%s' % (self.sv_downloader.host, wfs, params)
            result = self.sv_downloader.sess.get(url)
            if result.status_code == 200:
                self.parse_get_capabilities(result.content.decode('utf8'))
            else:
                raise SvNetworkError(
                    "Unable to download layers: %s" % result.error)

        def parse_get_capabilities(self, xml):
            # this raises a IOError if the file doesn't exist
            root = ElementTree.fromstring(xml)
            featuretypelist = root.find('%sFeatureTypeList' % NS_NET_OPENGIS_WFS)
            layers = featuretypelist.findall('%sFeatureType' % NS_NET_OPENGIS_WFS)

            for layer in layers:
                try:
                    keywords = layer.find('%sKeywords' % NS_NET_OPENGIS_WFS).text
                    if keywords is not None and (
                            'IRMT_QGIS_Plugin' in keywords or
                            # this is for backward compatibility
                            'SVIR_QGIS_Plugin' in keywords):
                        title = layer.find('%sTitle' % NS_NET_OPENGIS_WFS).text
                        layer_id = layer.find('%sName' % NS_NET_OPENGIS_WFS).text
                        abstract = layer.find(
                            '%sAbstract' % NS_NET_OPENGIS_WFS).text
                        bbox = layer.find(
                            '%sLatLongBoundingBox' % NS_NET_OPENGIS_WFS).attrib

                        self.extra_infos[layer_id] = {
                            'Title': title,
                            'Abstract': abstract,
                            'Keywords': keywords,
                            'Bounding Box': bbox}

                        # update combo box
                        item = QListWidgetItem()
                        item.setData(Qt.DisplayRole, title)
                        item.setData(Qt.ToolTipRole, layer_id)
                        self.layers_lst.addItem(item)
                except AttributeError:
                    continue

        def accept(self):
            dest_full_path_name = ask_for_destination_full_path_name(self)
            if not dest_full_path_name:
                return
            # ignoring file extension
            dest_file_stem_path, _dest_file_ext = os.path.splitext(
                dest_full_path_name)

            worker = DownloadPlatformProjectWorker(self.sv_downloader,
                                                   self.layer_id)
            worker.successfully_finished.connect(
                lambda zip_file: self._import_layer(
                    zip_file, self.sv_downloader, dest_file_stem_path, self))
            start_worker(worker, self.iface.messageBar(),
                         'Downloading data from platform')

        def _replace_file_names(self, source_files, dest_file_stem):
            # the name from the zip_file will be replaced with dest_file_stem
            dest_file_names = []
            for source_file in source_files:
                _name, ext = os.path.splitext(source_file)
                dest_file_name = dest_file_stem + ext
                dest_file_names.append(dest_file_name)
            return dest_file_names

        def _import_layer(
                self, zip_file, sv_downloader, dest_file_stem_path, parent_dlg):
            files_in_zip = zip_file.namelist()
            shp_file_in_zip = next(
                filename for filename in files_in_zip if '.shp' in filename)
            dest_dir = os.path.dirname(dest_file_stem_path)
            files_to_create = self._replace_file_names(files_in_zip,
                                                       dest_file_stem_path)
            existing_files_in_destination = files_exist_in_destination(
                dest_dir, files_to_create)

            if existing_files_in_destination:
                while confirm_overwrite(
                        parent_dlg,
                        existing_files_in_destination) == QMessageBox.No:
                    dest_full_path_name = ask_for_destination_full_path_name(
                        parent_dlg)
                    if not dest_full_path_name:
                        continue
                    # ignoring file extension
                    dest_file_stem_path, _dest_file_ext = os.path.splitext(
                        dest_full_path_name)
                    dest_dir = os.path.dirname(dest_file_stem_path)
                    files_to_create = self._replace_file_names(files_in_zip,
                                                               dest_file_stem_path)
                    existing_files_in_destination = files_exist_in_destination(
                        dest_dir, files_to_create)
                    if not existing_files_in_destination:
                        break

            temp_path = os.path.join(tempfile.gettempdir(), shp_file_in_zip[:-4])
            if os.path.exists(temp_path):
                # clearing the temporary directory should be safe
                for the_file in os.listdir(temp_path):
                    file_path = os.path.join(temp_path, the_file)
                    if os.path.isfile(file_path):
                        os.unlink(file_path)
            else:
                os.makedirs(temp_path)  # it returns None if successful
            zip_file.extractall(temp_path)
            # copy extracted files to the destination directory chosen by the user,
            # substituting the file names with the name chosen by the user
            for the_file in os.listdir(temp_path):
                _name, ext = os.path.splitext(the_file)
                new_file_path = dest_file_stem_path + ext
                # the full file name of the shapefile will be used to create the
                # vector layer
                if ext == '.shp':
                    new_shp_file_path = new_file_path
                shutil.move(os.path.join(temp_path, the_file), new_file_path)

            request_url = '%s/svir/get_supplemental_information?layer_name=%s' % (
                sv_downloader.host, parent_dlg.layer_id)
            get_supplemental_information_resp = sv_downloader.sess.get(request_url)
            if not get_supplemental_information_resp.ok:
                msg = 'Unable to retrieve the project definitions for the layer'
                log_msg(msg, level='C', message_bar=self.iface.messageBar())
                return
            supplemental_information = json.loads(
                get_supplemental_information_resp.content.decode('utf8'))
            # attempt to convert supplemental information in old list format
            # or those that did not nest project definitions into a
            # project_definitions attribute
            if (isinstance(supplemental_information, list)
                    or 'project_definitions' not in supplemental_information):
                supplemental_information = {
                    'project_definitions': supplemental_information}

            layer = QgsVectorLayer(
                new_shp_file_path,
                parent_dlg.extra_infos[parent_dlg.layer_id]['Title'], 'ogr')
            if layer.isValid():
                QgsProject.instance().addMapLayer(layer)
                msg = 'Shapefile was imported to %s' % new_shp_file_path
                log_msg(msg, level='S', message_bar=self.iface.messageBar())
            else:
                msg = 'Layer invalid'
                log_msg(msg, level='C', message_bar=self.iface.messageBar())
                return
            try:
                # dlg.layer_id has the format "oqplatform:layername"
                style_name = parent_dlg.layer_id.split(':')[1] + '.sld'
                request_url = '%s/gs/rest/styles/%s' % (
                    sv_downloader.host, style_name)
                get_style_resp = sv_downloader.sess.get(request_url)
                if not get_style_resp.ok:
                    raise SvNetworkError(get_style_resp.reason)
                fd, sld_file = tempfile.mkstemp(suffix=".sld")
                os.close(fd)
                with open(sld_file, 'w', newline='') as f:
                    f.write(get_style_resp.text)
                layer.loadSldStyle(sld_file)
            except Exception as e:
                error_msg = ('Unable to download and apply the'
                             ' style layer descriptor: %s' % e)
                log_msg(error_msg, level='C', message_bar=self.iface.messageBar(),
                        exception=e)
            self.iface.setActiveLayer(layer)
            project_definitions = supplemental_information['project_definitions']
            # ensure backwards compatibility with projects with a single
            # project definition
            if not isinstance(project_definitions, list):
                project_definitions = [project_definitions]
            supplemental_information['project_definitions'] = project_definitions
            supplemental_information['platform_layer_id'] = parent_dlg.layer_id
            if 'selected_project_definition_idx' not in supplemental_information:
                supplemental_information['selected_project_definition_idx'] = 0
            self.downloaded_layer_id = layer.id()
            write_layer_suppl_info_to_qgs(
                self.downloaded_layer_id, supplemental_information)
            super(DownloadLayerDialog, self).accept()