# -*- coding: utf-8 -*-
"""
/***************************************************************************
 Districts
                                 A QGIS plugin
 This is the Districts Modeler plugin
 Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/
                              -------------------
        begin                : 2026-01-16
        git sha              : $Format:%H$
        copyright            : (C) 2026 by EQUA
        email                : peter.nageler@equa.ch
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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 qgis.PyQt.QtCore import QTimer
from qgis.PyQt.QtGui import QStandardItemModel
from qgis.PyQt.QtWidgets import QTabWidget,QMenu,QAbstractItemView,QFileDialog
from qgis.PyQt.QtCore import Qt,QSettings, QTranslator, QCoreApplication
from qgis.PyQt.QtGui import QIcon,QPixmap
from qgis.core import QgsRasterLayer, QgsMapSettings, QgsMapRendererParallelJob, QgsCoordinateTransform, QgsProject
from qgis.PyQt.QtGui import QImage
from qgis.PyQt.QtCore import Qt  # Qt.GlobalColor lives here
from functools import partial
from qgis.PyQt.QtWidgets import QShortcut  

# Import the code for the dialog
from .utility_functions.switch_debug_mode import *
from .utility_functions.compat import QAction, QT6
from .districts_dialog import *
from .ida_ph_dialog import *
from .ida_resources_dialog import *
from .ida_pp_dialog import *
from .ida_mosim_dialog import *
from .ida_pp import *
from .ida_ph import *
from .ida_mosim import *
from .ida_resources import *
from .ida_import import *
from .ida_import_dialog import *
from .ida_rv import *
from .ida_rv_dialog import *
from .osm import *
from .load_results import *
from .elevation_data import *
from .update_sensors import *
from .utility_functions.dialog import *
from .utility_functions.db import *
from .utility_functions.invoke import *
import os.path
import tempfile
import webbrowser

class Districts:
    """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__)
        
        self.icon_path = os.path.join(
            self.plugin_dir,
            'icons',
            'IDA_Districts_Icon_QGIS.png'
        )
        # initialize locale
        locale = QSettings().value('locale/userLocale')[0:2]

        locale_path = os.path.join(
            self.plugin_dir,
            'i18n',
            f'districts_{locale}.qm'
        )

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

            loaded = self.translator.load(locale_path)
            QCoreApplication.installTranslator(self.translator)
            
        # Declare instance attributes
        self.actions = []
        self.menu = self.tr(u'&Districts Modeler')

        # Check if plugin was started the first time in current QGIS session
        # Must be set in initGui() to survive plugin reloads
        self.first_start = True
      
        self.conn=None
        self.cur=None
        self.conn_postgres=None
        self.cur_postgres=None
        self.config=load_plugin_settings()
        self.projectConfig = loadProjectConfig(self.config)  
        self.timer=None
        self.process_running=False
        
        #print('shortcuts')
        # 1. Kontext & Speicher sicherstellen
        ctx = Qt.ShortcutContext.ApplicationShortcut if hasattr(Qt, 'ShortcutContext') else Qt.ApplicationShortcut

        if not hasattr(self, 'my_shortcuts'):
            self.my_shortcuts = []
        else:
            # Vorhandene Shortcuts explizit löschen, bevor die Liste geleert wird
            for s in self.my_shortcuts:
                s.setEnabled(False)
                s.setParent(None)
            self.my_shortcuts.clear()

        parent = iface.mainWindow()

        configs = {
            'Shift+D': ('action', self.openPlugin),  
            'Shift+J': ('layer', tr("@default",'junctions')),             
            'Shift+L': ('layer', tr("@default",'lines')),         
            'Shift+C': ('layer', tr("@default",'customers')),              
            'Shift+E': ('layer', tr("@default",'energy_plants')),          
            'Shift+B': ('layer', tr("@default",'buildings')),              
            'Shift+S': ('layer', tr("@default",'streets')),                
            'Shift+1': ('switchtab', {'fnc':self.switchTab,'tab_name':'tab_project_handling'}),      
            'Shift+2': ('switchtab', {'fnc':self.switchTab,'tab_name':'tab_import'}),      
            'Shift+3': ('switchtab', {'fnc':self.switchTab,'tab_name':'tab_data_center'}),      
            'Shift+4': ('switchtab', {'fnc':self.switchTab,'tab_name':'tab_preprocessing'}),      
            'Shift+5': ('switchtab', {'fnc':self.switchTab,'tab_name':'tab_modeling_simulation'}),      
            'Shift+6': ('switchtab', {'fnc':self.switchTab,'tab_name':'tab_results_visualization'}),      
            'Shift+A': ('action', self.selectionMode),      
            'Shift+Q': ('action', self.startEditLayer),    
            'Shift+W': ('action', self.saveLayer),          
            'Shift+Z': ('action', self.rollBackLayerEdits),       
            'Space':   ('action', self.toggle_active_layer)      # SPACE -> Layer on/off            
        }


        for key, (mode, target) in configs.items():
            shortcut = QShortcut(QKeySequence(key), parent)
            shortcut.setContext(ctx)
            
            if mode == 'action':
                shortcut.activated.connect(target)
            elif mode == 'switchtab':
                shortcut.activated.connect(lambda t=target: t['fnc'](t['tab_name']))
            elif mode == 'layer':
                shortcut.activated.connect(lambda t=target: self.activeLayer(t))
            
            self.my_shortcuts.append(shortcut)

        shortcut_plugin = QShortcut(QKeySequence('Ctrl+D'), iface.mainWindow())
        shortcut_plugin.setContext(ctx)
        shortcut_plugin.activated.connect(self.openPlugin)

    def switchTab(self,tab_name):
        try:
            # Find all tab widgets inside your dialog
            tab_widgets = self.dlg.findChildren(QTabWidget)

            if not tab_widgets:
                #print("No QTabWidget found")
                return

            tab_widget = tab_widgets[0]  # adjust if you have multiple

            # Loop through tabs and match by objectName
            for i in range(tab_widget.count()):
                widget = tab_widget.widget(i)
                if widget.objectName() == tab_name:
                    tab_widget.setCurrentIndex(i)
                    #print(f"Switched to tab: {tab_name}")
                    return

            #print(f"Tab not found: {tab_name}")
        except:
            pass

            
    def toggle_active_layer(self):
        layer = iface.activeLayer()
        if layer:
            node = QgsProject.instance().layerTreeRoot().findLayer(layer.id())
            if node:
                node.setItemVisibilityChecked(not node.isVisible())
            
    def openPlugin(self):
        self.run()

    def activeLayer(self,layer_name):
        #print('active Layer: '+layer_name)
        layer=QgsProject.instance().mapLayersByName(layer_name)
        if layer:
            iface.setActiveLayer(layer[0])

    def startEditLayer(self):
        layer = iface.activeLayer() 
        if layer and layer.isEditable() is False:
            layer.startEditing()
            #print(f"Editing mode started for layer: {layer.name()}")

    def saveLayer(self):
        layer = iface.activeLayer() 
        if layer.isEditable():
            layer.commitChanges()
            #print(f"Edits committed for layer: {layer.name()}")

    def rollBackLayerEdits(self):
        layer = iface.activeLayer() 
        if layer.isEditable():
            layer.rollBack()
            #print(f"Edits rolled back for layer: {layer.name()}")

    def selectionMode(self):
        #print('selection mode')
        iface.actionSelect().trigger()
        
    # 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('Modeler Districts', 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."""

        self.add_action(
            self.icon_path,
            text=self.tr(u'Districts Modeler'),
            callback=self.run,
            parent=self.iface.mainWindow())

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

    def openMenu(self, position):
        """Function to add right click menu to treeview item"""
        indexes = self.dlg.treeViewVersions.selectedIndexes()
        mdlIdx = self.dlg.treeViewVersions.indexAt(position)
        if not mdlIdx.isValid():
            return
        item = self.model.itemFromIndex(mdlIdx)
        if len(indexes) > 0:
            level = 0
            index = indexes[0]
            while index.parent().isValid():
                index = index.parent()
                level += 1
        else:
            level = 0
        right_click_menu = QMenu()
        act_add = right_click_menu.addAction(QCoreApplication.translate("Districts", "add_child_version"))
        act_add.triggered.connect(partial(self.treeItem_addDialog, level, mdlIdx))
        act_add = right_click_menu.addAction(QCoreApplication.translate("Districts", "save_version_as"))
        act_add.triggered.connect(partial(self.treeItem_saveAsDialog, level, mdlIdx))
        act_add = right_click_menu.addAction(QCoreApplication.translate("Districts", "rename_version"))
        act_add.triggered.connect(partial(self.treeItem_renameDialog, level, mdlIdx,item))
        act_del = right_click_menu.addAction(QCoreApplication.translate("Districts", "delete_item"))
        act_del.triggered.connect(partial(self.deleteVersionDialog, item))
        act_del = right_click_menu.addAction(QCoreApplication.translate("Districts", "load_version"))
        act_del.triggered.connect(partial(treeItem_load, item, mdlIdx,self))
        right_click_menu.exec(self.dlg.treeViewVersions.viewport().mapToGlobal(position))

    def treeItem_addDialog(self,level, mdlIdx):
        """Project version add dialog --> connects to the TreeItem_Add function, which does the adding"""
        #print('Add dialog started')
        if self.conn:
            title=tr('@default','add_child_version')
            label_text=tr('@default','child_version_name')
            self.dlg_addVersion = IDA_Districts_NameDialog(title,label_text,'','')
            self.dlg_addVersion.btn_ok.clicked.connect(lambda: treeItem_add(level,mdlIdx,self.dlg_addVersion,self))
            self.dlg_addVersion.btn_cancel.clicked.connect(lambda: closeDialog(self.dlg_addVersion))
            self.dlg_addVersion.show()               
        else:
            self.iface.messageBar().pushMessage("Info", tr('@default','no_db_connection'), level=Qgis.Info)

    def treeItem_saveAsDialog(self,level, mdlIdx):
        """Project version Save As dialog --> connects to the TreeItem_SaveAs function, which does the Save As"""
        #print('Save As dialog started')
        if self.conn:
            title=tr('@default','save_project_version_as')
            label_text=tr('@default','project_version_name')
            self.dlg_saveAsVersion = IDA_Districts_NameDialog(title,label_text,'','')
            self.dlg_saveAsVersion.btn_ok.clicked.connect(lambda: treeItem_saveAs(level,mdlIdx,self,self.dlg_saveAsVersion))
            self.dlg_saveAsVersion.btn_cancel.clicked.connect(lambda: closeDialog(self.dlg_saveAsVersion))
            self.dlg_saveAsVersion.show()               
        else:
            self.iface.messageBar().pushMessage("Info", tr('@default','no_db_connection'), level=Qgis.Info)

    def showDeleteVersionDialog(self):
        try:
            #print(self.model)
            #print(self.dlg.treeViewVersions.selectedIndexes())
            item=self.model.itemFromIndex(self.dlg.treeViewVersions.selectedIndexes()[0])
            #print(item)
            self.deleteVersionDialog(item)
        except:
            iface.messageBar().pushMessage("Info", tr('@default','no_item_selected'), level=Qgis.Info)
            #print('No item selected!')

    def deleteVersionDialog(self,item):
        """Delete project version dialog"""
        #print('Delete project version dialog started')
        versionName=item.text()
        if self.conn_postgres != '':
            title=tr('@default','delete_version')
            label_text=tr('@default','ask_delete_version').format(versionName)
            self.dlg_deleteVersion = ApproveDialog(title,label_text)
            self.dlg_deleteVersion.btn_ok.clicked.connect(lambda: treeItem_delete(item,self.dlg_deleteVersion,self))
            self.dlg_deleteVersion.btn_cancel.clicked.connect(lambda: closeDialog(self.dlg_deleteVersion))
            self.dlg_deleteVersion.show()               
        else:
            self.iface.messageBar().pushMessage("Info", tr('@default','no_db_connection'), level=Qgis.Info)

            
    def addBaseVersionDialog(self):
        #Creating version --> new schema in project 
        if self.conn != '':
            title=tr('@default','add_base_version')
            label_text=tr('@default','add_base_version')
            self.dlg_addBase = IDA_Districts_NameDialog(title,label_text,'','')
            self.dlg_addBase.btn_ok.clicked.connect(lambda: addBaseVersion(self.dlg_addBase,self))
            self.dlg_addBase.btn_cancel.clicked.connect(lambda: closeDialog(self.dlg_addBase))
            self.dlg_addBase.show()               
        else:
            self.iface.messageBar().pushMessage("Info", tr('@default','no_db_connection'), level=Qgis.Info)

    def loadSelectedVersion(self):
        try:
            mdlIdx=self.dlg.treeViewVersions.selectedIndexes()[0]
            item=self.model.itemFromIndex(mdlIdx)
            treeItem_load(item, mdlIdx,self)
        except:
            self.iface.messageBar().pushMessage("Info", tr('@default','no_item_selected'), level=Qgis.Info)
            #print('No item selected!')

    def renameVersion(self):
        try:
            mdlIdx=self.dlg.treeViewVersions.selectedIndexes()[0]
            item=self.model.itemFromIndex(mdlIdx)
            level = 0
            index = mdlIdx
            while index.parent().isValid():
                index = index.parent()
                level += 1
            self.treeItem_renameDialog(level, mdlIdx,item)
        except:
            self.iface.messageBar().pushMessage("Info", tr('@default','no_item_selected'), level=Qgis.Info)
            #print('No item selected!')

    def treeItem_renameDialog(self, level, mdlIdx,item):
        """Project version rename dialog --> connects to the TreeItem_Rename function, which does the renameing"""
        #print('Rename dialog started')
        title=tr('@default','rename_version')
        label_text=tr('@default','new_version_name')
        if self.conn:
            description=getDescription(item.text(),self.cur)
            self.dlg_renameVersion = IDA_Districts_NameDialog(title,label_text,item.text(),description)
            self.dlg_renameVersion.btn_ok.clicked.connect(lambda: treeItem_rename(level,mdlIdx,self,self.dlg_renameVersion))
            self.dlg_renameVersion.btn_cancel.clicked.connect(lambda: closeDialog(self.dlg_renameVersion))
            self.dlg_renameVersion.show()               
        else:
            self.iface.messageBar().pushMessage("Info", tr('@default','no_db_connection'), level=Qgis.Info)

    def fileDialog(self,dlg,dir,extensions):
        #print(dir)
        filename, _filter = QFileDialog.getOpenFileName(
            dlg, tr('@default','select_file'),dir, extensions)
        if filename:
            dlg.lineEditFileName.setText(filename)

    def folderDialog(self, dlg, dir,lineEdit):
        folder = QFileDialog.getExistingDirectory(
            dlg,
            tr('@default',"select_directory"),
            dir
        )

        if folder:
            lineEdit.setText(standardizePath(folder))

    def openImportDlg(self,default_path='',title='',ok_fn='',extensions=''):
        #print(default_path)
        if self.conn:
            if self.config['versionName']:
                self.cur=self.conn.cursor(cursor_factory = psycopg2.extras.RealDictCursor)
                self.dlg_import=ImportGeoDataDlg(title=title,default_path=default_path)
                self.dlg_import.btn_import.clicked.connect(lambda: ok_fn(self.dlg_import))
                self.dlg_import.btn_cancel.clicked.connect(lambda: ok_fn(lambda: closeDialog(self.dlg_import)))
                
                self.dlg_import.btn_fileDialog.clicked.connect(lambda: self.fileDialog(self.dlg_import,default_path,extensions))            
                self.dlg_import.show()
            else:
                self.iface.messageBar().pushMessage("Info", tr('@default','no_version_loaded'), level=Qgis.Info)
        else:
            self.iface.messageBar().pushMessage("Info", tr('@default','no_db_connection'), level=Qgis.Info)           

    def importBuildingsFromOSM(self,dlg):
        if self.conn:
            if self.config['versionName']:
                osmBuildingsFileName=dlg.lineEditFileName.text()
                clearOldBuildings=dlg.checkBoxClearOldFeatures.isChecked()     
                dlg.process_running=True
                self.worker_importBuildings = WorkerOSMBuildingsImport(filePath=osmBuildingsFileName,clearOldFeatures=clearOldBuildings,config=self.config,plugin_dir=self.plugin_dir)
                QThreadPool.globalInstance().start(self.worker_importBuildings)
                self.worker_importBuildings.signals.error.connect(show_error_message)
                self.worker_importBuildings.signals.progress.connect(dlg.update_progress)    
                self.worker_importBuildings.signals.finished.connect(lambda s: dlg.update_finished(s,self.dlg))    
            else:
                self.iface.messageBar().pushMessage("Info", tr('@default','no_version_loaded'), level=Qgis.Info)
        else:
            self.iface.messageBar().pushMessage("Info", tr('@default','no_db_connection'), level=Qgis.Info)
  
    def importStreetsFromOSM(self,dlg):
        if self.conn:
            if self.config['versionName']:
                osmStreetFileName=dlg.lineEditFileName.text()
                clearOldStreets=dlg.checkBoxClearOldFeatures.isChecked() 
                dlg.process_running=True
                self.worker_importStreets = WorkerOSMStreetsImport(filePath=osmStreetFileName,clearOldFeatures=clearOldStreets,config=self.config,plugin_dir=self.plugin_dir)
                QThreadPool.globalInstance().start(self.worker_importStreets) 
                self.worker_importStreets.signals.error.connect(show_error_message)
                self.worker_importStreets.signals.progress.connect(dlg.update_progress)
                self.worker_importStreets.signals.finished.connect(lambda s: dlg.update_finished(s,self.dlg))
            else:
                self.iface.messageBar().pushMessage("Info", tr('@default','no_version_loaded'), level=Qgis.Info)
        else:
            self.iface.messageBar().pushMessage("Info", tr('@default','no_db_connection'), level=Qgis.Info)
        
    def importElevationData(self,dlg):
        if self.conn:
            if self.config['versionName']:
                elevationFileName=dlg.lineEditFileName.text()
                clearOldTerrain=dlg.checkBoxClearOldFeatures.isChecked()    
                dlg.process_running=True
                self.worker_importElevevationData = WorkerImportElevationData(filePath=elevationFileName,clearOldTerrain=clearOldTerrain,config=self.config,plugin_dir=self.plugin_dir)
                QThreadPool.globalInstance().start(self.worker_importElevevationData) 
                self.worker_importElevevationData.signals.error.connect(show_error_message)
                self.worker_importElevevationData.signals.progress.connect(dlg.update_progress)
                self.worker_importElevevationData.signals.finished.connect(lambda s: dlg.update_finished(s,self.dlg))
            else:
                self.iface.messageBar().pushMessage("Info", tr('@default','no_version_loaded'), level=Qgis.Info)
        else:
            self.iface.messageBar().pushMessage("Info", tr('@default','no_db_connection'), level=Qgis.Info)     
        
    def show_manageClimateTemplate(self):
        openClimateMacro(self)

    def show_setClimateVersionDialog(self):
        """ show set climate dialog"""
        if self.conn:
            if self.config['versionName']:
                climateData=getClimateData(self.cur,self.config,True)

                self.dlg_climate=ClimateDialog(climateData)
                self.dlg_climate.btn_climateFileDialog.clicked.connect(self.dlg_climate.fileDialog)
                self.dlg_climate.btn_ok.clicked.connect(lambda: writeClimateDataToDB(self.dlg_climate,self))
                self.dlg_climate.btn_cancel.clicked.connect(lambda: closeDialog(self.dlg_climate))
                self.dlg_climate.show()
            else:
                self.iface.messageBar().pushMessage("Info", tr('@default','no_version_loaded'), level=Qgis.Info)        
        else:
            self.iface.messageBar().pushMessage("Info", tr('@default','no_db_connection'), level=Qgis.Info)     

    def showExportProject(self):
        if self.conn:
            self.dlg_export = ExportProjectDialog()
            self.dlg_export.btn_ok.clicked.connect(lambda: exportProject(filename=self.dlg_export.dirname.text()+self.dlg_export.project_name_input.text(),dlg=self.dlg_export,exportPrn=self.dlg_export.exportPrn.isChecked(),exportInvokedFeatures=self.dlg_export.exportInvokedFeatures.isChecked(),exportDBResults=self.dlg_export.exportDBResults.isChecked(),plugin_dir=self.plugin_dir,config=self.config,main_dlg=self.dlg))
            self.dlg_export.btn_cancel.clicked.connect(lambda: closeDialog(self.dlg_export))
            self.dlg_export.show()               
        else:
            self.iface.messageBar().pushMessage("Info", tr('@default','no_db_connection'), level=Qgis.Info)
  

    def importProject(self):
        """ import a project from a sql file and write the data center and modelling files to the folders"""
        #print("--importProject--")
        if self.cur_postgres:
            project_folder = QFileDialog.getExistingDirectory(
                self.dlg,
                tr('@default',"import_districts_project_directory"),
                ''
            )
            project_folder=project_folder.replace('/','\\')

            if project_folder:
                auth_cfg = QgsAuthMethodConfig()
                QgsApplication.authManager().loadAuthenticationConfig(self.config["auth_id"], auth_cfg, True)  
 
                self.worker_import = WorkerImportProject(versionNames=[],projectNames=self.dlg.projectNames,project_name=False,filename=project_folder,config=self.config,plugin_dir=self.plugin_dir,cur=self.cur_postgres,dlg=self.dlg,filter_extensions=[],filter_folders=[],no_db_results=False,password=auth_cfg.config("password"),username=auth_cfg.config("username"))
                self.worker_import.signals.progress.connect(self.dlg.update_progress)
                self.worker_import.signals.error.connect(self.dlg.show_error_message)   
                self.worker_import.signals.finished.connect(lambda: finishedImportProject(dlg=None,main=self,projectName=project_folder.split('\\')[-1].split('.')[0]))                  
                QThreadPool.globalInstance().start(self.worker_import)    
        else:
            self.iface.messageBar().pushMessage("Info", tr('@default','no_db_connection'), level=Qgis.Info)


    def deleteProjectDialog(self):
        """Delete project dialog --> connects to the deleteProject function, which deletes the project"""
        #print('--deleteProjectDialog--')
        projectName=self.dlg.selectProject.currentText()
        if self.conn_postgres != '':
            if projectName:
                title=tr('@default','delete_project')
                label_text=tr('@default','ask_delete_project').format(projectName)
                self.dlg_deleteProject = ApproveDialog(title,label_text)
                self.dlg_deleteProject.btn_ok.clicked.connect(lambda: deleteProject(projectName,self.dlg,self.cur_postgres,self.plugin_dir,self.config,self.model,dlg=self.dlg_deleteProject))
                self.dlg_deleteProject.btn_cancel.clicked.connect(lambda: closeDialog(self.dlg_deleteProject))
                self.dlg_deleteProject.show()               
            else:
                iface.messageBar().pushMessage("Info", tr('@default','no_project_selected'), level=Qgis.Info)
        else:
            iface.messageBar().pushMessage("Info", tr('@default','no_db_connection'), level=Qgis.Info)

    def importPointLayer(self):
        #print('Import plant or customer from point layer')
        if self.conn:
            if self.config['versionName']:
                self.dlg_importPointLayer=ImportPointLayer(self.config,self.plugin_dir)

                self.dlg_importPointLayer.selectLayer.currentTextChanged.connect(lambda s: setLayerListAttributes(s,self.dlg_importPointLayer.listWidget_layerAttributes,self.dlg_importPointLayer))
                self.dlg_importPointLayer.btn_import.pressed.connect(lambda: importLayerToDb('point',self.dlg_importPointLayer,self))
                self.dlg_importPointLayer.btn_cancel.pressed.connect(lambda: closeDialog(self.dlg_importPointLayer))
                self.dlg_importPointLayer.show()     
            else:
                self.iface.messageBar().pushMessage("Info", tr('@default','no_version_loaded'), level=Qgis.Info)
        else:
            self.iface.messageBar().pushMessage("Info", tr('@default','no_db_connection'), level=Qgis.Info)   

    def showGeneratePipebundleTyps(self,dlg):
        """Generate pipe bundle types based on layer attributes"""
        layer=getImportLayer(dlg)
        if layer:
            self.dlg_pipeBundleEditor=PipeBundleEditor(self.config,self.plugin_dir,layer,getLayerAttributesName(layer=layer))
            self.dlg_pipeBundleEditor.btn_cancel.pressed.connect(lambda: closeDialog(self.dlg_pipeBundleEditor))
            self.dlg_pipeBundleEditor.btn_generate.pressed.connect(lambda: generatePipebundleTyps(dlg,self.dlg_pipeBundleEditor, layer,self))
            self.dlg_pipeBundleEditor.show() 
        else:
            iface.messageBar().pushMessage("Info", tr('@default','no_layer_selected'), level=Qgis.Info)
            
    def importNetworkTopologyFromLayer(self):
        #print('Import Network topology from layer')
        if self.conn:
            if self.config['versionName']:
                self.cur=self.conn.cursor(cursor_factory = psycopg2.extras.RealDictCursor)
                self.dlg_importNetworkTopologyFromLayer=ImportNetworkTopologyFromLayer(self.config,self.plugin_dir)

                self.dlg_importNetworkTopologyFromLayer.selectLayer.currentTextChanged.connect(lambda s: setLayerListAttributes(s,self.dlg_importNetworkTopologyFromLayer.listWidget_layerAttributes,self.dlg_importNetworkTopologyFromLayer))
                self.dlg_importNetworkTopologyFromLayer.btn_import.pressed.connect(lambda: importLayerToDb('line',self.dlg_importNetworkTopologyFromLayer,self))
                self.dlg_importNetworkTopologyFromLayer.btn_cancel.pressed.connect(lambda: closeDialog(self.dlg_importNetworkTopologyFromLayer))
                self.dlg_importNetworkTopologyFromLayer.btn_generate_pipe_bundles.pressed.connect(lambda: self.showGeneratePipebundleTyps(self.dlg_importNetworkTopologyFromLayer))
                self.dlg_importNetworkTopologyFromLayer.show() 
            else:
                self.iface.messageBar().pushMessage("Info", tr('@default','no_version_loaded'), level=Qgis.Info)
        else:
            self.iface.messageBar().pushMessage("Info", tr('@default','no_db_connection'), level=Qgis.Info)   
           
    def showCreateNewProject(self):
        """Creates a new Project"""
        
        self.dlg_createNewProject=NewProjectDlg(self.dlg,self.config)
        self.dlg_createNewProject.btn_ok.clicked.connect(lambda: createNewProject(self.dlg_createNewProject,self))
        self.dlg_createNewProject.btn_cancel.clicked.connect(lambda: closeDialog(self.dlg_createNewProject))
        self.dlg_createNewProject.show()   

    def openHelp(self):
        """Open Wiki page"""
        try:
            tab_name=self.dlg.tabWidget.currentWidget().objectName()
            #print(tab_name)
                
            if tab_name=='tab_project_handling':
                url="https://equa.cloud.xwiki.com/xwiki/bin/view/IDA_Districts/Districts_help/Districts_Modeler_help/Districts_Project_handling/"
            elif tab_name=='tab_import':
                url="https://equa.cloud.xwiki.com/xwiki/bin/view/IDA_Districts/Districts_help/Districts_Modeler_help/Districts_Import/"
            elif tab_name=='tab_data_center':
                url="https://equa.cloud.xwiki.com/xwiki/bin/view/IDA_Districts/Districts_help/Districts_Modeler_help/Districts_Resources/"
            elif tab_name=='tab_preprocessing':
                url="https://equa.cloud.xwiki.com/xwiki/bin/view/IDA_Districts/Districts_help/Districts_Modeler_help/Districts_Preprocessing/"
            elif tab_name=='tab_modeling_simulation':
                url="https://equa.cloud.xwiki.com/xwiki/bin/view/IDA_Districts/Districts_help/Districts_Modeler_help/Districts_Modeling-Simulation/"
            elif tab_name=='tab_results_visualization':
                url="https://equa.cloud.xwiki.com/xwiki/bin/view/IDA_Districts/Districts_help/Districts_Modeler_help/Districts_Results-Visualization/"
        except:
            url = "https://equa.cloud.xwiki.com/xwiki/bin/view/IDA_Districts/Districts_GS/"
        webbrowser.open(url)

    def showDistrictsSettings(self):
        """Show districts settings"""
        
        self.dlg_showDistrictsSettings=DistrictsSettingsDialog(self)
        self.dlg_showDistrictsSettings.btn_save.clicked.connect(lambda: self.saveDistrictsSettings(self.dlg_showDistrictsSettings))
        self.dlg_showDistrictsSettings.btn_cancel.clicked.connect(lambda: closeDialog(self.dlg_showDistrictsSettings))
        self.dlg_showDistrictsSettings.show()   

    def show_DefaultsLinesDialog(self):
        """ show the efaultsLinesDialog"""
        if self.conn:
            defaults=getDefaults("lines",self.cur,self.config)
            #print(defaults)
            self.dlg_defaultsLines=DefaultsDialog('line',tr('@default','defaults_layer_lines'),[{'label': tr('@default','type'),'value': ['type',0,'general']},{'label': tr('@default','pipe_bundle'),'value': ['pipe_bundle_type_id',0,'general']},{'label': tr('@default','network'),'value': ['network',0,'general']}],self.cur)
            self.dlg_defaultsLines.btn_ok.clicked.connect(lambda: writeDefaultsToDB(self.dlg_defaultsLines,'lines',self.cur,self.config,self.plugin_dir,self))
            self.dlg_defaultsLines.btn_cancel.clicked.connect(lambda: self.closeDialog(self.dlg_defaultsLines,'',['',[],[],'','',[]]))
            showDefaults(self.dlg_defaultsLines,defaults,'line',self.cur,self.config)
            self.dlg_defaultsLines.show()
        
    def show_DefaultsCustomersDialog(self):
        """ show the efaultsLinesDialog"""
        if self.conn:
            defaults=getDefaults("customers",self.cur,self.config)
            #print(defaults)
            self.dlg_defaultsCustomers=DefaultsDialog('customer',tr('@default','defaults_layer_customers'),[{'label': tr('@default','template'),'value': ['template',0,'general']}],self.cur)
            self.dlg_defaultsCustomers.btn_ok.clicked.connect(lambda: writeDefaultsToDB(self.dlg_defaultsCustomers,'customers',self.cur,self.config,self.plugin_dir,self))
            self.dlg_defaultsCustomers.btn_cancel.clicked.connect(lambda: self.closeDialog(self.dlg_defaultsCustomers,'',['',[],[],'','',[]]))
            showDefaults(self.dlg_defaultsCustomers,defaults,'customer',self.cur,self.config)
            self.dlg_defaultsCustomers.show()
        
    def show_DefaultsPlantsDialog(self):
        """ show the defaultsDialog"""
        if self.conn:
            defaults=getDefaults("energy_plants",self.cur,self.config)
            self.dlg_defaultsPlants=DefaultsDialog('energy_plant',tr('@default','defaults_layer_energy_plants'),[{'label': tr('@default','template'),'value': ['template',0,'general']}],self.cur)
            self.dlg_defaultsPlants.btn_ok.clicked.connect(lambda: writeDefaultsToDB(self.dlg_defaultsPlants,'energy_plants',self.cur,self.config,self.plugin_dir,self))
            self.dlg_defaultsPlants.btn_cancel.clicked.connect(lambda: self.closeDialog(self.dlg_defaultsPlants,'',['',[],[],'','',[]]))
            showDefaults(self.dlg_defaultsPlants,defaults,'energy_plant',self.cur,self.config)
            self.dlg_defaultsPlants.show()

    def manageConnections(self):
        if self.conn:
            #print('Manage connections')
            dropdowns=[[1,'public','prefered_conn_dir','id','name']]
            checkBoxes=[2]
            columns='(id,type,p_ctrl,temp,p,mdot,description)'
            dlg = ConnectionsDialog(tr('@default','connections'),[tr('@default','connection_id'),tr('@default','type'),tr('@default','massflow_set_asboundary_condition'),tr('@default','design_temperature'),tr('@default','design_pressure'),tr('@default','design_massflow'),tr('@default','description')])
            dlg.btn_ok.clicked.connect(lambda: saveConnectionsTable(dlg,'connections',columns,dropdowns,False,checkBoxes,self))
            dlg.btn_cancel.clicked.connect(lambda: closeDialog(dlg))
            dlg.btn_delete.clicked.connect(lambda: deleteTableRowTrace(dlg,True))
            dlg.btn_add.clicked.connect(lambda: addConnectionsTableRow(dlg,dropdowns,0,self.cur))
            showConnectionsContent(dlg,dropdowns,self.conn,self.cur)         
            dlg.tableWidget.itemChanged.connect(dlg.changedItem)
            dlg.show()

    def showImportMeasurements(self):                  
        if self.conn:
            self.dlg_importMeasurements=ImportMeasuremntsDialog()
            
            self.dlg_importMeasurements.btn_file_source.clicked.connect(lambda: dataSourceDialog(self.dlg_importMeasurements,tr('@default','select_file'),'file'))
            self.dlg_importMeasurements.btn_dir_source.clicked.connect(lambda: dataSourceDialog(self.dlg_importMeasurements,tr('@default','select_directory'),'directory'))
            self.dlg_importMeasurements.btn_import.clicked.connect(lambda: importMeasurementData(self.dlg_importMeasurements,self.config,self.cur,self.plugin_dir,self.conn))
            self.dlg_importMeasurements.btn_cancel.clicked.connect(lambda: closeDialog(self.dlg_importMeasurements))
            self.dlg_importMeasurements.show()
        
    def manageConnectionTypes(self):
        if self.conn:
            show_TableDialog(main=self,title=tr('@default','connection_types'),table='connection_types',headers=[tr('@default','connection_type_id'),tr('@default','description')],columns='(id,description)', openFn=show_TableCurrentRowDialog, openFnArg=['connection_type_connections',['sequence','connection_id'],[tr('@default','sequence'),tr('@default','connection_id')],'WHERE connection_type_id =','ORDER BY sequence',[[1,'public','connections','id','description']],False,'','conn_type_trace',False,[]]) #title, table, list: table headers, String: column names, Import button function , Open button function, Open function arguments: table,columns,headers,filter, order by, dropdowns [[column_numbers],[tables],[table_cols],[table_names]],openFunction,openFunctionArg,trace, save as btn,[deactivated]

    def manageConnBundleTypes(self):
        if self.conn:
            show_TableDialog(main=self,title=tr('@default','connection_bundles'),table='conn_bundle_types',headers=[tr('@default','connection_bundle_id'),tr('@default','description')],columns='(id,description)', openFn=show_TableCurrentRowDialog, openFnArg=['bundle_type_conns',['sequence','conn_type_id','description'],[tr('@default','sequence'),tr('@default','type'),tr('@default','description')],'WHERE conn_bundle_type_id =','ORDER BY sequence',[[1,'public','connection_types','id','description']],False,'','bt_conns_trace',False,[0]]) 

    def managePipeBundlesTypes(self):
        if self.conn:
            show_TableDialog(main=self,title=tr('@default','pipe_bundle'),table='pipe_bundle_types',headers=[tr('@default','pipe_bundle_id'),tr('@default','investment_costs'),tr('@default','operating_costs'),tr('@default','description')],columns='(id,invest_costs,operation_costs,description)', openFn=show_TableCurrentRowDialog, openFnArg=['bundle_pipes',['sequence','pipe_id','x','y','ambient'],[tr('@default','sequence'),tr('@default','pipe_id'),tr('@default','x_coord'),tr('@default','y_coord'),tr('@default','ambient')],'WHERE pipe_bundle_type_id =','ORDER BY sequence',[[1,'public','pipes','id','name'],[4,'public','pipe_ambient','id','ambient']],False,'',False,False,[0]]) 
            
    def manageMaterials(self):
        if self.conn:
            show_TableDialog(main=self,title=tr('@default','materials'),table='materials',headers=[tr('@default','id'),tr('@default','name'),tr('@default','thermal_conductivity'),tr('@default','specific_heat'),tr('@default','density')],columns='(id,name,thermal_conductivity_w7mkelvin,specific_heat_j7kgkelvin,density_kg7m3)') 

    def managePipeConstructions(self):
        if self.conn:
            show_TableDialog(main=self,title=tr('@default','constructions'),table='pipe_constructions',headers=[tr('@default','construction_id'),tr('@default','name')],columns='(id,name)', openFn=show_TableCurrentRowDialog,openFnArg=['pipe_layers',['sequence','materialid','thickness'],[tr('@default','sequence'), tr('@default','material'),tr('@default','thickness')],'WHERE pipe_construction_id =','ORDER BY id',[[1,'public','materials','id','name']],False,'',False,False,[0]]) 

    def managePipes(self):
        if self.conn:
            show_TableDialog(main=self,title=tr('@default','pipes'),table='pipes',headers=[tr('@default','id'),tr('@default','name'),tr('@default','inner_diameter'),tr('@default','absolute_roughness'),tr('@default','pipe_construction_id'),tr('@default','pipe_costs'),tr('@default','description')],columns='(id,name,innerpipediameter,piperoughnessfactor,pipe_construction_id,costs,description)',dropdowns=[[4,'public','pipe_constructions','id','name']]) 

    def manageTemplates(self,type):
        if self.conn:
            show_templateDialog(main=self,type=type) 

    def manageNetworks(self):
        if self.conn:
            show_TableDialog(main=self,title=tr('@default','networks'),table='"{}".network'.format(self.config['versionName']),headers=[tr('@default','id'),tr('@default','description')],columns='(id,description)',ok_fn=updateNetworkDependingFields,ok_fn_arg=[self.cur,self.config],deactivated=[]) 

    def pipeLayingAlgorithm(self):
        #print('--pipeLayingAlgorithm--')
        if self.conn:
            if self.config['versionName']:
                self.dlg_pipeLayingAlgorithm=PipeLayingDialog(self.config,self.plugin_dir)
                self.dlg_pipeLayingAlgorithm.show()   
            else:
                self.iface.messageBar().pushMessage("Info", tr('@default','no_version_loaded'), level=Qgis.Info)
        else:
            self.iface.messageBar().pushMessage("Info", tr('@default','no_db_connection'), level=Qgis.Info)   
            
    def generateNetworkTopology(self):
        """ Generate network topology"""
        #print('--generateNetworkTopology--')
        if self.conn:
            if self.config['versionName']:
                self.window_topology=NetworkTopologyDialog(self.config,self.plugin_dir)
                self.window_topology.show() 
            else:
                self.iface.messageBar().pushMessage("Info", tr('@default','no_version_loaded'), level=Qgis.Info)
        else:
            self.iface.messageBar().pushMessage("Info", tr('@default','no_db_connection'), level=Qgis.Info)

    def pipeSizing(self):
        """Pipe sizing"""
        #print('--pipeSizing--')
        if self.conn: 
            if self.config['versionName']:
                self.dlg_pipeSizing=PipeSizingDlg(self.config,self.plugin_dir,self.cur)
                self.dlg_pipeSizing.btn_save.pressed.connect(lambda: savePipeSizingResults(self.config,self.conn,self.dlg_pipeSizing))
                self.dlg_pipeSizing.btn_start.pressed.connect(lambda: startPipeSizing(self.config,self.dlg_pipeSizing,self.plugin_dir))
                self.dlg_pipeSizing.btn_reject.pressed.connect(lambda: rejectPipeSizingResults(self.config,self.conn,self.dlg_pipeSizing))
                
                loadPipes(self.config, self.cur,self.dlg_pipeSizing)
                self.dlg_pipeSizing.show()  
            else:
                self.iface.messageBar().pushMessage("Info", tr('@default','no_version_loaded'), level=Qgis.Info)
        else:
            self.iface.messageBar().pushMessage("Info", tr('@default','no_db_connection'), level=Qgis.Info)
        
    
    def mapFeatures(self):
        """Map Features to lines"""
        #print('--mapFeatures--')
        if self.conn:
            if self.config['versionName']:
                self.window_mapPlants=MapFeaturesDialog(self.config,self.plugin_dir)
                self.window_mapPlants.show()
            else:
                self.iface.messageBar().pushMessage("Info", tr('@default','no_version_loaded'), level=Qgis.Info)
        else:
            self.iface.messageBar().pushMessage("Info", tr('@default','no_db_connection'), level=Qgis.Info)  
            
    def setProjectConfig(self):
        """ open Dialog to set Project configuration data"""
        #print("Project config dialog")
        self.dlg_projectConfig = ProjectConfigDialog()
        self.dlg_projectConfig.btn_ok.clicked.connect(lambda: saveProjectConfigSettings(self.dlg_projectConfig,self))
        self.dlg_projectConfig.btn_cancel.clicked.connect(lambda: closeDialog(self.dlg_projectConfig))
        showProjectConfigData(self.dlg_projectConfig,self)
        self.dlg_projectConfig.show()
        
    def saveDistrictsSettings(self,dlg):
        """Save the districts settings to QSettings"""
        settings = QSettings()        
        settings.beginGroup("districts")
        pathProjects=standardizePath(dlg.lineEdit_pathProjects.text(),create_if_not_exists=False)
        settings.setValue("pathProjects",pathProjects)
        if not os.path.exists(pathProjects):
            dlg_question = QMessageBox(dlg)
            dlg_question.setWindowTitle('IDA Project Path does not exist!')
            dlg_question.setText("""Do you want to create the project folder?""")
            dlg_question.setStandardButtons(QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.Cancel)
            dlg_question.setIcon(QMessageBox.Icon.Question)
            button = dlg_question.exec()

            if button == QMessageBox.StandardButton.Yes:
                os.makedirs(pathProjects)
            else:
                pass
            
        pathDistricts=standardizePath(dlg.lineEdit_pathDistricts.text())
        pathPostgresql=standardizePath(dlg.lineEdit_pathPostgresql.text())
        if not os.path.exists(pathDistricts):
            self.iface.messageBar().pushMessage("Info", "IDA Districts Path does not exist!", level=Qgis.Info)
        settings.setValue("pathDistricts", pathDistricts)
        settings.setValue("pathPostgresql", pathPostgresql)
        settings.setValue("districts_api_delay", dlg.lineEdit_districts_api_delay.text())
        settings.setValue("debug", dlg.checkBox_debug.checkState())
        settings.setValue("autosave", dlg.groupBox_autosave.isChecked())
        settings.setValue("autosave_dt", dlg.lineEdit_autosave_dt.text())
        settings.setValue("exportInvokedFeatures", dlg.checkBox_exportInvokedFeatures.checkState())
        settings.setValue("exportPrn", dlg.checkBox_exportPrn.checkState())
        settings.setValue("exportDbResults", dlg.checkBox_exportDbResults.checkState())
        settings.setValue("host", dlg.lineEdit_host.text())
        settings.setValue("port", dlg.lineEdit_port.text())
        try:
            #print(dlg.auth_dict)
            authId=dlg.auth_dict[dlg.comboBox_AuthId.currentText()]
            settings.setValue("auth_id", authId)
        except:
            authId=''
            settings.setValue("auth_id", authId)
        settings.endGroup()
        
        if dlg.checkBox_debug.isChecked() != self.config["debug"]:
            switchDebugMode(True if dlg.checkBox_debug.isChecked() else False,self.plugin_dir)
      
        #check if db settings settings changed
        if self.config["auth_id"]!=authId or self.config["host"]!=dlg.lineEdit_host.text() or self.config["port"]!=dlg.lineEdit_port.text():
            self.config= load_plugin_settings() #update config
            self.conn_postgres,self.cur_postgres,self.dlg.projectNames=connectDBPostgres(self.config,self.dlg)
            updateProjectNamesList(self.dlg,self.config)
        else:
            self.config= load_plugin_settings() #update config

        self.checkDBConnectionState()

        #timer for autosave
        if self.timer:
            self.timer.stop()
            if self.config['projectName']:
                self.timer.start(int(float(dlg.lineEdit_autosave_dt.text())*60*1000))
            dlg.close()
            
        closeDialog(dlg)
        self.dlg.statusMessage.setText(tr('@default','settings_saved'))

        #print("finished settings")
            
    def setTreeViewModel(self,data):
        self.model = QStandardItemModel()   
        self.model.setHorizontalHeaderLabels(['Version name', 'Description'])
        self.dlg.treeViewVersions.header().setDefaultSectionSize(180)
        self.dlg.treeViewVersions.setModel(self.model)
        importData(self.model,data)
        self.dlg.treeViewVersions.expandAll()
        self.dlg.treeViewVersions.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)           
        
    def loadProject(self):
        #print('--load project--')
        self.conn=dbConnect(self.config,True)
        #print(self.config)
        #print(self.conn)
        if self.conn:
            #print(self.config)
            self.cur = self.conn.cursor(cursor_factory = psycopg2.extras.RealDictCursor)    
            data=getVersionData(self.cur)
            #print(data)
            self.setTreeViewModel(data)
            
            #timer if autosave
            if self.config['autosave']:
                temp_folder = tempfile.gettempdir()+'\\'
                name='ida_districts'
                createDir(temp_folder,name)
                
                #print(temp_folder)
                try:
                    self.timer.stop()
                except:
                    pass
                self.timer = QTimer()
                self.timer.timeout.connect(lambda: exportProject(config=self.config,exportPrn=self.config['exportPrn'],exportInvokedFeatures=self.config['exportInvokedFeatures'],exportDBResults=self.config['exportDbResults'],plugin_dir=self.plugin_dir))
                self.timer.start(int(float(self.config['autosave_dt'])*60*1000))
                
        try:
            self.dlg.labelActiveProject.setText(self.config['projectName'])
            self.dlg.label_activeVersion.setText(self.config['versionName'])
            self.projectConfig = loadProjectConfig(self.config) 
            self.dlg.btn_sridProject.setText('EPSG: '+str(self.projectConfig['srid']))
            self.dlg.statusMessage.setText(tr('@default','project_loaded').format(self.config['projectName']))
        except:
            pass
            
    def selectedProjectUpdated(self,index):
        """load selected project and write it to QSettings and update config"""
        #print('--selectedProjectUpdated--')
        #print(index)        
        self.config['projectName']=self.dlg.selectProject.currentText()
        self.config['versionName']=''
        write_plugin_settings(self.config)
        self.dlg.update_progress(1)
        removeLayers()
        self.loadProject()
        self.dlg.update_progress(100)
        
    def closeDlg(self):
        if self.timer:
            self.timer.stop()
            
        if self.conn_postgres:
            self.conn_postgres.close()    
            #print('close postgres connection')
        if self.conn:
            self.conn.close()
            #print('close project connection')   
            
    def checkDBConnectionState(self):
        if self.conn_postgres:
            self.dlg.connectionStatusIcon.setPixmap(QgsApplication.getThemeIcon("/mIconSuccess.svg").pixmap(16,16))
            self.dlg.labelDBConnectState.setText(tr('@default','db_connected'))
            return True
        else:
            self.dlg.connectionStatusIcon.setPixmap(QgsApplication.getThemeIcon("/mIconCritical.svg").pixmap(16,16))
            self.dlg.labelDBConnectState.setText(tr('@default','db_disconnected'))   
            self.dlg.statusMessage.setText(tr('@default','no_db_connection'))
            return False

    def showModellingSettings(self):
        if self.conn:
            if self.config['versionName']:
                modellingSettings=loadModellingSettings(self.plugin_dir,self.config)
                self.dlg_modellingSettings=ModellingSettings(self.plugin_dir,modellingSettings,self.cur)
                self.dlg_modellingSettings.btn_ok.clicked.connect(lambda: setModellingSettings(self.plugin_dir,self.config,self.dlg_modellingSettings))
                self.dlg_modellingSettings.btn_cancel.clicked.connect(lambda: closeDialog(self.dlg_modellingSettings))
                self.dlg_modellingSettings.show() 
            else:
                self.iface.messageBar().pushMessage("Info", tr('@default','no_version_loaded'), level=Qgis.Info)
        else:
            self.iface.messageBar().pushMessage("Info", tr('@default','no_db_connection'), level=Qgis.Info)  

    def showRequestedOutputs(self):
        if self.conn:
            if self.config['versionName']:
                self.requestedOutputs=loadRequestedOutputs(self.plugin_dir,self.config)
                self.dlg_outputs=RequestedOutputsDialog(self.requestedOutputs)
                self.dlg_outputs.btn_ok.clicked.connect(lambda: setRequestedOutputs(self.config,self.plugin_dir,self.dlg_outputs,self.requestedOutputs))
                self.dlg_outputs.btn_cancel.clicked.connect(lambda: closeDialog(self.dlg_outputs))
                self.dlg_outputs.show()
            else:
                self.iface.messageBar().pushMessage("Info", tr('@default','no_version_loaded'), level=Qgis.Info)
        else:
            self.iface.messageBar().pushMessage("Info", tr('@default','no_db_connection'), level=Qgis.Info)  

    def showParmMapping(self):
        if self.conn:
            if self.config['versionName']:
                self.dlg_featureParm=FeatureModelParmDlg(self.cur,self.config)
                self.dlg_featureParm.btn_add.clicked.connect(lambda: addParmTableRow(self.dlg_featureParm,self.cur,self.config))
                self.dlg_featureParm.btn_remove.clicked.connect(lambda: deleteSelectedTableRow(self.dlg_featureParm.tableWidget_parameters))
                self.dlg_featureParm.btn_ok.clicked.connect(lambda: setFeatureParm(self.dlg_featureParm,self.conn,self.config,self.plugin_dir))
                self.dlg_featureParm.btn_cancel.clicked.connect(lambda: closeDialog(self.dlg_featureParm))
                self.dlg_featureParm.show()
            else:
                self.iface.messageBar().pushMessage("Info", tr('@default','no_version_loaded'), level=Qgis.Info)
        else:
            self.iface.messageBar().pushMessage("Info", tr('@default','no_db_connection'), level=Qgis.Info)  

    def sensorSignals(self):
        """Create sensor signals for comunication between plants, customers and supervisory control"""
        #print('Create sensor signals for comunication between plants, customers and supervisory control')
        if self.conn:
            if self.config['versionName']:
                self.updateSensor=UpdateSensors(config=self.config,cur=self.cur,plugin_dir=self.plugin_dir,dlg_main=self.dlg)
            else:
                self.iface.messageBar().pushMessage("Info", tr('@default','no_version_loaded'), level=Qgis.Info)
        else:
            self.iface.messageBar().pushMessage("Info", tr('@default','no_db_connection'), level=Qgis.Info)  

    def showFeatureModels(self):
        if self.conn:
            if self.config['versionName']:
                self.invoke_features=InvokeFeatures(self.plugin_dir,self.config)
            else:
                self.iface.messageBar().pushMessage("Info", tr('@default','no_version_loaded'), level=Qgis.Info)
        else:
            self.iface.messageBar().pushMessage("Info", tr('@default','no_db_connection'), level=Qgis.Info)  

    def showBuildModel(self):
        """ Build IDA subnetwork models; loop over subnetworks"""
        #print('Build IDA model')
        if self.conn:
            if self.config['versionName']:
                self.dlg_buildModel=BuildNetworkModelDialog(self.dlg)
                sql="""SELECT network FROM "{}".lines GROUP BY network ORDER BY network;""".format(self.config['versionName']) # nosec B608
                self.cur.execute(sql)
                self.dlg_buildModel.combo_network_models.addItem(tr('@default','check_all_items'))
                networks=self.cur.fetchall()
                self.dlg_buildModel.combo_network_models.addItems([str(i['network']) for i in networks])
                for i in range(len(networks)):
                    self.dlg_buildModel.combo_network_models.setItemChecked(i+1,False)
                
                self.dlg_buildModel.combo_submodels.addItem(tr('@default','check_all_items'))
                submodels=getUsedNetworkSubmodels(self.cur,self.config)
                self.dlg_buildModel.combo_submodels.addItems(submodels)
                
                for i in range(len(submodels)):
                    if i==0:
                        self.dlg_buildModel.combo_submodels.setItemChecked(i+1,True)
                    else:
                        self.dlg_buildModel.combo_submodels.setItemChecked(i+1,False)
                self.dlg_buildModel.show()
                self.dlg_buildModel.btn_cancel.clicked.connect(lambda: closeDialog(self.dlg_buildModel))
                self.dlg_buildModel.btn_buildNetworkModel.clicked.connect(lambda: buildModel(self.dlg_buildModel,self))
            else:
                self.iface.messageBar().pushMessage("Info", tr('@default','no_version_loaded'), level=Qgis.Info)
        else:
            self.iface.messageBar().pushMessage("Info", tr('@default','no_db_connection'), level=Qgis.Info)  

    def showOpenModel(self,mode='network'):
        """ Show IDA submodels; loop over submodels"""
        #print('Show IDA model')
        if self.conn:
            if self.config['versionName']:
                dir=self.config['pathProjects']+'{}\\versions\\{}'.format(self.config['projectName'],self.config['versionName'])
                submodels=getNetworkFileSubmodels(dir) if mode=='network' else getBuildingFileSubmodels(dir) 
                if submodels:
                    submodel=submodels[0]
                else:
                    self.iface.messageBar().pushMessage("Info", "Simulation model has not yet been built!", level=Qgis.Info)
                    return False
                fname=dir+'\\{}_{}.idm'.format(mode,submodel)
                if os.path.exists(fname):
                    #print(fname)
                    worker_openNetwork = WorkerOpenModelCmd(fname,self.config,submodel=submodel)
                    QThreadPool.globalInstance().start(worker_openNetwork) 
                    worker_openNetwork.signals.error.connect(show_error_message)
                    worker_openNetwork.signals.progress.connect(self.dlg.update_progress)
                    """
                    self.dlg_openModel=OpenModelDialog(self.dlg,mode)
                    self.dlg_openModel.combo_submodels.addItem(tr('@default','check_all_items'))
                        #print(submodels)
                        self.dlg_openModel.combo_submodels.addItems(submodels)
                        for i in range(len(submodels)):
                            self.dlg_openModel.combo_submodels.setItemChecked(i+1,False)
                        self.dlg_openModel.show()
                        self.dlg_openModel.btn_cancel.clicked.connect(lambda: closeDialog(self.dlg_openModel))
                        self.dlg_openModel.btn_openModel.clicked.connect(lambda: openModel(self.dlg_openModel,self.plugin_dir,self.config,mode))  
                    """
                    
                else:
                    self.iface.messageBar().pushMessage("Info", "Simulation model has not yet been built!", level=Qgis.Info)
            else:
                self.iface.messageBar().pushMessage("Info", tr('@default','no_version_loaded'), level=Qgis.Info)
        else:
            self.iface.messageBar().pushMessage("Info", tr('@default','no_db_connection'), level=Qgis.Info)  

    def showRunModel(self):
        """ Run IDA submodels; loop over submodels"""
        #print('Run IDA model')
        if self.conn:
            if self.config['versionName']:
                self.dlg_runModel=RunNetworkModelDialog(self.plugin_dir,self.config)
                self.dlg_runModel.combo_submodels.addItem(tr('@default','check_all_items'))
                dir=self.config['pathProjects']+'{}\\versions\\{}'.format(self.config['projectName'],self.config['versionName'])
                submodels=getNetworkFileSubmodels(dir)
                self.dlg_runModel.combo_submodels.addItems(submodels)
                for i in range(len(submodels)):
                    if i==0:
                        self.dlg_runModel.combo_submodels.setItemChecked(int(i)+1,True)
                    else:
                        self.dlg_runModel.combo_submodels.setItemChecked(int(i)+1,False)
                self.dlg_runModel.show()
                self.dlg_runModel.btn_cancel.clicked.connect(lambda: closeDialog(self.dlg_runModel))
                self.dlg_runModel.btn_runModel.clicked.connect(lambda: runModel(self.dlg_runModel,self.plugin_dir,self.config))
            else:
                self.iface.messageBar().pushMessage("Info", tr('@default','no_version_loaded'), level=Qgis.Info)
        else:
            self.iface.messageBar().pushMessage("Info", tr('@default','no_db_connection'), level=Qgis.Info)  

    def showLoadResults(self):
        """ show load results; loop over submodels"""
        #print('load results')
        if self.conn:
            if self.config['versionName']:
                self.cur=self.conn.cursor(cursor_factory = psycopg2.extras.RealDictCursor)    
                self.dlg_loadResults=LoadResultsDialog(self.dlg)
                self.dlg_loadResults.combo_submodels.addItem(tr('@default','check_all_items'))
                dir=self.config['pathProjects']+'{}\\versions\\{}'.format(self.config['projectName'],self.config['versionName'])
                submodels=getNetworkFileSubmodels(dir)
                self.dlg_loadResults.combo_submodels.addItems(submodels)
                for i in range(len(submodels)):
                   self. dlg_loadResults.combo_submodels.setItemChecked(int(i)+1,False)
                self.dlg_loadResults.show()
                self.dlg_loadResults.btn_cancel.clicked.connect(lambda: closeDialog(self.dlg_loadResults))
                self.dlg_loadResults.btn_loadResults.clicked.connect(lambda: loadResults(self.dlg_loadResults,self))
            else:
                self.iface.messageBar().pushMessage("Info", tr('@default','no_version_loaded'), level=Qgis.Info)
        else:
            self.iface.messageBar().pushMessage("Info", tr('@default','no_db_connection'), level=Qgis.Info)  

    def showPathReports(self):                  
        if self.conn:
            self.dlg_pathReports=IDADistrictsPathReportsDialog(self.cur,self.config,self.dlg)
            self.dlg_pathReports.btn_ok.clicked.connect(lambda: runPathReports(self.dlg_pathReports,self))
            self.dlg_pathReports.btn_cancel.clicked.connect(lambda: closeDialog(self.dlg_pathReports))
            self.dlg_pathReports.btn_addSelectedIDs.clicked.connect(lambda: addSelectedIDs(self.dlg_pathReports))
            self.dlg_pathReports.btn_deleteIDs.clicked.connect(lambda: deleteIDs(self.dlg_pathReports))
            self.dlg_pathReports.btn_addID.clicked.connect(lambda: addID(self.dlg_pathReports))
            
            self.cur.execute('SELECT network FROM "{}".lines GROUP BY network;'.format(self.config['versionName'])) # nosec B608
            self.dlg_pathReports.network.addItems([str(i['network']) for i in self.cur.fetchall()])
            self.dlg_pathReports.show()
        else:
            self.iface.messageBar().pushMessage("Info", tr('@default','no_db_connection'), level=Qgis.Info)  

    def loadProfiles(self):                   
        if self.conn:
            self.dlg_plotLoads=PlotLoadProfilesDialog()
            self.dlg_plotLoads.btn_plot.clicked.connect(lambda: plotLoadProfiles(self.dlg_plotLoads,self.plugin_dir,self.config,self.cur))
            self.dlg_plotLoads.btn_cancel.clicked.connect(lambda: closeDialog(self.dlg_plotLoads))
            self.dlg_plotLoads.rbtn_customer.toggled.connect(lambda: loadFeatureIds(self.dlg_plotLoads,self.cur,self.config))
            self.dlg_plotLoads.rbtn_energy_plant.toggled.connect(lambda: loadFeatureIds(self.dlg_plotLoads,self.cur,self.config))
            
            self.dlg_plotLoads.combo_networks.addItem(tr('@default','check_all_items'))
            networks=getNetworks(self.cur,self.config)
            self.dlg_plotLoads.combo_networks.addItems([str(i) for i in networks])
            for i in range(len(networks)):
                self.dlg_plotLoads.combo_networks.setItemChecked(i+1,False)
            self.dlg_plotLoads.combo_networks.activated.connect(lambda: loadFeatureIds(self.dlg_plotLoads,self.cur,self.config))
            loadFeatureIds(self.dlg_plotLoads,self.cur,self.config)
            self.dlg_plotLoads.show()
        else:
            self.iface.messageBar().pushMessage("Info", tr('@default','no_db_connection'), level=Qgis.Info)  

    def showSupervisoryCtrl(self):
        if self.conn:
            if self.config['versionName']:
                openSupervisoryCtrl(self.cur,self.plugin_dir,self.config)
                """
                self.dlg_supervisoryCrtl=SupervisoryCtrlDlg(self.cur,self.config)
                self.dlg_supervisoryCrtl.btn_ok.clicked.connect(lambda: setSupervisoryCrtlSubmodel(self.dlg_supervisoryCrtl,self.cur))
                self.dlg_supervisoryCrtl.btn_cancel.clicked.connect(lambda: closeDialog(self.dlg_supervisoryCrtl))
                self.dlg_supervisoryCrtl.btn_open.clicked.connect(lambda: openSupervisoryCtrl(self.dlg_supervisoryCrtl,self.cur,self.plugin_dir,self.config))
                self.dlg_supervisoryCrtl.show()  
                """
            else:
                self.iface.messageBar().pushMessage("Info", tr('@default','no_version_loaded'), level=Qgis.Info)
        else:
            self.iface.messageBar().pushMessage("Info", tr('@default','no_db_connection'), level=Qgis.Info)   
            
    def showDataOnMap(self,networkReportDlg=None,function_items=None,time_values=None):                      
        if self.conn:
            self.dlg_showOnMap=ShowOnMapDialog(self.cur,self.config,self.plugin_dir,self.dlg,networkReportDlg=networkReportDlg,function_items=function_items,time_values=time_values)
            self.dlg_showOnMap.btn_showOnMap.clicked.connect(lambda: showOnMap(self.dlg_showOnMap,self,networkReportDlg=networkReportDlg))
            self.dlg_showOnMap.btn_cancel.clicked.connect(lambda: closeDialog(self.dlg_showOnMap))
            self.dlg_showOnMap.featureGroupChanged(False)
            self.dlg_showOnMap.show()
        else:
            self.iface.messageBar().pushMessage("Info", tr('@default','no_db_connection'), level=Qgis.Info) 
            
    def showNetworkReport(self):                      
        if self.conn:
            self.dlg_networkReport=NetworkReportDialog(self.plugin_dir)
            self.dlg_networkReport.btn_addPlot.clicked.connect(lambda: self.showDataOnMap(networkReportDlg=self.dlg_networkReport,function_items=['Max','Min','Average','Sum','Last value','First value'],time_values=[]))
            self.dlg_networkReport.btn_deletePlot.clicked.connect(self.dlg_networkReport.deleteTableRow)
            self.dlg_networkReport.btn_ok.clicked.connect(lambda: networkReport(self.dlg_networkReport,self.plugin_dir,self.cur,self.config,self.dlg))
            self.dlg_networkReport.btn_cancel.clicked.connect(lambda: closeDialog(self.dlg_networkReport))
            self.dlg_networkReport.show()
        else:
            self.iface.messageBar().pushMessage("Info", tr('@default','no_db_connection'), level=Qgis.Info)  
            
    def on_tab_changed(self,index):
        widget = self.dlg.tabWidget.widget(index)           # get the QWidget for the tab
        if widget.objectName() == "tab_import":  # compare with object name
            #print("Import tab selected")

            canvas = iface.mapCanvas()

            # OSM layer (Web Mercator)
            url_with_type = "type=xyz&url=https://tile.openstreetmap.org/{z}/{x}/{y}.png"
            osm_layer = QgsRasterLayer(url_with_type, "OSM Base Map", "wms")  # "wms" works for XYZ layers in Python API

            if not osm_layer.isValid():
                raise Exception("OSM layer failed to load!")

            # Transform canvas extent from current CRS to OSM CRS (EPSG:3857)
            crs_src = canvas.mapSettings().destinationCrs()
            crs_dest = osm_layer.crs()  # EPSG:3857
            transform = QgsCoordinateTransform(crs_src, crs_dest, QgsProject.instance())
            extent_3857 = transform.transformBoundingBox(canvas.extent())

            # Map settings
            settings = QgsMapSettings()
            settings.setLayers([osm_layer])
            settings.setOutputSize(canvas.size())
            settings.setExtent(extent_3857)
            settings.setBackgroundColor(Qt.GlobalColor.white)

            # Render
            img = QImage(
                settings.outputSize().width(),
                settings.outputSize().height(),
                QImage.Format.Format_ARGB32_Premultiplied
            )
            img.fill(Qt.GlobalColor.white)
            job = QgsMapRendererParallelJob(settings)
            job.start()
            job.waitForFinished()
            img = job.renderedImage()
            img.save(self.plugin_dir+"\icons\osm_snapshot.png")
            #print("Snapshot saved!")
            
            load_image(self.plugin_dir+"\icons\osm_snapshot.png",self.dlg.labelOsmPreview,scale=True)
            #self.svg_osm.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)

            
    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.first_start == True:
            self.first_start = False
            self.dlg = DistrictsDialog(self.plugin_dir)
            
            #general
            self.dlg.btn_settings.clicked.connect(self.showDistrictsSettings)
            self.dlg.btn_help.clicked.connect(self.openHelp)
            self.dlg.tabWidget.currentChanged.connect(self.on_tab_changed)
            
            #project handling connections
            self.dlg.btn_createProject.clicked.connect(self.showCreateNewProject)
            self.dlg.btn_deleteProject.clicked.connect(self.deleteProjectDialog)
            
            try: 
                self.dlg.btn_sridProject.setText('EPSG: '+str(self.projectConfig['srid']))
            except:
                pass
            self.dlg.btn_sridProject.clicked.connect(self.setProjectConfig)
            self.dlg.btn_importProject.clicked.connect(self.importProject)
            self.dlg.btn_exportProject.clicked.connect(self.showExportProject)
            self.dlg.btn_addVersion.clicked.connect(self.addBaseVersionDialog)
            self.dlg.btn_loadVersion.clicked.connect(self.loadSelectedVersion)
            self.dlg.btn_deleteVersion.clicked.connect(self.showDeleteVersionDialog)
            
            self.dlg.treeViewVersions.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
            self.dlg.treeViewVersions.customContextMenuRequested.connect(self.openMenu)
            self.dlg.signals.close.connect(self.closeDlg)
            
            #import connections
            self.dlg.btn_importStreetsFromOSM.clicked.connect(lambda: self.openImportDlg(default_path="",title=tr('@default','osm_streets'),ok_fn=self.importStreetsFromOSM,extensions='*.osm'))
            self.dlg.btn_importBuildingsFromOSM.clicked.connect(lambda: self.openImportDlg(default_path="",title=tr('@default','osm_buildings'),ok_fn=self.importBuildingsFromOSM,extensions='*.osm'))
            self.dlg.btn_importElevationData.clicked.connect(lambda: self.openImportDlg(default_path="",title=tr('@default','elevation_data'),ok_fn=self.importElevationData,extensions='*.hgt;*.tif;*.tiff'))
            self.dlg.btn_importPointLayer.clicked.connect(self.importPointLayer)
            self.dlg.btn_importNetworkTopologyFromLayer.clicked.connect(self.importNetworkTopologyFromLayer)

            #resources
            self.dlg.btn_climateTemplate.clicked.connect(self.show_manageClimateTemplate)
            self.dlg.btn_defaults_lines.clicked.connect(self.show_DefaultsLinesDialog)
            self.dlg.btn_defaults_customers.clicked.connect(self.show_DefaultsCustomersDialog)
            self.dlg.btn_defaults_plants.clicked.connect(self.show_DefaultsPlantsDialog)
            self.dlg.btn_connections.clicked.connect(lambda: self.manageConnections())
            self.dlg.btn_connection_types.clicked.connect(lambda: self.manageConnectionTypes())
            self.dlg.btn_conn_bundle_types.clicked.connect(lambda: self.manageConnBundleTypes())
            self.dlg.btn_pipe_bundle_types.clicked.connect(lambda: self.managePipeBundlesTypes())
            self.dlg.btn_materials.clicked.connect(lambda: self.manageMaterials())
            self.dlg.btn_pipe_constructions.clicked.connect(lambda: self.managePipeConstructions())
            self.dlg.btn_pipes.clicked.connect(lambda: self.managePipes())
            self.dlg.btn_manageCustomerTemplates.clicked.connect(lambda: self.manageTemplates('customer'))
            self.dlg.btn_manageEnergyPlantTemplates.clicked.connect(lambda: self.manageTemplates('energy_plant'))
            
            #preprocessing
            self.dlg.btn_manageNetworks.clicked.connect(self.manageNetworks)
            self.dlg.btn_pipeLayingAlgorithm.clicked.connect(self.pipeLayingAlgorithm)
            self.dlg.btn_generateTopology.clicked.connect(self.generateNetworkTopology)
            self.dlg.btn_pipeSizing.clicked.connect(self.pipeSizing)
            self.dlg.btn_mapFeatures.clicked.connect(self.mapFeatures)
            
            #modeling and simulation
            self.dlg.btn_modellingSettings.clicked.connect(self.showModellingSettings)
            self.dlg.btn_requestedOutputs.clicked.connect(self.showRequestedOutputs)
            self.dlg.btn_parmMapping.clicked.connect(self.showParmMapping)
            self.dlg.btn_sensorSignals.clicked.connect(self.sensorSignals)
            self.dlg.btn_supervisory.clicked.connect(self.showSupervisoryCtrl)
            self.dlg.btn_climateVersionData.clicked.connect(self.show_setClimateVersionDialog)
            self.dlg.btn_featureModels.clicked.connect(self.showFeatureModels)
            self.dlg.btn_buildModel.clicked.connect(self.showBuildModel)
            self.dlg.btn_openModel.clicked.connect(lambda: self.showOpenModel(mode='network'))
            self.dlg.btn_runModel.clicked.connect(self.showRunModel)
            
            #results and visualization
            self.dlg.btn_loadResults.clicked.connect(self.showLoadResults)
            self.dlg.btn_path_reports.clicked.connect(self.showPathReports)
            self.dlg.btn_plotLoadProfiles.clicked.connect(self.loadProfiles)
            self.dlg.btn_showDataOnMap.clicked.connect(self.showDataOnMap)            
            self.dlg.btn_networkReport.clicked.connect(self.showNetworkReport)         
            self.dlg.btn_importMeasurements.clicked.connect(self.showImportMeasurements)
            
            
            # show the dialog
            self.dlg.show()
            
            #connect to DB and load project and version
            self.dlg.update_progress(0)
            self.conn_postgres,self.cur_postgres,self.dlg.projectNames=connectDBPostgres(self.config,self.dlg)
            if self.config['projectName']:
                self.loadProject()
            if self.config['versionName']:
                loadVersion(main=self)
                
            self.dlg.selectProject.currentIndexChanged.connect(self.selectedProjectUpdated)
            self.dlg.update_progress(100)
            if self.checkDBConnectionState():
                self.dlg.statusMessage.setText('Districts Modeler is ready!')

            self.dlg.setWindowIcon(QIcon(self.icon_path))
            checkToolPaths(self.config)
        else:
            self.dlg.show()
            self.dlg.activateWindow()
            #print("activate")
