# -*- coding: utf-8 -*-
"""
/***************************************************************************
 QTalsim
                                 A QGIS plugin
 This Plugin creates Hydrologic Response Units (HRUs), that can be used as input data for Talsim.
 Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/
                              -------------------
        begin                : 2023-10-12
        git sha              : $Format:%H$
        copyright            : (C) 2023 by SYDRO Consult GmbH
        email                : l.treitler@sydro.de
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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 QSettings, QTranslator, QCoreApplication, Qt
from qgis.PyQt.QtGui import QIcon, QCursor, QMovie
from qgis.PyQt.QtWidgets import QMainWindow, QAction, QTableWidgetItem, QComboBox, QFileDialog, QInputDialog, QDialogButtonBox, QCompleter, QAbstractItemView, QRadioButton, QMenu, QToolButton, QDockWidget, QMessageBox, QApplication, QDialog, QPushButton, QGroupBox
from qgis.PyQt.QtCore import QVariant, QTimer, pyqtSignal, QEvent, QObject
try:
    from .resources import *
except:
    from resources import *
try:
    from .qtalsim_dialog import QTalsimDialog
    from .qtalsim_sqllite_dialog import SQLConnectDialog
    from .qtalsim_subbasin_dialog import SubBasinPreprocessingDialog
    from .qtalsim_soil_dialog import SoilPreprocessingDialog
    from .qtalsim_landuse_dialog import LanduseAssignmentDialog
except:
    from qtalsim_dialog import QTalsimDialog
    from qtalsim_sqllite_dialog import SQLConnectDialog
    from qtalsim_subbasin_dialog import SubBasinPreprocessingDialog
    from qtalsim_soil_dialog import SoilPreprocessingDialog
    from qtalsim_landuse_dialog import LanduseAssignmentDialog
import os.path
from qgis.core import QgsProject, QgsField, QgsVectorLayer, QgsRasterLayer, QgsFeature, QgsGeometry, QgsSpatialIndex, Qgis, QgsMessageLog, QgsLayerTreeGroup, QgsLayerTreeLayer, QgsProcessingFeedback, QgsWkbTypes, QgsFeatureRequest, QgsMapLayer, QgsFields, QgsTask, QgsTaskManager, QgsApplication, QgsExpression
from qgis.analysis import QgsGeometrySnapper
import processing
import pandas as pd
import webbrowser
import numpy as np
from collections import defaultdict
import re
import csv
import sqlite3


class QTalsim:
    """QGIS Plugin Implementation."""

    def __init__(self, iface):
        """Constructor.

        :param iface: An interface instance that will be passed to this class
            which provides the hook by which you can manipulate the QGIS
            application at run time.
        :type iface: QgsInterface
        """
        # Save reference to the QGIS interface
        self.iface = iface
        # initialize plugin directory
        self.plugin_dir = os.path.dirname(__file__)
        # initialize locale
        locale = QSettings().value('locale/userLocale')[0:2]
        locale_path = os.path.join(
            self.plugin_dir,
            'i18n',
            'QTalsim_{}.qm'.format(locale))

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

        # Declare instance attributes
        self.actions = []
        self.menu = self.tr(u'&QTalsim')
        # Check if plugin was started the first time in current QGIS session
        # Must be set in initGui() to survive plugin reloads

        self.first_start = None
        self.initialize_parameters()

    def show_message_log_panel(self):
        # Access the QGIS main window's status bar
        message_log_viewer = self.iface.mainWindow().findChild(QDialog, 'QgsMessageLogViewer')
        if message_log_viewer:
            message_log_viewer.show()
            message_log_viewer.raise_()
            message_log_viewer.activateWindow()

    def initialize_parameters(self):
        '''
            Initialize the parameters
        '''
        self.dialog_status = None
        self.unique_values_landuse = set()

        self.noLayerSelected = 'No Layer selected'
        self.selected_layer_ezg = None
        self.ezgLayer = None
        self.clippingEZG = None
        self.soilTalsim = None
        self.soilLayer = None
        self.soilFieldNames = []
        self.soilLayerIntermediate = None
        self.soilGaps = None

        self.landuseLayer = None
        self.landuseFields = []
        self.landuseField = None
        self.landuseTalsim = None
        self.dfLanduseTalsim = None
        self.selected_landuse_parameters = []
        self.delimiter = ','
        self.finalLayer = None
        self.parameterFieldsLanduse = None
        self.landuseGaps = None

        #DEM/Slope
        self.demLayer = None
        self.slopeLayer = None

        #Field Names (soil names must be the same name as in soilParameter.csv)
        self.slopeFieldName = 'Slope'
        self.IDSoil = 'ID_Soil'
        self.nameSoil = 'NameSoil'
        self.fieldNameAreaEFL = "PercentageShare"
        self.fieldLanduseID = 'ID_LNZ'
        self.nameLanduse = 'Name'
        self.soilTypeThickness = 'LayerThickness1'
        self.soilTextureId1 = 'SoilTextureId1'
        self.soilDescription = 'Description'
        self.hruSoilTypeId = 'SoilTypeId'
        self.hruLandUseId = 'LandUseId'

        self.subBasinUI = 'EZG'

        #Output
        self.soilTypeList = []
        self.finalLayer = None
        self.eflLayer = None 
        self.landuseFinal = None
        self.soilTextureFinal = None
        self.soilTypeFinal = None
        self.outputFolder = None

        self.file_path_db = None

        self.geopackage_path = None

        self.last_logged_progress = 0

    # 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('QTalsim', 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)

        self.menu = 'QTalsim'

        if not hasattr(self, 'plugin_menu'):
            self.plugin_menu = QMenu(self.menu, parent)
            self.iface.mainWindow().menuBar().insertMenu(self.iface.firstRightStandardMenu().menuAction(), self.plugin_menu)

        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.dropdownMenu.addAction(action)

            #self.iface.addToolBarIcon(action)

        if add_to_menu:
            self.plugin_menu.addAction(action)

        self.actions.append(action)

        return action

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

        self.actions = []  # Ensure this is initialized
        self.menu = 'QTalsim'
        
        # Create the plugin menu once
        if not hasattr(self, 'plugin_menu'):
            self.plugin_menu = QMenu(self.menu, self.iface.mainWindow())
            menuBar = self.iface.mainWindow().menuBar()
            menuBar.insertMenu(self.iface.firstRightStandardMenu().menuAction(), self.plugin_menu)
        
        # Create toolbar and toolButton once
        if not hasattr(self, 'toolbar'):
            self.toolbar = self.iface.addToolBar(u'QTalsim')
            self.toolbar.setObjectName(u'QTalsim')
            self.toolButton = QToolButton()
            self.toolbar.addWidget(self.toolButton)
            self.dropdownMenu = QMenu(self.iface.mainWindow())
            self.toolButton.setMenu(self.dropdownMenu)
            self.toolButton.setPopupMode(QToolButton.InstantPopup)
            icon_path = ':/plugins/qtalsim/icon.png'
            self.toolButton.setIcon(QIcon(icon_path))
        
        # Add actions
        icon_path = ':/plugins/qtalsim/icon.png'
        self.add_action(icon_path, text=self.tr(u'HRU calculation'), callback=self.run, parent=self.iface.mainWindow(), add_to_toolbar=True)
        #self.add_action(icon_path, text=self.tr(u'Connect to Talsim DB'), callback=self.open_secondary_window, parent=self.iface.mainWindow(), add_to_toolbar=True)
        self.add_action(icon_path, text=self.tr(u'Connect to Talsim DB'), callback=self.open_sql_connect_dock, parent=self.iface.mainWindow(), add_to_toolbar=True)
        self.add_action(icon_path, text=self.tr(u'Sub-basin preprocessing'), callback=self.open_sub_basin_window, parent=self.iface.mainWindow(), add_to_toolbar=True)
        self.add_action(icon_path, text=self.tr(u'ISRIC Soil Type Converter'), callback=self.open_soil_window, parent=self.iface.mainWindow(), add_to_toolbar=True)
        self.add_action(icon_path, text=self.tr(u'Land use mapping'), callback=self.open_landuse_window, parent=self.iface.mainWindow(), add_to_toolbar=True)
        
        self.first_start = True
        self.initialize_parameters()

        self.show_message_log_panel()

    def unload(self):
        """Removes the plugin menu item and icon from QGIS GUI."""
        for action in self.actions:
            if hasattr(self, 'plugin_menu') and self.plugin_menu is not None:
                self.plugin_menu.removeAction(action)
            self.iface.removeToolBarIcon(action)

        if hasattr(self, 'toolbar'):
            self.iface.mainWindow().removeToolBar(self.toolbar)
            self.toolbar = None
        
        # Now remove the entire custom menu from the main menu bar
        if hasattr(self, 'plugin_menu') and self.plugin_menu is not None:
            self.iface.mainWindow().menuBar().removeAction(self.plugin_menu.menuAction())
            self.plugin_menu = None


    class CustomFeedback(QgsProcessingFeedback):
        def __init__(self, log_function):
            super().__init__()
            self.log_function = log_function

        def setProgress(self, progress):
            pass
            #self.log_function(f"Progress: {progress}%", Qgis.Info)

        def pushInfo(self, info):
            self.log_function(f"Info: {info}", Qgis.Info)
        
        def pushWarning(self, warning):
            self.log_function(f"Warning: {warning}", Qgis.Warning)
        
        def reportError(self, error, fatalError=False):
            level = Qgis.Critical if fatalError else Qgis.Warning
            self.log_function(f"Error: {error}", level)
    
    '''
        QTalsim Functions
    '''
    def start_operation(self):
        QApplication.setOverrideCursor(Qt.WaitCursor)  # Set cursor to busy

    def end_operation(self):
        QApplication.restoreOverrideCursor()  # Restore the default cursor

    def update_text_overview(self, text):
        #Set the text in the QTextBrowser
        self.dlg.textOverview.clear()
        self.dlg.textOverview.setText(text)

    def load_group_boxes_from_csv(self, path):
        """
        Load group boxes and associated text from a CSV file.
        """
        group_boxes = {}

        # Read the CSV file
        with open(path, mode='r', newline='', encoding='cp1252') as csvfile:
            reader = csv.DictReader(csvfile, delimiter = ';')
            for row in reader:
                widget_name = row['GroupBox']
                text = row['Text']

                # Retrieve the widget from self.dlg using its name
                widget = getattr(self.dlg, widget_name, None)
                if widget is not None:
                    group_boxes[widget] = text

        return group_boxes

    def log_to_qtalsim_tab(self, message, level):
        '''
            Logging to a QTalsim tab in the current Qgis Project.
        '''
        try:
            QgsMessageLog.logMessage(message, 'QTalsim', level)
            QCoreApplication.processEvents()
        except Exception as e:
            pass
    
    def layersAddedHandler(self):
        '''
            QTimer is needed to ensure that added layers are correctly added to the comboboxes.
        '''
        QTimer.singleShot(100, self.fillPolygonsCombobox)

    def fillPolygonsCombobox(self):
        '''
            Fills the combobox of sub-basins/land uses/soils layers with all polygon layers in the project.        
        '''
        #Get Layers
        layers, rasterLayers = self.getAllLayers(QgsProject.instance().layerTreeRoot())
        current_text = self.dlg.comboboxEZGLayer.currentText()
        self.dlg.comboboxEZGLayer.clear() #clear combobox EZG from previous runs
        self.dlg.comboboxEZGLayer.addItem(self.noLayerSelected)
        self.dlg.comboboxEZGLayer.addItems([layer.name() for layer in layers])
        index = self.dlg.comboboxEZGLayer.findText(current_text)
        if index != -1:
            self.dlg.comboboxEZGLayer.setCurrentIndex(index)

        self.safeConnect(self.dlg.comboboxEZGLayer.currentIndexChanged, self.on_ezg_changed)

        #Soil Layer
        current_text = self.dlg.comboboxSoilLayer.currentText()
        self.dlg.comboboxSoilLayer.clear() #clear combobox soil from previous runs
        self.dlg.comboboxSoilLayer.addItems([layer.name() for layer in layers])
        index = self.dlg.comboboxSoilLayer.findText(current_text)
        if index != -1:
            self.dlg.comboboxSoilLayer.setCurrentIndex(index)

        #Land use layer
        current_text = self.dlg.comboboxLanduseLayer.currentText()
        self.dlg.comboboxLanduseLayer.clear() #clear combobox Landuse from previous runs
        self.dlg.comboboxLanduseLayer.addItems([layer.name() for layer in layers])
        index = self.dlg.comboboxLanduseLayer.findText(current_text)
        if index != -1:
            self.dlg.comboboxLanduseLayer.setCurrentIndex(index)

        #DEM Layer
        current_text = self.dlg.comboboxDEMLayer.currentText()
        self.dlg.comboboxDEMLayer.clear() #clear combobox Landuse from previous runs
        self.dlg.comboboxDEMLayer.addItem("Optional: Upload DEM Layer") 
        self.dlg.comboboxDEMLayer.addItems([layer.name() for layer in rasterLayers])
        index = self.dlg.comboboxDEMLayer.findText(current_text)
        if index != -1:
            self.dlg.comboboxDEMLayer.setCurrentIndex(index)

    def update_layer_name(self, layer_name, function):
        '''
            Function to set the correct layer name of the edited soil/land use layer.
        '''
        
        if function == "overlap":
            task_suffix = "DeleteOverlaps"
        elif function == "gaps":
            task_suffix = "FillGaps"

        # Check if the layer name starts with the specified prefix
        if task_suffix in layer_name:
            # Strip off the task_suffix to handle numbers correctly
            suffix_index = layer_name.rfind(task_suffix)
            suffix_end_index = suffix_index + len(task_suffix)
            next_char = layer_name[suffix_end_index:suffix_end_index+1]
            
            # Check if there's a numeric part following the task_suffix
            if next_char.isdigit():
                # If there's a number, increment it
                new_number = int(next_char) + 1
                new_name = f"{layer_name[:suffix_end_index]}{new_number}{layer_name[suffix_end_index+1:]}"
            else:
                # If no number, this is the first addition of a number
                new_name = f"{layer_name[:suffix_end_index]}2{layer_name[suffix_end_index:]}"
        else:
            new_name = f"{layer_name}{task_suffix}"

        return new_name

    def checkOverlappingFeatures(self, layer):
        '''
            Checks for overlapping features in an input layer.
        '''
        layerName = layer.name()
        layer, _ = self.make_geometries_valid(layer)
        layer = processing.run("native:multiparttosingleparts", {'INPUT': layer,'OUTPUT': 'TEMPORARY_OUTPUT'}, feedback=None)['OUTPUT']
        index = QgsSpatialIndex()
        feature_dict = {feature.id(): feature for feature in layer.getFeatures()} #Dict with all features
        geometry_dict = {fid: feature.geometry() for fid, feature in feature_dict.items()} #Dict with all geometries

        checked_ids = set()
        overlapping_features = []

        for fid, geom in geometry_dict.items():
            index.addFeature(feature_dict[fid]) #Create Spatial Index

        count_all_features = len(feature_dict)
        last_logged_progress = 0
        analysed_features = 0

        for fid, geom in geometry_dict.items():
            candidate_ids = index.intersects(geom.boundingBox())

            #Logging the process
            analysed_features += 1
            progress = (analysed_features/count_all_features)*100
            if progress - last_logged_progress >= 10:
                self.log_to_qtalsim_tab(f"Progress: {progress:.2f}% done", Qgis.Info)
                last_logged_progress = progress
                #self.dlg.progressbar.setValue(int(last_logged_progress))

            for cid in candidate_ids:
                if cid == fid or (fid, cid) in checked_ids or (cid, fid) in checked_ids:
                    continue  #Skip if the same feature or already checked

                candidate_geom = geometry_dict[cid] #If feature and a candidate overlap, add them to the overlapping features list
                overlap_geom = geom.intersection(candidate_geom)
                overlap_area = overlap_geom.area()
                    
                if overlap_area >= 10: #Overlaps with less than 10 m² are ignored and not removed
                    if geom.overlaps(candidate_geom) or geom.contains(candidate_geom) or geom.within(candidate_geom):
                        overlapping_features.append((fid, cid))

                #Mark this pair as checked to avoid redundant checks
                checked_ids.add((fid, cid))
                checked_ids.add((cid, fid))

        #self.dlg.progressbar.setValue(100)
        if len(overlapping_features) >= 1:
            self.log_to_qtalsim_tab(f"{len(overlapping_features)} overlaps were detected. The following features overlap: {overlapping_features}", level=Qgis.Info)
            
            layer.setName(layerName)
            QgsProject.instance().addMapLayer(layer)
        else:
            self.log_to_qtalsim_tab(f"No overlapping polygons were detected.",level=Qgis.Info)

        return layer, overlapping_features
    
    def make_geometries_valid(self, layer):
        '''
            Checks for invalid geometries in an input layer and updates those geometries.
                Also deletes features with empty geometries.
        '''
        invalid_features = False
        layer.startEditing() 
        for feature in layer.getFeatures():
            geom = feature.geometry()
            if not geom.isGeosValid():
                fixed_geom = geom.makeValid()
                # Check if the fixed geometry is valid and of the correct type
                if fixed_geom.isGeosValid() and fixed_geom.wkbType() == QgsWkbTypes.MultiPolygon:
                    feature.setGeometry(fixed_geom)
                    layer.updateFeature(feature)
                else:
                    # Attempt to fix the geometry to be a MultiPolygon
                    if fixed_geom.wkbType() == QgsWkbTypes.Polygon:
                        fixed_geom = QgsGeometry.fromMultiPolygonXY([fixed_geom.asPolygon()])
                    elif fixed_geom.wkbType() == QgsWkbTypes.LineString or fixed_geom.wkbType() == QgsWkbTypes.MultiLineString:
                        fixed_geom = QgsGeometry.fromPolygonXY([fixed_geom.asPolyline()])
                    # Set the fixed geometry if it's valid
                    if fixed_geom.isGeosValid() and fixed_geom.wkbType() == QgsWkbTypes.MultiPolygon:
                        feature.setGeometry(fixed_geom)
                        layer.updateFeature(feature)
                    else:
                        invalid_features = True
            if geom.isEmpty() or geom.area() == 0:
                layer.deleteFeature(feature.id())
        layer.commitChanges()
        return layer, invalid_features
    
    def editOverlappingFeatures(self, layer):
        '''
            Deletes overlapping features of input layer.
        '''
        layer, _ = self.make_geometries_valid(layer)
        layer = processing.run("native:multiparttosingleparts", {'INPUT': layer,'OUTPUT': 'TEMPORARY_OUTPUT'}, feedback=None)['OUTPUT']
        layer.startEditing()
        index = QgsSpatialIndex()

        changes_made = False

        # Prepare spatial index for all features
        feature_dict = {feature.id(): feature for feature in layer.getFeatures()}

        for feature_id, feature in feature_dict.items():
            index.addFeature(feature)
        
        snap_tolerance = 0.01
        snapper = QgsGeometrySnapper(layer)
        def apply_snap_geometry(new_geom, tolerance):
            snapped_geom = snapper.snapGeometry(new_geom, tolerance)
            if not snapped_geom.isGeosValid():
                snapped_geom = snapped_geom.makeValid()
            return snapped_geom #if snapped_geom.isGeosValid() else None

        geometry_updates = {}

        total_features = len(feature_dict)
        last_logged_progress = 0
        analysed_features = 0

        for feature_id, feature in feature_dict.items():
            feature_geom = feature.geometry()
            candidate_ids = index.intersects(feature_geom.boundingBox())

            analysed_features += 1
            progress = (analysed_features / total_features) * 100
            if progress - last_logged_progress >= 10:
                self.log_to_qtalsim_tab(f"Progress: {progress:.2f}% done", Qgis.Info)
                last_logged_progress = progress
                #self.dlg.progressbar.setValue(int(last_logged_progress))

            for fid in candidate_ids:
                if fid == feature_id or fid not in feature_dict:
                    continue

                other_feature = feature_dict[fid]
                other_geom = geometry_updates.get(fid, other_feature.geometry())

                if feature_geom.overlaps(other_geom) or feature_geom.contains(other_geom) or feature_geom.within(other_geom):
                    overlap_geom = feature_geom.intersection(other_geom)
                    overlap_area = overlap_geom.area()
                    
                    if overlap_area >= 10: #Overlaps with less than 10 m² are ignored and not removed
                        if feature_geom.contains(other_geom):
                            new_geom = feature_geom.difference(other_geom)
                            snapped_geometry = apply_snap_geometry(new_geom, snap_tolerance)
                            
                            if snapped_geometry.contains(other_geom):
                                buffered_geometry = other_geom.buffer(0.0001, 5)
                                new_geom = snapped_geometry.difference(buffered_geometry)
                                snapped_geometry = apply_snap_geometry(new_geom, snap_tolerance)

                            #if snapped_geometry:
                            geometry_updates[feature_id] = snapped_geometry
                            feature_dict[feature_id].setGeometry(snapped_geometry)
                            feature_geom = snapped_geometry
                            changes_made = True

                        elif feature_geom.within(other_geom):
                            new_geom = other_geom.difference(feature_geom)
                            snapped_geometry = apply_snap_geometry(new_geom, snap_tolerance)
                            
                            #if snapped_geometry:
                            geometry_updates[other_feature.id()] = snapped_geometry
                            feature_dict[other_feature.id()].setGeometry(snapped_geometry)
                            changes_made = True

                        elif feature_geom.area() >= other_geom.area():
                            new_geom = feature_geom.difference(other_geom)
                            snapped_geometry = apply_snap_geometry(new_geom, snap_tolerance)

                            if snapped_geometry.overlaps(other_geom): #snapped_geometry and 
                                buffered_geometry = other_geom.buffer(0.0001, 5)
                                new_geom = snapped_geometry.difference(buffered_geometry)
                                snapped_geometry = apply_snap_geometry(new_geom, snap_tolerance)

                            #if snapped_geometry:
                            geometry_updates[feature_id] = snapped_geometry
                            feature_dict[feature_id].setGeometry(snapped_geometry)
                            feature_geom = snapped_geometry
                            changes_made = True

                        else:
                            new_geom = other_geom.difference(feature_geom)
                            snapped_geometry = apply_snap_geometry(new_geom, snap_tolerance)
                            
                            if snapped_geometry.overlaps(feature_geom):
                                buffered_geometry = feature_geom.buffer(0.0001, 5)
                                new_geom = snapped_geometry.difference(buffered_geometry)
                                snapped_geometry = apply_snap_geometry(new_geom, snap_tolerance)

                                if snapped_geometry.overlaps(feature_geom):
                                    snapped_geometry = other_geom.difference(feature_geom)
                            #if snapped_geometry:
                            geometry_updates[other_feature.id()] = snapped_geometry
                            feature_dict[other_feature.id()].setGeometry(snapped_geometry)
                            changes_made = True
                            #else:
                            #    self.log_to_qtalsim_tab(f"Editing the overlap of feature IDs {feature_id} and {fid} was unsuccessful, as this step would result in invalid geometries. Please try again or edit the overlap manually.", Qgis.Warning)

        # Step 5: Apply geometry updates
        layer.startEditing()
        for fid, geom in geometry_updates.items():
            if geom:
                index.deleteFeature(feature_dict[fid])
                layer.changeGeometry(fid, geom)
                index.addFeature(layer.getFeature(fid))
            elif geom.isEmpty():
                layer.deleteFeature(fid)
        layer.commitChanges()

        invalid_features = False
        layer, invalid_features = self.make_geometries_valid(layer)

        if invalid_features:
            original_features = {feature.id(): feature for feature in layer.getFeatures()}
            layer = processing.run("native:fixgeometries", {'INPUT': layer, 'METHOD': 1, 'OUTPUT': 'TEMPORARY_OUTPUT'}, feedback=None).get('OUTPUT')
            fixed_features = {feature.id(): feature for feature in layer.getFeatures()}
            deleted_features = set(original_features.keys()) - set(fixed_features.keys())
            if len(deleted_features) > 0:
                self.log_to_qtalsim_tab(f"The following features were deleted due to invalid geometries: {deleted_features}", Qgis.Warning)
        if last_logged_progress <= 99:
            self.log_to_qtalsim_tab(f"Progress: 100.00% done", Qgis.Info)
            #self.dlg.progressbar.setValue(0)
        return layer, changes_made

    def clipLayer(self, layer, clipping_layer):
        '''
            Clips input layer with clipping_layer.
                First checks validity of input layer and then clips this layer.
        '''
        #Check Geometry
        outputLayer = processing.run("native:multiparttosingleparts", {'INPUT': layer,'OUTPUT': 'TEMPORARY_OUTPUT'}, feedback=None)['OUTPUT']
        outputLayer, _ = self.make_geometries_valid(outputLayer)
        try:
            resultClipping = processing.run("native:clip", {
                    'INPUT': outputLayer,
                    'OVERLAY': clipping_layer,
                    'OUTPUT': 'TEMPORARY_OUTPUT'
            }, feedback=None)
        except:
            outputLayer = processing.run("native:fixgeometries", {'INPUT': outputLayer, 'METHOD': 1, 'OUTPUT': 'TEMPORARY_OUTPUT'}, feedback=None).get('OUTPUT')
            clipping_layer = processing.run("native:fixgeometries", {'INPUT': clipping_layer, 'METHOD': 1, 'OUTPUT': 'TEMPORARY_OUTPUT'}, feedback=None).get('OUTPUT')
            has_multipart = False
            for feature in outputLayer.getFeatures():
                geom = feature.geometry()
                if geom.isMultipart():
                    has_multipart = True
                    break
                    
            if has_multipart:
                outputLayer = processing.run("native:multiparttosingleparts", {'INPUT': outputLayer,'OUTPUT': 'TEMPORARY_OUTPUT'}, feedback=None)['OUTPUT']
                outputLayer, _ = self.make_geometries_valid(outputLayer)

            resultClipping = processing.run("native:clip", {
                    'INPUT': outputLayer,
                    'OVERLAY': clipping_layer,
                    'OUTPUT': 'TEMPORARY_OUTPUT'
            }, feedback=None)
        
        resultClippingLayer = resultClipping['OUTPUT']
        
        #After clipping, problems with multipart polygons can arise
        result_singlepart = processing.run("native:multiparttosingleparts", {'INPUT': resultClippingLayer,'OUTPUT': 'TEMPORARY_OUTPUT'}, feedback=None)
        layer = result_singlepart['OUTPUT']
        return layer
    
    def checkGaps(self, layer, extent):
        '''
        Checks for gaps
            very small gaps at the boundary of the layer are not detected.
        :param layer: layer where polygons are eliminated
        :param extent: layer (e.g. dissolved catchment area layer) that defines the boundary of layer.
        '''
        field_index = layer.fields().indexFromName('fid')
        if field_index != -1:
            layer.startEditing()
            layer.renameAttribute(field_index, 'old_fid')
            layer.commitChanges()

        #Low, negative Buffer, to exclude gaps at the edge of the polygon when looking for gaps of the soil & landuse layers
        result_buffer = processing.run("native:buffer", {'INPUT': extent,'DISTANCE': -0.00001,  'OUTPUT': 'TEMPORARY_OUTPUT'})
        extent = result_buffer['OUTPUT']

        #Find Gaps between layer and extent
        layer_features = [feature.geometry() for feature in layer.getFeatures()]
        extent_features = [feature.geometry() for feature in extent.getFeatures()] #extent = sub-basin's layer
        union_geom = QgsGeometry.unaryUnion(layer_features)
        union_geom_extent = QgsGeometry.unaryUnion(extent_features)
        holes = union_geom_extent.difference(union_geom)
        
        #Necessary step because dissolve cannot handle invalid geometries, GeometryCollections & Linestrings:
        layer, _ = self.make_geometries_valid(layer)

        layer = processing.run("native:multiparttosingleparts", {
            'INPUT': layer,
            'OUTPUT': 'memory:'
        },feedback=None)['OUTPUT']

        #Find gaps within the layer 
        dissolved = processing.run("native:dissolve", {
            'INPUT': layer,
            'FIELD':[],
            'SEPARATE_DISJOINT':True,
            'OUTPUT': 'memory:'
        },feedback=None)['OUTPUT']

        gaps = []
        for feature in dissolved.getFeatures():
            geom = feature.geometry()
            if geom.isMultipart():
                polygons = geom.asMultiPolygon()
            else:
                polygons = [geom.asPolygon()]
            
            for polygon in polygons:
                # Interior rings are at index 1 and onwards
                for interior_ring in polygon[1:]:
                    gaps.append(QgsGeometry.fromPolygonXY([interior_ring]))

        #Create Gaps Layer
        crs = layer.crs().authid()
        gapsLayer = QgsVectorLayer(f"Polygon?crs={crs}","Gaps","memory")

        dp = gapsLayer.dataProvider()
        dp.addAttributes([])
        gapsLayer.updateFields()

        for gap_geom in gaps:
            if holes.isMultipart():
                # If holes is a multipart geometry, combine with each part
                for part in holes.asGeometryCollection():
                    combined_geom = part.combine(gap_geom)
                    holes = QgsGeometry.unaryUnion([holes, combined_geom])
            else:
                # If holes is a single geometry
                holes = holes.combine(gap_geom)
        if holes.isMultipart():
            geom_parts = holes.asGeometryCollection()
        else:
            geom_parts = [holes]

        for hole_geom in geom_parts:
            feat = QgsFeature()
            feat.setGeometry(hole_geom)
            dp.addFeature(feat)
        gapsLayer.updateExtents()
        dp.addAttributes([QgsField("gapFeature", QVariant.Int)])
        gapsLayer.updateFields()

        gapsLayer.startEditing()
        #Set this new attribute for all features in gapsLayer
        for feature in gapsLayer.getFeatures():
            feature['gapFeature'] = 1  # Set identifier
            gapsLayer.updateFeature(feature)
        gapsLayer.commitChanges()

        self.log_to_qtalsim_tab("Existing gaps are represented in temporary layer 'Gaps'.",Qgis.Info)
        QgsProject.instance().addMapLayer(gapsLayer)

        return gapsLayer
    
    def fillGaps(self, layer, extent, mode):
        '''
        Fills Gaps
            First finds gaps between extent layer and layer and then gaps within the layer; fills those gaps with the eliminate-tool.
        :param layer: layer where polygons are eliminated
        :param extent: layer (e.g. dissolved catchment area layer) that defines the boundary of layer
        '''
        #If there is a field fid - rename it to old_fid (some tasks have problems with fields fid)
        field_index = layer.fields().indexFromName('fid')
        if field_index != -1:
            layer.startEditing()
            layer.renameAttribute(field_index, 'old_fid')
            layer.commitChanges()

        #Necessary step because dissolve cannot handle GeometryCollections & Linestrings:
        layer, _ = self.make_geometries_valid(layer)

        layer = processing.run("native:multiparttosingleparts", {
            'INPUT': layer,
            'OUTPUT': 'memory:'
        },feedback=None)['OUTPUT']

        #Find Gaps between layer and extent
        geom_request = QgsFeatureRequest().setNoAttributes()
        union_geom = QgsGeometry.unaryUnion([feature.geometry() for feature in layer.getFeatures(geom_request)])
        union_geom_extent = QgsGeometry.unaryUnion([feature.geometry() for feature in extent.getFeatures()])
        holes = union_geom_extent.difference(union_geom)
        
        def clean_geometry(geom): #Function to clean the geometry as complex geometries sometimes led to errors
            buffer_distance = 0.0001  
            #Apply a small buffer and immediately reverse it
            cleaned_geom = geom.buffer(buffer_distance, 5).buffer(-buffer_distance, 5)
            return cleaned_geom
        
        #Find gaps within the layer 
        dissolved = processing.run("native:dissolve", {
            'INPUT': layer,
            'FIELD':[],
            'SEPARATE_DISJOINT':True,
            'OUTPUT': 'memory:'
        }, feedback=None)['OUTPUT']
        
        self.log_to_qtalsim_tab("Detecting Gaps...", Qgis.Info)
        gaps = []
        for feature in dissolved.getFeatures():
            geom = feature.geometry()
            if geom.isMultipart():
                polygons = geom.asMultiPolygon()
            else:
                polygons = [geom.asPolygon()]
            
            for polygon in polygons:
                # Interior rings are at index 1 and onwards
                for interior_ring in polygon[1:]:
                    gap_geom = QgsGeometry.fromPolygonXY([interior_ring])
                    cleaned_gap_geom = clean_geometry(gap_geom)
                    gaps.append(cleaned_gap_geom)
        
        #Create Gaps Layer
        crs = layer.crs().authid()
        gapsLayer = QgsVectorLayer(f"Polygon?crs={crs}","Gaps","memory")
        valid_gaps = []
        for geom in gaps:
            if not geom.isGeosValid():
                fixed_geom = geom.makeValid() 
                if fixed_geom.isGeosValid():
                    valid_gaps.append(fixed_geom)
            else:
                valid_gaps.append(geom)
        gaps = valid_gaps 

        fixed_holes = holes.makeValid() if not holes.isGeosValid() else holes
        all_geometries = [fixed_holes] + valid_gaps
        merged_geometry = QgsGeometry.unaryUnion(all_geometries)

        if merged_geometry.isMultipart():
            geom_parts = merged_geometry.asGeometryCollection()
        else:
            geom_parts = [merged_geometry]
        
        dp = gapsLayer.dataProvider()
        dp.addAttributes([QgsField("gapFeature", QVariant.Int)])
        gapsLayer.updateFields()

        gapsLayer.startEditing()
        for hole_geom in geom_parts:
            feat = QgsFeature()
            feat.setGeometry(hole_geom)
            feat.setAttributes([1])
            dp.addFeature(feat)
        gapsLayer.updateExtents()
        gapsLayer.commitChanges()

        self.log_to_qtalsim_tab("Eliminating Gaps...", Qgis.Info)

        gapsLayer, _ = self.make_geometries_valid(gapsLayer)

        gapsLayer = processing.run("native:multiparttosingleparts", {
                'INPUT': gapsLayer,
                'OUTPUT': 'TEMPORARY_OUTPUT'
        })['OUTPUT']

        layer, _ = self.make_geometries_valid(layer)

        layer = processing.run("native:multiparttosingleparts", {
                'INPUT': layer,
                'OUTPUT': 'TEMPORARY_OUTPUT'
        })['OUTPUT']

        #Merge the layer with the gaps to the initial layer
        result_merge = processing.run("native:mergevectorlayers", {'LAYERS': [layer, gapsLayer],  'OUTPUT': 'TEMPORARY_OUTPUT'}, feedback=None)
        merged_layer = result_merge['OUTPUT']

        #Select the gaps in the merged_layer 
        request = QgsFeatureRequest().setFlags(QgsFeatureRequest.NoGeometry).setSubsetOfAttributes(
                ['gapFeature'], merged_layer.fields()
        )
        feature_ids_to_select = [feature.id() for feature in merged_layer.getFeatures(request) if feature['gapFeature'] == 1]

        #Select all gap features 
        if feature_ids_to_select:
            merged_layer.selectByIds(feature_ids_to_select)

        #Eliminate with the user's input mode
        result_eliminate = processing.run("qgis:eliminateselectedpolygons", {
            'INPUT': merged_layer,
            'MODE': mode,  
            'OUTPUT': 'TEMPORARY_OUTPUT'
        },feedback=self.feedback)
        layer_without_gaps = result_eliminate['OUTPUT']

        layer_without_gaps, _ = self.make_geometries_valid(layer_without_gaps)

        layer_without_gaps = processing.run("native:multiparttosingleparts", {
                'INPUT': layer_without_gaps,
                'OUTPUT': 'TEMPORARY_OUTPUT'
        })['OUTPUT']

        request = QgsFeatureRequest().setFlags(QgsFeatureRequest.NoGeometry).setSubsetOfAttributes(['gapFeature'], layer_without_gaps.fields())
        features_to_delete = [feature.id() for feature in layer_without_gaps.getFeatures(request) if feature['gapFeature'] == 1]
        if features_to_delete:
            layer_without_gaps.startEditing()
            layer_without_gaps.deleteFeatures(features_to_delete)
            layer_without_gaps.commitChanges()
        
        return layer_without_gaps
    
    def deleteOverlappingFeatures(self, layer, table, list_overlapping_features):
        '''
            Deletes overlapping parts of features that are selected by the user in the table. 
        '''
        geometry_updates = {} #to store the features with the updated geometry
        selected_indexes = table.selectionModel().selectedIndexes() #get the indices of the features to change 
        selected_cells = [(index.row(), index.column()) for index in selected_indexes]

        #Logging:
        analysed_features = 0
        number_total_features = max(row for row, _ in selected_cells) + 1
        last_logged_progress = 0

        #Snapping of the new geometries is necessary
        snapper = QgsGeometrySnapper(layer)
        snap_tolerance = 0.01
        def apply_snap_geometry(new_geom, tolerance):
            snapped_geom = snapper.snapGeometry(new_geom, tolerance)
            if not snapped_geom.isGeosValid():
                snapped_geom = snapped_geom.makeValid()
            return snapped_geom #if snapped_geom.isGeosValid() else None
            
        for row, column in selected_cells:
            feature_id_selected = list_overlapping_features[row][column]
           
            #Choose geometry from geometry_updates if available, otherwise use the feature's geometry
            feature_selected = layer.getFeature(feature_id_selected)
            feature_geom = geometry_updates.get(feature_id_selected, feature_selected.geometry())

            #Log Progress
            analysed_features += 1
            progress = (analysed_features/number_total_features)*100
            if progress - last_logged_progress >= 10:
                self.log_to_qtalsim_tab(f"Progress: {progress:.2f}% done", Qgis.Info)
                last_logged_progress = progress

            if column == 0:
                column_to_keep = 1
            elif column == 1:
                column_to_keep = 0

            feature_id_keep = list_overlapping_features[row][column_to_keep]
            feature_keep = layer.getFeature(feature_id_keep)
            feature_keep_geometry = geometry_updates.get(feature_id_keep, feature_keep.geometry())
            
            overlap_geom = feature_geom.intersection(feature_keep_geometry) 
            overlap_area = overlap_geom.area()
                    
            if overlap_area >= 10: #Overlaps with less than 10 m² are ignored and not removed
                # Calculate the difference between feature and other_feature and create new geom for the feature (that contains other_feature)
                new_geom = feature_geom.difference(feature_keep_geometry)
                snapped_geometry = apply_snap_geometry(new_geom, snap_tolerance)
                if snapped_geometry.contains(feature_keep_geometry) or snapped_geometry.within(feature_keep_geometry) or snapped_geometry.overlaps(feature_keep_geometry):
                    buffered_geometry = feature_keep_geometry.buffer(0.0001, 5)  # very small buffer
                    new_geom = snapped_geometry.difference(buffered_geometry)
                    snapped_geometry = apply_snap_geometry(new_geom, snap_tolerance)       
                
                geometry_updates[feature_id_selected] = snapped_geometry

        #Apply geometry updates
        layer.startEditing()
        for fid, geom in geometry_updates.items():
            if geom:
                layer.changeGeometry(fid, geom)
            elif geom.isEmpty():
                layer.deleteFeature(fid)
        layer.commitChanges()

        #Fix the invalid geometries (during the steps above, new (potentially invalid) geometries are created)
        invalid_features = False
        layer, _ = self.make_geometries_valid(layer)
        if invalid_features:
            original_features = {feature.id(): feature for feature in layer.getFeatures()}
            layer = processing.run("native:fixgeometries", {'INPUT': layer, 'METHOD': 1, 'OUTPUT': 'TEMPORARY_OUTPUT'}, feedback=None).get('OUTPUT')
            fixed_features = {feature.id(): feature for feature in layer.getFeatures()}
            deleted_features = set(original_features.keys()) - set(fixed_features.keys())
            if len(deleted_features) > 0:
                self.log_to_qtalsim_tab(f"The following features were deleted due to invalid geometries: {deleted_features}", Qgis.Warning)
        if last_logged_progress <= 99:
            self.log_to_qtalsim_tab(f"Progress: 100.00% done", Qgis.Info)      
        return layer
    
    def on_selection_change(self, table_widget):
        '''
            Logic for the tables where the user can select overlapping parts to be deleted
                Only one of the two features should be selectable
        '''
        last_selected_item = {}  #Dictionary to keep track of the last selected item in each row

        for item in table_widget.selectedItems():
            row = item.row()
            column = item.column()
            if column == 2:  #Radio-Buttons are in the third column - making the cell below third column unselectable
                item.setSelected(False)
                continue
            # If there's already a selected item in this row and it's not the current item, deselect it
            if row in last_selected_item and last_selected_item[row] != item:
                last_selected_item[row].setSelected(False)
            last_selected_item[row] = item

    def on_radio_button_toggled(self, checked, layer, feature_id_list, radio_button_to_row, radio_button):
        '''
            Handles selection of features in table of overlapping features.
        '''   
        if checked:  # The radio button column
            row = radio_button_to_row[radio_button]
            feature_ids = feature_id_list[row]
            layer.removeSelection()
            layer.selectByIds(feature_ids, QgsVectorLayer.AddToSelection)
            self.iface.mapCanvas().zoomToSelected(layer)
            self.log_to_qtalsim_tab(f"Selected Feature IDs: {feature_ids}", Qgis.Info)
        else:
            for feature_id in feature_id_list:
                if feature_id in layer.selectedFeatureIds():
                    layer.selectByIds([feature_id], QgsVectorLayer.RemoveFromSelection)

    '''
        Sub-basins Layer
    '''
    def on_ezg_changed(self):
        '''
            If the user changes the sub-basin's layer, the field comboboxes to select UI and slope field are updated. 
        '''
        try:
            current_text = self.dlg.comboboxEZGLayer.currentText() 

            current_text_comboboxui = self.dlg.comboboxUICatchment.currentText() #if the current field name exists in new layer - leave this field
            index_ui = -1
            
            layers = QgsProject.instance().mapLayersByName(current_text)
            
            if not layers: #if the layer does not exist
                return
            
            if current_text is not None and current_text != self.noLayerSelected:
                index = self.dlg.comboboxEZGLayer.findText(current_text)
                if index == -1:
                    return
                self.dlg.comboboxEZGLayer.setCurrentIndex(index)
                self.ezgLayerCombobox = QgsProject.instance().mapLayersByName(current_text)[0]
                
                self.dlg.comboboxUICatchment.clear() #clear combobox EZG from previous runs
                self.dlg.comboboxUICatchment.addItems([field.name() for field in self.ezgLayerCombobox.fields()])
                if current_text_comboboxui is not None:
                    index_ui = self.dlg.comboboxUICatchment.findText(current_text_comboboxui)
                    if index_ui != -1:
                        self.dlg.comboboxUICatchment.setCurrentIndex(index_ui) 

        except:
            return

    def on_input_db_changed(self): 
        if not os.path.exists(self.file_path_db):
            return  # Ignore if path doesn't exist

        try:
            conn = sqlite3.connect(self.file_path_db)
            cursor = conn.cursor()

            # Assuming the table is named 'scenarios' with columns 'id' and 'name'
            cursor.execute("SELECT id, name FROM Scenario")
            rows = cursor.fetchall()

            # Clear and repopulate the combobox
            self.dlg.comboboxScenarios.clear()
            for scenario_id, name in rows:
                display_text = f"{name} (ID: {scenario_id})"
                self.dlg.comboboxScenarios.addItem(display_text, scenario_id)
            conn.close()
        except Exception as e:
            self.log_to_qtalsim_tab(f"Error loading scenarios: {e}", Qgis.Critical)

    def selectEZG(self):
        '''
            Loads the sub-basins layer.
            Deletes overlapping features and duplicates.
            A temporary sub-basins layer, which is used for clipping the soil and landuse layer, is created. 
            This temporary sub-basins layer is a dissolved, non-self-overlapping layer without gaps.
        '''
        try:
            self.start_operation()
            self.log_to_qtalsim_tab(f"Selecting sub-basin layer...", Qgis.Info)
            #self.dlg.progressbar.setValue(0)
            self.ezgLayer = self.ezgLayerCombobox
            layerName = self.ezgLayer.name()

            self.ezgUniqueIdentifier = self.dlg.comboboxUICatchment.currentText()

            #Check if the UI starts with an A
            #Get unique values for the specified field
            unique_values = set()
            for feature in self.ezgLayer.getFeatures():
                value = feature[self.ezgUniqueIdentifier]
                if value is not None:
                    unique_values.add(str(value))
            
            invalid_values = [value for value in unique_values if not value.startswith("A")]

            if invalid_values:
                self.end_operation()
                #Show warning message and ask user if they want to continue
                message = (
                    f"The following unique identifiers do not start with 'A':\n"
                    f"{', '.join(invalid_values)}\n\n"
                    "Unique identifiers of sub-basins in Talsim must start with an A. Do you want to continue?"
                )
                reply = QMessageBox.warning(
                    None,
                    "Warning",
                    message,
                    QMessageBox.Yes | QMessageBox.No
                )
                if reply == QMessageBox.No:
                    return  #User chose not to continue
                else:
                    self.start_operation()
            #self.dlg.progressbar.setValue(10)
            self.log_to_qtalsim_tab(f"Progress: 10.00% done", Qgis.Info)
            #Delete overlapping features in the catchment area layer
            outputLayer = processing.run("native:deleteduplicategeometries", {'INPUT': self.ezgLayer ,'OUTPUT':'TEMPORARY_OUTPUT'}, feedback=self.feedback)['OUTPUT']
            self.last_logged_progress = 0
            self.log_to_qtalsim_tab(f"Editing overlapping features...", Qgis.Info)
            self.ezgLayer, _ = self.editOverlappingFeatures(outputLayer)
            self.log_to_qtalsim_tab(f"Progress: 50.00% done", Qgis.Info)
            #Dissolve of catchment areas for better clipping performance
            result = processing.run("native:dissolve", {'INPUT': self.ezgLayer, 'FIELD':[],'SEPARATE_DISJOINT':False,'OUTPUT':'TEMPORARY_OUTPUT'}, feedback=None)
            outputLayer = result['OUTPUT']
            self.log_to_qtalsim_tab(f"Progress: 60.00% done", Qgis.Info)
            #self.dlg.progressbar.setValue(60)
            result_deleteholes = processing.run("native:deleteholes", {'INPUT':outputLayer,'MIN_AREA':0,'OUTPUT':'TEMPORARY_OUTPUT'}, feedback=self.feedback)
            self.clippingEZG = result_deleteholes['OUTPUT'] #delete holes within the dissolved catchment area layer for clipping
            #QgsProject.instance().addMapLayer(self.clippingEZG)
            #self.dlg.progressbar.setValue(70)
            self.log_to_qtalsim_tab(f"Progress: 70.00% done", Qgis.Info)
            result = processing.run("native:dissolve", {'INPUT': self.ezgLayer, 'FIELD':[self.ezgUniqueIdentifier],'SEPARATE_DISJOINT':False,'OUTPUT':'TEMPORARY_OUTPUT'}, feedback=None)
            self.ezgLayer = result['OUTPUT']
            #self.dlg.progressbar.setValue(90)
            self.log_to_qtalsim_tab(f"Progress: 90.00% done", Qgis.Info)
            current_text = self.dlg.onEZG.text()
            if "✓" not in current_text:  #Avoid duplicate checkmarks
                self.dlg.onEZG.setText(f"{current_text} ✓")
                current_text_groupbox = self.dlg.subBasinGroupBox.title()
                self.dlg.subBasinGroupBox.setTitle(f"{current_text_groupbox} ✓")
            self.log_to_qtalsim_tab(f"Progress: 100.00% done", Qgis.Info)

            #Enable the collapsible group boxes
            self.dlg.soilGroupBox.setEnabled(True)
            self.dlg.landuseGroupBox.setEnabled(True)

            #Rename the UI field of input layer so no problems arise later on
            rename_params = {
                'INPUT': self.ezgLayer,
                'FIELD': self.ezgUniqueIdentifier,  #Current field name
                'NEW_NAME': 'ID_SubB',
                'OUTPUT': 'memory:'
            }
            self.ezgLayer = processing.run('native:renametablefield', rename_params)['OUTPUT']
            self.ezgUniqueIdentifier = 'ID_SubB' #Update variable to reference the new field name

            self.ezgLayer.setName("Sub-basins")
            QgsProject.instance().addMapLayer(self.ezgLayer)

            #self.dlg.progressbar.setValue(0)
            self.log_to_qtalsim_tab(f"Successfully selected and clipped sub-basin layer: {self.ezgLayer.name()}.", Qgis.Info) 

        except Exception as e:
            self.log_to_qtalsim_tab(f"{e}", Qgis.Critical) 
            #self.dlg.progressbar.setValue(0)
        finally:
            self.end_operation()


        
    '''
       Soil Layer
    '''

    def selectSoil(self):
        '''
            Selects and clips the soil layer and removes duplicate geometries.
                Also fills the soil mapping table. 
        '''
        try:
            self.start_operation()
            #self.dlg.progressbar.setValue(0)
            self.log_to_qtalsim_tab(f"Starting the clipping process of the Soil Layer.", Qgis.Info) 
            if self.clippingEZG is None:
                selected_layer_soil = None
                raise Exception("User has not selected a sub-basins layer.")
            
            #Select Layer
            selected_layer_soil = self.dlg.comboboxSoilLayer.currentText()
            self.soilLayer = QgsProject.instance().mapLayersByName(selected_layer_soil)[0]
            
            #Create field with the feature-id
            self.soilFieldInputID = 'fid_qta'
            existing_field_names = [field.name() for field in self.soilLayer.fields()]
            if self.soilFieldInputID in existing_field_names:
                self.log_to_qtalsim_tab(f"Please rename field {self.soilFieldInputID} of layer {selected_layer_soil} or delete the field.", Qgis.Critical)
                return
            #self.dlg.progressbar.setValue(10)
            self.log_to_qtalsim_tab(f"Progress: 10.00% done", Qgis.Info)

            self.soilLayer.startEditing()
            #Add a new integer field called 'ID'
            field = QgsField(self.soilFieldInputID, QVariant.Int)
            self.soilLayer.dataProvider().addAttributes([field])
            self.soilLayer.updateFields()

            #Assign ID values to each feature
            for i, feature in enumerate(self.soilLayer.getFeatures()):
                self.soilLayer.changeAttributeValue(feature.id(), self.soilLayer.fields().indexFromName(self.soilFieldInputID), i + 1)
            #self.dlg.progressbar.setValue(25)
            self.log_to_qtalsim_tab(f"Progress: 20.00% done", Qgis.Info)
            self.soilLayer.commitChanges()

            #Clip Layer
            outputLayer = self.clipLayer(self.soilLayer, self.clippingEZG)
            self.log_to_qtalsim_tab(f"Progress: 80.00% done", Qgis.Info)
            outputLayer = processing.run("native:deleteduplicategeometries", {'INPUT': outputLayer ,'OUTPUT':'TEMPORARY_OUTPUT'}, feedback=None)['OUTPUT']
            self.soilLayer = outputLayer

            self.fillSoilTable()            
            #self.dlg.progressbar.setValue(90)
            self.log_to_qtalsim_tab(f"Progress: 90.00% done", Qgis.Info)
            #Add checkmark when process is finished
            current_text = self.dlg.onSoil.text()
            if "✓" not in current_text:  #Avoid duplicate checkmarks
                self.dlg.onSoil.setText(f"{current_text} ✓")
            self.soilLayer.setName("SoilLayer")
            QgsProject.instance().addMapLayer(self.soilLayer)
            #self.dlg.progressbar.setValue(0)
            self.log_to_qtalsim_tab(f"Progress: 100.00% done", Qgis.Info)
            self.log_to_qtalsim_tab(f"Successfully selected and clipped Soil Layer: {self.soilLayer.name()}.", Qgis.Info) 

        except Exception as e:
            self.log_to_qtalsim_tab(f"{e}", Qgis.Critical) 
            #self.dlg.progressbar.setValue(0)

        finally:
            if selected_layer_soil:
                #Remove the created soil-ID-field from the input layer
                layer = QgsProject.instance().mapLayersByName(selected_layer_soil)[0]
                layer.startEditing()
                field_index = layer.fields().indexFromName(self.soilFieldInputID)

                if field_index != -1:  
                    layer.dataProvider().deleteAttributes([field_index])
                    layer.updateFields()
                    layer.commitChanges()
            self.end_operation()

    def fillSoilTable(self):
        '''
            Fills the soil mapping table with the field names of the soil layer and the Talsim parameter names.
                Is executed in function selectSoil.
        '''
        try:
            self.start_operation()
            fieldsSoil = [field.name() for field in self.soilLayer.fields()]
            current_path = os.path.dirname(os.path.abspath(__file__))
            soilTalsimPath = os.path.join(current_path, "talsim_parameter", "soilParameter.csv") 
            self.dfsoilParametersTalsim = pd.read_csv(soilTalsimPath, delimiter = ';', skiprows=[1]) #Skip ID_Soil as it is not needed as input

            #Create Table
            self.dlg.tableSoilMapping.setRowCount(self.dfsoilParametersTalsim.shape[0])
            self.dlg.tableSoilMapping.setColumnCount(7) 
            self.dlg.tableSoilMapping.setHorizontalHeaderLabels(['Talsim Soil Parameters', 'Soil Layer 1', 'Soil Layer 2', 'Soil Layer 3', 'Soil Layer 4', 'Soil Layer 5', 'Soil Layer 6'])
            
            # Set the size of the table columns
            self.dlg.tableSoilMapping.setColumnWidth(0, 200)
            self.dlg.tableSoilMapping.setColumnWidth(1, 200)
            self.dlg.tableSoilMapping.setColumnWidth(2, 200)
            self.dlg.tableSoilMapping.setColumnWidth(3, 200)
            self.dlg.tableSoilMapping.setColumnWidth(4, 200)
            self.dlg.tableSoilMapping.setColumnWidth(5, 200)
            self.dlg.tableSoilMapping.setColumnWidth(6, 200)
            #self.dlg.progressbar.setValue(30)

            #Get Data from csv-file
            soilTextures = self.dfsoilParametersTalsim.loc[:,'SoilTexture']
            soilTexturesUnit = self.dfsoilParametersTalsim.loc[:,'TextureUnit']
            self.number_soilLayers = 6

            num_rows = self.dfsoilParametersTalsim.shape[0]
            analysed_rows = 0
            last_logged_progress = 30
            
            #Fill data
            for row, data in enumerate(soilTextures):
                analysed_rows += 1
                progress = (analysed_rows / num_rows) * 100 + 30 #30 is starting value
                if progress - last_logged_progress >= 10:
                    last_logged_progress = progress
                item = QTableWidgetItem(str(data)) #add soil
                unit = soilTexturesUnit[row] #get unit
                if unit != '-':
                    item.setText(item.text() + ' [' + str(unit) + ']')  #Add unit of parameter
                self.dlg.tableSoilMapping.setItem(row, 0, item)
                restriction = self.dfsoilParametersTalsim.loc[row, 'TextureRestrictions'] #Add restriction as tooltip
                if pd.notna(restriction):
                    item.setToolTip(f"Restriction: {restriction}")

                for i in range(0, self.number_soilLayers): #Add a combobox to every column in the soil mapping table
                    combo_box = QComboBox()
                    #Add parameters as items to the combo box
                    if data != self.nameSoil or i != 0: #Only one soil layer is needed
                        combo_box.addItem('Parameter not available')

                    #if data == self.IDSoil and i == 0:
                    #    combo_box.addItem('Feature IDs of Soil Layer')

                    combo_box.addItems([str(field) for field in fieldsSoil])
                    i += 1
                    self.dlg.tableSoilMapping.setCellWidget(row, i, combo_box)
            #self.dlg.progressbar.setValue(40)
            self.dlg.onCreateSoilLayer.setVisible(True)
        
        except Exception as e:
            self.log_to_qtalsim_tab(f"{e}", Qgis.Critical) 
            #self.dlg.progressbar.setValue(0)

        finally:
            self.end_operation()


    def confirmSoilMapping(self):
        '''
            Creates a new Soil Talsim layer, which holds all the parameters of Table soilmapping. 
                Also eliminates soil polygons defined as too small (by user input). 
        '''
        try:
            self.start_operation()
            self.log_to_qtalsim_tab(f"Starting Soil Mapping.", Qgis.Info) 
            #self.dlg.progressbar.setValue(0)
            self.soilLayerIntermediate = None

            #Create Layer
            self.soilLayerIntermediate = QgsVectorLayer(f"Polygon?crs={self.soilLayer.crs().authid()}", "SoilLayerEdited", "memory")

            #Get features of input soil layer
            feats = [feat for feat in self.soilLayer.getFeatures()] 

            #Populate new Soil Talsim Layers with features of input soil layer
            mem_layer_data = self.soilLayerIntermediate.dataProvider()
            attr = self.soilLayer.dataProvider().fields().toList()
            mem_layer_data.addAttributes(attr)
            self.soilLayerIntermediate.updateFields()
            mem_layer_data.addFeatures(feats)

            #Get the mapping of the talsim soil parameter and the corresponding parameters defined by the user
            textureTypes = self.dfsoilParametersTalsim.loc[:,'TextureType'] #Get the texture types
            value_mapping = {}
            new_fields = []
            self.soilIDNames = []
            fields_wrong_datatype = [] #Store those fields that have a wrong datatype 
            #self.dlg.progressbar.setValue(10)
            self.log_to_qtalsim_tab(f"Progress: 10.00% done", Qgis.Info)

            for row in range(self.dlg.tableSoilMapping.rowCount()): #Loop over all entries of the Soil Mapping Table
                for i in range(0, self.number_soilLayers): 
                    old_field = self.dlg.tableSoilMapping.cellWidget(row, i+1).currentText() #Current Text of Combo-Box specified by user
                    full_text = self.dlg.tableSoilMapping.item(row, 0).text()
                    #Remove the unit from the text of the field name
                    cleaned_text = full_text.split()[0]
                    new_field = f"{cleaned_text}_soillayer{i+1}" #Add the soil layer to the name of the field
                    #new_field = f"{self.dlg.tableSoilMapping.item(row, 0).text()}_soillayer{i+1}" #Get Talsim parameter
                    value_mapping[new_field] = old_field
                    if textureTypes[row].strip() == 'string':
                        type = QVariant.String
                    elif textureTypes[row].strip() == 'float':
                        type = QVariant.Double
                    elif textureTypes[row].strip() == 'int':
                        type = QVariant.Int
                    else:
                        type = QVariant.String
                    new_fields.append(QgsField(str(new_field), type)) #Store talsim parameters in a variable
                    self.soilFieldNames.append(new_field)
                    if self.soilLayerIntermediate.fields().indexOf(old_field) != -1:
                        type_old = self.soilLayerIntermediate.fields().field(old_field).type()
                        if type_old != type:
                            self.log_to_qtalsim_tab(f'You entered {old_field} for Talsim parameter {new_field}. Your field has type {QVariant.typeToName(type_old)}, when it should have type {QVariant.typeToName(type)}.', Qgis.Warning)
                            fields_wrong_datatype.append(old_field)
                        #if new_field == 'Name' and old_field is 'Parameter not available':
            
            #self.dlg.progressbar.setValue(20)
            
            #Get the number of soilLayers added by the user
            self.number_soilLayers = 0 
            #Iterate over the columns of the names
            for col in range(1, self.dlg.tableSoilMapping.columnCount()):
                cell_value = self.dlg.tableSoilMapping.cellWidget(0, col).currentText()
                #Check if the cell value is not 'Parameter not available'
                if cell_value != 'Parameter not available':
                    self.number_soilLayers += 1  #increment the counter

            #Add ID column for each soil layer
            for i in range(0, self.number_soilLayers): 
                new_field = f"{self.IDSoil}_soillayer{i+1}"
                new_fields.append(QgsField(str(new_field), QVariant.Int))
                self.soilFieldNames.append(new_field)
                self.soilIDNames.append(new_field)

            self.soilLayerIntermediate.dataProvider().addAttributes(new_fields) #create new fields with the talsim parameters
            self.soilLayerIntermediate.updateFields()
            self.log_to_qtalsim_tab(f"Progress: 20.00% done", Qgis.Info)
            total_features = self.soilLayerIntermediate.featureCount()
            analysed_features = 0
            last_logged_progress = 20
            start_progress = 20
            if self.dlg.checkboxIntersectShareofArea.isChecked() or self.dlg.checkboxIntersectMinSizeArea.isChecked(): 
                end_progress = 50
            else:
                end_progress = 80
            progress_range = end_progress - last_logged_progress

            #Populate soil parameter fields in soil layer
            request = QgsFeatureRequest().setFlags(QgsFeatureRequest.NoGeometry)
            self.soilLayerIntermediate.startEditing()
            try: 
                i = 1 #Add an ID for internal processing
                for feature in self.soilLayerIntermediate.getFeatures(request):
                    analysed_features += 1
                    relative_progress = (analysed_features / total_features) * progress_range
                    progress = start_progress + relative_progress
                    if relative_progress - last_logged_progress >= 5:
                        last_logged_progress = progress
                        #self.dlg.progressbar.setValue(int(progress))
                        self.log_to_qtalsim_tab(f"Progress: {progress:.2f}% done", Qgis.Info) 
                    for field_name in self.soilIDNames: #Set an ID for each soil layer
                        feature.setAttribute(field_name, i) 
                    i += 1 #increment ID
                    for new_field, old_field in value_mapping.items():
                        if old_field == 'Parameter not available' and str(new_field).startswith('BulkDensityClass') and int(str(new_field)[-1]) <= self.number_soilLayers:
                            feature[new_field] = 3 #Medium bulk density class if there is no input
                        elif old_field == 'Parameter not available':
                            feature[new_field] = None 
                        else:
                            new_field_type = self.soilLayerIntermediate.fields().field(new_field).type()
                            if isinstance(feature[old_field], str) and new_field_type == QVariant.Int:
                                try:
                                    feature[new_field] = int(feature[old_field])
                                except:
                                    feature[new_field] = None
                            elif isinstance(feature[old_field], str) and new_field_type == QVariant.Double:
                                try:
                                    feature[new_field] = float(feature[old_field])
                                except:
                                    feature[new_field] = None
                            else:
                                feature[new_field] = feature[old_field]                              
                    try:
                        self.soilLayerIntermediate.updateFeature(feature)
                    except Exception as e:
                        self.soilLayerIntermediate.updateFeature(feature)
                        self.log_to_qtalsim_tab(f"{e}", level=Qgis.Warning)
            except Exception as e:
                error_message = f"An error occurred: {str(e)}"
                self.log_to_qtalsim_tab(error_message, level=Qgis.Critical)
            self.soilLayerIntermediate.commitChanges()
            
            if self.dlg.checkboxIntersectShareofArea.isChecked() or self.dlg.checkboxIntersectMinSizeArea.isChecked(): 
                self.soilLayerIntermediate = self.deletePolygonsBelowThreshold(self.soilLayerIntermediate, self.soilFieldNames, self.soilIDNames)
            #self.dlg.progressbar.setValue(90)
            self.log_to_qtalsim_tab(f"Progress: 90.00% done", Qgis.Info)
            try:   
                #Only keep relevant fields
                all_fields = [field.name() for field in self.soilLayerIntermediate.fields()]
                fields_to_delete_indices = [self.soilLayerIntermediate.fields().indexFromName(field) for field in all_fields if field not in self.soilFieldNames]
                self.soilLayerIntermediate.startEditing()
                self.soilLayerIntermediate.dataProvider().deleteAttributes(fields_to_delete_indices)
                self.soilLayerIntermediate.commitChanges()
                self.soilLayerIntermediate.updateFields()

            except Exception as e:
                self.log_to_qtalsim_tab(f"{e}", Qgis.Critical)

            self.soilLayerIntermediate.dataProvider().reloadData()

            #Add checkmark when process is finished
            current_text = self.dlg.onConfirmSoilMapping.text()
            if "✓" not in current_text:  #Avoid duplicate checkmarks
                self.dlg.onConfirmSoilMapping.setText(f"{current_text} ✓")
                self.dlg.groupboxSoilOptional.setEnabled(True)
            
            self.soilLayerIntermediate.setName("SoilLayerEdited")
            QgsProject.instance().addMapLayer(self.soilLayerIntermediate)
            
            self.log_to_qtalsim_tab(f"Progress: 100.00% done", Qgis.Info)
            #self.dlg.progressbar.setValue(0)
            self.log_to_qtalsim_tab(f"Finished soil parameter mapping. Inspect results in this temporary layer: {self.soilLayerIntermediate.name()}.", Qgis.Info) 
        
        except Exception as e:
            self.log_to_qtalsim_tab(f"{e}", Qgis.Critical) 
            #self.dlg.progressbar.setValue(0)

        finally:
            self.end_operation()
        
    def checkOverlappingSoil(self):
        '''
            Checks for overlapping soilFeatures and fills the table to delete overlapping parts of features.
        '''
        try:
            
            self.start_operation()
            layer_input_name = self.soilLayerIntermediate.name()
            self.log_to_qtalsim_tab(f"QTalsim is currently loading, checking for overlapping features.", Qgis.Info)
            self.soilLayerIntermediate, self.overlapping_soil_features = self.checkOverlappingFeatures(self.soilLayerIntermediate)

            #Create unique combinations of the overlapping features
            unique_combinations_set = set()
            for feature_pair in self.overlapping_soil_features:
                # Convert the list to a tuple for set compatibility
                feature_pair_tuple = tuple(sorted(feature_pair))
                unique_combinations_set.add(feature_pair_tuple)
            self.overlapping_soil_features = [list(pair) for pair in unique_combinations_set]

            self.dlg.tableSoilTypeDelete.clear()
            #self.dlg.tableSoilTypeDelete.setSelectionMode(QListWidget.ExtendedSelection)
            
            self.dlg.tableSoilTypeDelete.setRowCount(len(self.overlapping_soil_features))
            self.dlg.tableSoilTypeDelete.setColumnCount(3)
            self.dlg.tableSoilTypeDelete.setHorizontalHeaderLabels(['Overlapping Feature 1', 'Overlapping Feature 2', 'Show Features in Layer'])
            
            # Set the size of the table columns
            self.dlg.tableSoilTypeDelete.setColumnWidth(0, 200)
            self.dlg.tableSoilTypeDelete.setColumnWidth(1, 200)
            self.dlg.tableSoilTypeDelete.setColumnWidth(2, 200)

            self.soilFields = self.soilLayerIntermediate.fields()
            field_index = self.soilFields.indexFromName(f"{self.nameSoil}_soillayer1") #Which field should be shown in the list of overlapping 
            self.dlg.tableSoilTypeDelete.setUpdatesEnabled(False)
            self.radio_buttons_soil = []
            self.radio_button_to_row_soil = {}

            count_features = len(self.overlapping_soil_features)
            analysed_features = 0
            last_logged_progress = 0

            #Iterate through the features of the duplicate layer
            for row, feature_pair in enumerate(self.overlapping_soil_features):
                #Log Progress
                analysed_features += 1
                if count_features >= 1:
                    progress = (analysed_features/count_features)*100
                if progress - last_logged_progress >= 10:
                    self.log_to_qtalsim_tab(f"Progress: {progress:.2f}% done", Qgis.Info)
                    last_logged_progress = progress

                feature_id1, feature_id2 = feature_pair
                feature1 = self.soilLayerIntermediate.getFeature(feature_id1)
                feature2 = self.soilLayerIntermediate.getFeature(feature_id2)

                overlappingName1 = str(feature1[field_index])  #Get the names of field soil names
                overlappingName2 = str(feature2[field_index]) #Get the names of field soil names
                
                item1 = QTableWidgetItem(overlappingName1) #add first name
                self.dlg.tableSoilTypeDelete.setItem(row, 0, item1)

                item2 = QTableWidgetItem(overlappingName2)
                self.dlg.tableSoilTypeDelete.setItem(row, 1, item2)

                transparent_item = QTableWidgetItem()
                transparent_item.setFlags(Qt.ItemIsEnabled)
                self.dlg.tableSoilTypeDelete.setItem(row, 2, transparent_item)

                #Add radio buttons to select features in the layer
                radio_button = QRadioButton()
                self.radio_buttons_soil.append(radio_button)
                self.dlg.tableSoilTypeDelete.setCellWidget(row, 2, radio_button)
                self.radio_button_to_row_soil[radio_button] = row

            self.dlg.tableSoilTypeDelete.itemSelectionChanged.connect(lambda: self.on_selection_change(self.dlg.tableSoilTypeDelete))
            for radio_button in self.radio_buttons_soil:
                radio_button.toggled.connect(
                    lambda checked, rb=radio_button: self.on_radio_button_toggled(
                        checked, self.soilLayerIntermediate, self.overlapping_soil_features, self.radio_button_to_row_soil, rb
                    )
                )
            self.dlg.tableSoilTypeDelete.setUpdatesEnabled(True)

            self.soilLayerIntermediate.setName(layer_input_name)

            #Add checkmark when process is finished
            current_text = self.dlg.onOverlappingSoils.text()
            if "✓" not in current_text:  #Avoid duplicate checkmarks
                self.dlg.onOverlappingSoils.setText(f"{current_text} ✓")
        except Exception as e:
            self.log_to_qtalsim_tab(f"{e}", Qgis.Critical) 
            #self.dlg.progressbar.setValue(0)

        finally:
            self.end_operation()
    
    def deleteSoilTypes(self):
        '''
            Deletes overlapping soil features, selected by the user.
        ''' 
        try:
            self.start_operation()
            self.log_to_qtalsim_tab("QTalsim is currently loading, editing overlapping soil features.", Qgis.Info)
            layer_input_name = self.soilLayerIntermediate.name()

            for radio_button in self.radio_buttons_soil:
                radio_button.setChecked(False)
            self.soilLayerIntermediate = self.deleteOverlappingFeatures(self.soilLayerIntermediate, self.dlg.tableSoilTypeDelete, self.overlapping_soil_features)
            #self.dlg.progressbar.setValue(50)
            self.log_to_qtalsim_tab(f"Progress: 50.00% done", Qgis.Info)
            self.checkOverlappingSoil()
            #self.dlg.progressbar.setValue(90)
            self.log_to_qtalsim_tab(f"Progress: 90.00% done", Qgis.Info)
            if not layer_input_name.startswith("SoilLayerEdited"):
                layer_input_name = "SoilLayerEdited"

            layer_name = self.update_layer_name(layer_input_name, function='overlap')
            self.soilLayerIntermediate.setName(layer_name)
            
            #Add checkmark when process is finished
            current_text = self.dlg.onSoilTypeDelete.text()
            if "✓" not in current_text:  #Avoid duplicate checkmarks
                self.dlg.onSoilTypeDelete.setText(f"{current_text} ✓")
            QgsProject.instance().addMapLayer(self.soilLayerIntermediate)
            #self.dlg.progressbar.setValue(0)
            self.log_to_qtalsim_tab(f"Progress: 100.00% done", Qgis.Info)

            self.log_to_qtalsim_tab(f"Deleting overlapping soil features finished.", Qgis.Info)

        except Exception as e:
            self.log_to_qtalsim_tab(f"{e}", Qgis.Critical) 
            #self.dlg.progressbar.setValue(0)

        finally:
            self.end_operation()

    def deleteOverlappingSoilFeatures(self):
        '''
            Deletes Overlapping Soil Features.
        '''
        try:
            self.start_operation()
            self.log_to_qtalsim_tab("QTalsim is currently loading, deleting overlapping soil features.", Qgis.Info)
            changes_made = True            
            layer_input_name = self.soilLayerIntermediate.name()
            self.last_logged_progress = 0
            
            self.soilLayerIntermediate, changes_made = self.editOverlappingFeatures(self.soilLayerIntermediate)
            
            if not layer_input_name.startswith("SoilLayerEdited"):
                layer_input_name = "SoilLayerEdited"

            layer_name = self.update_layer_name(layer_input_name, function='overlap')
            self.soilLayerIntermediate.setName(layer_name)

            #Add checkmark when process is finished
            current_text = self.dlg.onDeleteOverlappingSoilFeatures.text()
            if "✓" not in current_text:  #Avoid duplicate checkmarks
                self.dlg.onDeleteOverlappingSoilFeatures.setText(f"{current_text} ✓")
            QgsProject.instance().addMapLayer(self.soilLayerIntermediate)
            
            self.end_operation()
            self.log_to_qtalsim_tab(f"Deleting overlapping soil features finished.", Qgis.Info)
        
        except Exception as e:
            self.log_to_qtalsim_tab(f"{e}", Qgis.Critical) 

        finally:
            self.end_operation()

    def checkGapsSoil(self):
        '''
            Checks for gaps in soil layer.
        '''
        try:
            self.log_to_qtalsim_tab("QTalsim is currently loading, checking for gaps in soil layer.", Qgis.Info)
            self.start_operation()
            self.soilGaps = self.checkGaps(self.soilLayerIntermediate, self.clippingEZG)

            #Add checkmark when process is finished
            current_text = self.dlg.onCheckGapsSoil.text()
            if "✓" not in current_text:  #Avoid duplicate checkmarks
                self.dlg.onCheckGapsSoil.setText(f"{current_text} ✓")

        except Exception as e:
            self.log_to_qtalsim_tab(f"{e}", Qgis.Critical) 

        finally:
            self.end_operation()

    def fillGapsSoil(self):
        '''
            Fills gaps of soil layer according to mode chosen by user.
        '''
        self.log_to_qtalsim_tab("QTalsim is currently loading, filling gaps of soil layer.", Qgis.Info)
        try:
            self.start_operation()
            mode = 0 
            layer_input_name = self.soilLayerIntermediate.name()
            if self.dlg.comboboxModeEliminateSoil.currentText() == 'Smallest Area':
                mode = 1
            elif self.dlg.comboboxModeEliminateSoil.currentText() == 'Largest Common Boundary':
                mode = 2
            elif self.dlg.comboboxModeEliminateSoil.currentText() == 'Largest Area':
                mode = 0
            
            layerWithoutGaps = self.fillGaps(self.soilLayerIntermediate, self.clippingEZG, mode)
            if self.soilLayerIntermediate:
                QgsProject.instance().removeMapLayer(self.soilLayerIntermediate)
            if self.soilGaps:
                QgsProject.instance().removeMapLayer(self.soilGaps)
                self.soilGaps = None
            self.soilLayerIntermediate = layerWithoutGaps

            #If a gap cannot be removed, log the feature-id and tell the user to remove it manually
            for feature in self.soilLayerIntermediate.getFeatures():
                if feature['gapFeature'] == 1:
                    self.log_to_qtalsim_tab(f"Gap with feature-id {feature.id()} cannot be eliminated. If this gap is unwanted, please delete it manually.", Qgis.Info)

            if not layer_input_name.startswith("SoilLayerEdited"): #This should always be the start of these edited layers
                layer_input_name = "SoilLayerEdited"
            
            layer_name = self.update_layer_name(layer_input_name, function='gaps')

            self.soilLayerIntermediate.setName(layer_name)
            self.log_to_qtalsim_tab(f"Filled gaps of layer {self.soilLayerIntermediate.name()}.", Qgis.Info) 
            QgsProject.instance().addMapLayer(self.soilLayerIntermediate)

            #Add checkmark when process is finished
            current_text = self.dlg.onFillGapsSoil.text()
            if "✓" not in current_text:  #Avoid duplicate checkmarks
                self.dlg.onFillGapsSoil.setText(f"{current_text} ✓")
        except Exception as e:
            self.log_to_qtalsim_tab(f"{e}", Qgis.Critical) 

        finally:
            self.end_operation()
        
    def createSoilLayer(self):
        '''
            Creates Talsim Soil Layer 
                Dissolves by Talsim parameters and deletes fields that are not needed for Talsim. 
        '''
        try:
            self.start_operation()
            #self.dlg.progressbar.setValue(0)
            #Dissolve the layer using the talsim soil parameters
            if not self.soilLayerIntermediate:
                self.log_to_qtalsim_tab("Soil layer was deleted or not yet created. Please create the soil layer.", Qgis.Critical)
                self.end_operation()
                return
            try:
                resultDissolve = processing.run("native:dissolve", {'INPUT':self.soilLayerIntermediate,'FIELD': self.soilFieldNames,'SEPARATE_DISJOINT':True,'OUTPUT':'TEMPORARY_OUTPUT'}, feedback=None)
                self.soilTalsim = resultDissolve['OUTPUT']
            except:
                self.soilLayerIntermediate,_ = self.make_geometries_valid(self.soilLayerIntermediate)
                resultDissolve = processing.run("native:dissolve", {'INPUT':self.soilLayerIntermediate,'FIELD': self.soilFieldNames,'SEPARATE_DISJOINT':True,'OUTPUT':'TEMPORARY_OUTPUT'}, feedback=None)
                self.soilTalsim = resultDissolve['OUTPUT']
            
            #self.dlg.progressbar.setValue(50)
            self.log_to_qtalsim_tab(f"Progress: 50.00% done", Qgis.Info)

            all_fields = [field.name() for field in self.soilTalsim.fields()]
            fields_to_delete_indices = [self.soilTalsim.fields().indexFromName(field)  for field in all_fields if field not in self.soilFieldNames]
            self.soilTalsim.startEditing()
            self.soilTalsim.dataProvider().deleteAttributes(fields_to_delete_indices)
            self.soilTalsim.commitChanges()
            self.soilTalsim.updateFields()
            #self.dlg.progressbar.setValue(90)
            self.log_to_qtalsim_tab(f"Progress: 90.00% done", Qgis.Info)
            if self.soilLayer:
                QgsProject.instance().removeMapLayer(self.soilLayer)

            if self.soilLayerIntermediate:
                QgsProject.instance().removeMapLayer(self.soilLayerIntermediate)
            self.soilTalsim.setName("Talsim Soil")
            QgsProject.instance().addMapLayer(self.soilTalsim)  
            
            #Add checkmark when process is finished
            current_text = self.dlg.onCreateSoilLayer.text()
            if "✓" not in current_text:  #Avoid duplicate checkmarks
                self.dlg.onCreateSoilLayer.setText(f"{current_text} ✓")
                current_text_groupbox = self.dlg.soilGroupBox.title()
                self.dlg.soilGroupBox.setTitle(f"{current_text_groupbox} ✓")
            self.log_to_qtalsim_tab(f"Progress: 100.00% done", Qgis.Info)
            if self.landuseTalsim:
                self.dlg.groupboxIntersect.setEnabled(True)
                self.dlg.groupboxIntersect.setChecked(True)
            #self.dlg.progressbar.setValue(0)
            self.log_to_qtalsim_tab(f"Created Soil Layer with Talsim Parameters: {self.soilTalsim.name()}.", Qgis.Info) 
        
        except Exception as e:
            self.log_to_qtalsim_tab(f"{e}", Qgis.Critical) 
            #self.dlg.progressbar.setValue(0)

        finally:
            self.end_operation()

    '''
        Land Use Layer
    '''
    def selectLanduse(self):
        '''
            Clips Landuse Layer and deletes duplicate geometries. 
        '''
        try:
            self.start_operation()
            self.log_to_qtalsim_tab(f"QTalsim is currently loading, starting the clipping process of the land use layer.", Qgis.Info)
            
            if self.landuseLayer:
                QgsProject.instance().removeMapLayer(self.landuseLayer)
            if self.clippingEZG is None:
                selected_layer_name = None
                raise Exception("User has not selected a sub-basins layer.")
            selected_layer_name = self.dlg.comboboxLanduseLayer.currentText()
            self.landuseLayer = QgsProject.instance().mapLayersByName(selected_layer_name)[0]
            
            #Create field with the feature-id
            self.landuseFieldInputID = 'fid_qta' 
            existing_field_names = [field.name() for field in self.landuseLayer.fields()]
            if self.landuseFieldInputID in existing_field_names:
                self.log_to_qtalsim_tab(f"Please rename field {self.landuseFieldInputID} of layer {selected_layer_name} or delete the field.", Qgis.Critical)
                return

            #Start editing the layer
            self.landuseLayer.startEditing()

            #Add a new integer field called 'ID'
            field = QgsField(self.landuseFieldInputID, QVariant.Int)
            self.landuseLayer.dataProvider().addAttributes([field])
            self.landuseLayer.updateFields()

            request = QgsFeatureRequest().setFlags(QgsFeatureRequest.NoGeometry).setSubsetOfAttributes(
                [self.landuseFieldInputID], self.landuseLayer.fields()
            )

            #Assign ID values to each feature
            for i, feature in enumerate(self.landuseLayer.getFeatures(request)): 
                self.landuseLayer.changeAttributeValue(feature.id(), self.landuseLayer.fields().indexFromName(self.landuseFieldInputID), i + 1)

            #Commit changes
            self.landuseLayer.commitChanges()
            #self.dlg.progressbar.setValue(10)
            self.log_to_qtalsim_tab(f"Progress: 10.00% done", Qgis.Info)
            number_of_features = self.landuseLayer.featureCount()
            #Clip Layer
            outputLayer = self.clipLayer(self.landuseLayer, self.clippingEZG)
            outputLayer = processing.run("native:deleteduplicategeometries", {'INPUT': outputLayer ,'OUTPUT':'TEMPORARY_OUTPUT'},feedback=self.feedback)['OUTPUT']
            self.landuseLayer = outputLayer
            #self.dlg.progressbar.setValue(50)
            self.log_to_qtalsim_tab(f"Progress: 50.00% done", Qgis.Info)
            #Get the fields of the selected layer
            self.landuseFields = self.landuseLayer.fields()
            
            self.fillLanduseTable()
            #self.dlg.progressbar.setValue(90)
            self.log_to_qtalsim_tab(f"Progress: 90.00% done", Qgis.Info)
            self.landuseLayer.setName("LanduseLayer")
            QgsProject.instance().addMapLayer(self.landuseLayer)

            #Add checkmark when process is finished
            current_text = self.dlg.onLanduseLayer.text()
            if "✓" not in current_text:  #Avoid duplicate checkmarks
                self.dlg.onLanduseLayer.setText(f"{current_text} ✓")

            self.log_to_qtalsim_tab(f"Successfully selected and clipped land use layer: {self.landuseLayer.name()}.", Qgis.Info) 
            #self.dlg.progressbar.setValue(0)
            self.log_to_qtalsim_tab(f"Progress: 100.00% done", Qgis.Info)
        except Exception as e:
            self.log_to_qtalsim_tab(f"{e}", Qgis.Critical) 
            #self.dlg.progressbar.setValue(0)

        finally:
            if selected_layer_name:
                # Remove the created landuse-ID-field from input layer
                layer = QgsProject.instance().mapLayersByName(selected_layer_name)[0]
                layer.startEditing()
                field_index = layer.fields().indexFromName(self.landuseFieldInputID)

                # Delete the field
                layer.dataProvider().deleteAttributes([field_index])
                layer.updateFields()
                layer.commitChanges()
            self.end_operation()

    def fillLanduseTable(self):
        '''
            Fills the landuse mapping table with the field names of the land use layer and the Talsim parameter names.
                Is executed in function selectLanduse.
        '''
        try:
            self.start_operation()
            fieldsLanduse = [field.name() for field in self.landuseLayer.fields()]
            current_path = os.path.dirname(os.path.abspath(__file__))
            landuseTalsimPath = os.path.join(current_path, "talsim_parameter", "landuseParameter.csv")
            self.dfLanduseParametersTalsim = pd.read_csv(landuseTalsimPath,delimiter = ';')

            #Create Table
            self.dlg.tableLanduseMapping.setRowCount(self.dfLanduseParametersTalsim.shape[0])
            self.dlg.tableLanduseMapping.setColumnCount(2)
            self.dlg.tableLanduseMapping.setHorizontalHeaderLabels(['Talsim Land use Parameters', 'Land use Layer Fields'])
            
            # Set the size of the table columns
            self.dlg.tableLanduseMapping.setColumnWidth(0, 300)
            self.dlg.tableLanduseMapping.setColumnWidth(1, 300)

            #Get Landuse Data from csv-file
            landuseTypes = self.dfLanduseParametersTalsim.loc[:,'LandUse']
            landuseUnit = self.dfLanduseParametersTalsim.loc[:,'Unit']
            #Fill data
            for row, data in enumerate(landuseTypes):

                item = QTableWidgetItem(str(data)) #add landuse
                unit = landuseUnit[row] #get unit
                if unit != '-':
                    item.setText(item.text() + ' [' + str(unit) + ']')  #Add unit of parameter
                self.dlg.tableLanduseMapping.setItem(row, 0, item)

                restriction = self.dfLanduseParametersTalsim.loc[row, 'Restrictions'] #Add restriction as tooltip
                if pd.notna(restriction):
                    item.setToolTip(f"Restriction: {restriction}")
                
                combo_box = QComboBox()
                # Add parameters as items to the combo box
                if data != self.fieldLanduseID:
                    combo_box.addItem('Parameter not available')

                if data == self.fieldLanduseID:
                    combo_box.addItem('Feature IDs of Land use Layer')

                combo_box.addItems([str(field) for field in fieldsLanduse])
                self.dlg.tableLanduseMapping.setCellWidget(row, 1, combo_box)

            self.dlg.onCreateLanduseLayer.setVisible(True)
        
        except Exception as e:
            self.log_to_qtalsim_tab(f"{e}", Qgis.Critical) 

        finally:
            self.end_operation()

    def confirmLanduseMapping(self):
        '''
            Creates a new Land use Talsim layer, which holds all the parameters of Table landusemapping. 
                Also eliminates land use polygons defined as too small (by user input). 
        '''
        try:
            self.start_operation()
            self.log_to_qtalsim_tab(f"Starting Land use Mapping.", Qgis.Info) 
            self.landuseTalsim = None

            #Get Talsim land use parameter values
            current_path = os.path.dirname(os.path.abspath(__file__))
            landuseValuesTalsimPath = os.path.join(current_path, "talsim_parameter", "landuseParameterValues.csv")
            self.dfLanduseParameterValuesTalsim = pd.read_csv(landuseValuesTalsimPath, delimiter = ';')
            print(self.dfLanduseParameterValuesTalsim) #hier weitermachen - nan values Problem - evtl. unten mal alle ausgeben lassen if nan then None

            #Create Layer
            self.landuseTalsim = QgsVectorLayer(f"Polygon?crs={self.landuseLayer.crs().authid()}", "LanduseLayerEdited", "memory")

            #Get features of input landuse layer
            feats = [feat for feat in self.landuseLayer.getFeatures()] 

            #Populate new Land use Talsim Layers with features of input land use layer
            mem_layer_data = self.landuseTalsim.dataProvider()
            attr = self.landuseLayer.dataProvider().fields().toList()
            mem_layer_data.addAttributes(attr)
            self.landuseTalsim.updateFields()
            mem_layer_data.addFeatures(feats)
              
            #Get the mapping of the talsim land use parameter and the corresponding parameters defined by the user
            fieldType = self.dfLanduseParametersTalsim.loc[:,'Type'] #Get the texture types
            value_mapping = {}
            new_fields = []
            fields_wrong_datatype = [] #Store those fields that have a wrong datatype 
            for row in range(self.dlg.tableLanduseMapping.rowCount()): #Loop over all entries of the Land use Mapping Table
                old_field = self.dlg.tableLanduseMapping.cellWidget(row, 1).currentText() #Current Text of Combo-Box specified by user
                
                full_text = self.dlg.tableLanduseMapping.item(row, 0).text() 
                #Remove the unit from the text of the field name
                new_field = full_text.split()[0] #save only the field name without the unit
                value_mapping[new_field] = old_field
                if fieldType[row].strip() == 'string':
                    type = QVariant.String
                elif fieldType[row].strip() == 'float':
                    type = QVariant.Double
                elif fieldType[row].strip() == 'int':
                    type = QVariant.Type.Int
                else:
                    type = QVariant.String
                new_fields.append(QgsField(str(new_field), type)) #Store talsim parameters in a variable
                self.selected_landuse_parameters.append(new_field)
                if self.landuseTalsim.fields().indexOf(old_field) != -1:
                    type_old = self.landuseTalsim.fields().field(old_field).type()
                    if type_old != type:
                        self.log_to_qtalsim_tab(f'You entered {old_field} for Talsim parameter {new_field}. Your field has type {QVariant.typeToName(type_old)}, when it should have type {QVariant.typeToName(type)}.', Qgis.Warning)
                        fields_wrong_datatype.append(old_field)
            #self.dlg.progressbar.setValue(20)      
            self.landuseTalsim.dataProvider().addAttributes(new_fields) #Create new fields with the talsim parameters
            self.landuseTalsim.updateFields()
            print(value_mapping)
            #Populate landuse parameter fields in land use layer
            total_features = self.landuseTalsim.featureCount()
            self.log_to_qtalsim_tab(f"Progress: 20.00% done", Qgis.Info)  
            analysed_features = 0
            last_logged_progress = 20
            start_progress = 20
            if self.dlg.checkboxIntersectShareofArea.isChecked() or self.dlg.checkboxIntersectMinSizeArea.isChecked(): 
                end_progress = 50
            else:
                end_progress = 80
            progress_range = end_progress - last_logged_progress
            print(self.dfLanduseParameterValuesTalsim.columns)
            request = QgsFeatureRequest().setFlags(QgsFeatureRequest.NoGeometry)
            self.landuseTalsim.startEditing()
            try:
                for feature in self.landuseTalsim.getFeatures(request): 
                    analysed_features += 1
                    relative_progress = (analysed_features / total_features) * progress_range
                    progress = start_progress + relative_progress
                    if relative_progress - last_logged_progress >= 5:
                        last_logged_progress = progress
                        #self.dlg.progressbar.setValue(int(progress))
                        self.log_to_qtalsim_tab(f"Progress: {progress:.2f}% done", Qgis.Info)
                    old_field = value_mapping['Name']
                    input_landuse = str(feature[old_field]).strip().lower()
                    for new_field, old_field in value_mapping.items():
                        if old_field == 'Parameter not available' and not str(new_field).startswith('pTAW'):
                            print(new_field)
                            if new_field in self.dfLanduseParameterValuesTalsim.columns:
                                #Assign the saved talsim land use parameters to the land use features
                                print("hier")
                                mask = self.dfLanduseParameterValuesTalsim['Name'].str.strip().str.lower() == input_landuse.strip().lower()
                                if mask.any():
                                    val = self.dfLanduseParameterValuesTalsim.loc[mask, new_field].iloc[0]
                                    feature[new_field] = None if pd.isna(val) else val
                                    print(feature[new_field])
                            else:
                                feature[new_field] = None 
                        elif old_field == 'Parameter not available' and str(new_field).startswith('pTAW'):
                            feature[new_field] = 0.5
                        elif old_field == 'Feature IDs of Land use Layer':
                            feature[new_field] = int(feature[self.landuseFieldInputID])
                        else:
                            new_field_type = self.landuseTalsim.fields().field(new_field).type()
                            if isinstance(feature[old_field], str) and new_field_type == QVariant.Int:
                                try:
                                    feature[new_field] = int(feature[old_field])
                                except:
                                    feature[new_field] = None
                            elif isinstance(feature[old_field], str) and new_field_type == QVariant.Double:
                                try:
                                    feature[new_field] = float(feature[old_field])
                                except:
                                    feature[new_field] = None
                            else:
                                feature[new_field] = feature[old_field]                              
                    try:
                        self.landuseTalsim.updateFeature(feature)
                    except Exception as e:
                        self.landuseTalsim.updateFeature(feature)
                        self.log_to_qtalsim_tab(f"{e}", level=Qgis.Warning)
            except Exception as e:
                error_message = f"An error occurred: {str(e)}"
                self.log_to_qtalsim_tab(error_message, level=Qgis.Critical)
            self.landuseTalsim.commitChanges()

            if self.dlg.checkboxIntersectShareofArea.isChecked() or self.dlg.checkboxIntersectMinSizeArea.isChecked(): 
                self.landuseTalsim = self.deletePolygonsBelowThreshold(self.landuseTalsim, self.selected_landuse_parameters, self.fieldLanduseID)
            #self.dlg.progressbar.setValue(90)   
            self.log_to_qtalsim_tab(f"Progress: 90.00% done", Qgis.Info)
            try:   
                #Only keep relevant fields
                all_fields = [field.name() for field in self.landuseTalsim.fields()]
                fields_to_delete_indices = [self.landuseTalsim.fields().indexFromName(field) for field in all_fields if field not in self.selected_landuse_parameters]
                self.landuseTalsim.startEditing()
                self.landuseTalsim.dataProvider().deleteAttributes(fields_to_delete_indices)
                self.landuseTalsim.commitChanges()
                self.landuseTalsim.updateFields()
            except Exception as e:
                self.log_to_qtalsim_tab(f"{e}", Qgis.Critical)
            self.landuseTalsim.dataProvider().reloadData()
            self.landuseTalsim.setName("LanduseLayerEdited")
            QgsProject.instance().addMapLayer(self.landuseTalsim)

            #Add checkmark when process is finished
            current_text = self.dlg.onConfirmLanduseMapping.text()
            if "✓" not in current_text:  #Avoid duplicate checkmarks
                self.dlg.onConfirmLanduseMapping.setText(f"{current_text} ✓")
                self.dlg.groupboxLanduseOptional.setEnabled(True)

            self.log_to_qtalsim_tab(f"Finished land use parameter mapping. Inspect results in this temporary layer: {self.landuseTalsim.name()}.", Qgis.Info) 
            #self.dlg.progressbar.setValue(0) 
            self.log_to_qtalsim_tab(f"Progress: 100.00% done", Qgis.Info)
        except Exception as e:
            self.log_to_qtalsim_tab(f"{e}", Qgis.Critical) 
            #self.dlg.progressbar.setValue(0) 
        finally:
            self.end_operation()

    def checkOverlappingLanduse(self):
        '''
            Checks for overlapping land use Features and fills the table to delete overlapping parts of features.
        '''
        try:
            self.start_operation()
            layer_input_name = self.landuseTalsim.name()
            self.log_to_qtalsim_tab(f"QTalsim is currently loading, checking for overlapping features.", Qgis.Info)
            self.landuseTalsim, self.overlapping_landuse_features = self.checkOverlappingFeatures(self.landuseTalsim)
            #self.dlg.progressbar.setValue(30)
            self.log_to_qtalsim_tab(f"Progress: 30% done", Qgis.Info)
            #Create unique combinations of the overlapping features
            unique_combinations_set = set()
            for feature_pair in self.overlapping_landuse_features:
                # Convert the list to a tuple for set compatibility
                feature_pair_tuple = tuple(sorted(feature_pair))
                unique_combinations_set.add(feature_pair_tuple)
            self.overlapping_landuse_features = [list(pair) for pair in unique_combinations_set]
            #self.dlg.progressbar.setValue(40)
            self.log_to_qtalsim_tab(f"Progress: 40% done", Qgis.Info)
            self.dlg.tableLanduseDelete.clear()

            self.dlg.tableLanduseDelete.setRowCount(len(self.overlapping_landuse_features))
            self.dlg.tableLanduseDelete.setColumnCount(3)
            self.dlg.tableLanduseDelete.setHorizontalHeaderLabels(['Overlapping Feature 1', 'Overlapping Feature 2','Show Features in Layer'])
            
            # Set the size of the table columns
            self.dlg.tableLanduseDelete.setColumnWidth(0, 200)
            self.dlg.tableLanduseDelete.setColumnWidth(1, 200)
            self.dlg.tableLanduseDelete.setColumnWidth(2, 200)
            self.landuseFields = self.landuseTalsim.fields()

            field_index = self.landuseFields.indexFromName('Name')
            self.radio_buttons_landuse = []
            self.radio_button_to_row_landuse = {}

            count_features = len(self.overlapping_landuse_features)
            analysed_features = 0
            last_logged_progress = 50

            # Iterate through the features of the duplicate layer
            for row, feature_pair in enumerate(self.overlapping_landuse_features):
                if count_features >= 1:
                    progress = (analysed_features/count_features)*100
                if progress - last_logged_progress >= 10:
                    self.log_to_qtalsim_tab(f"Progress: {progress:.2f}% done", Qgis.Info)
                    last_logged_progress = progress
                    #self.dlg.progressbar.setValue(progress)

                feature_id1, feature_id2 = feature_pair
                feature1 = self.landuseTalsim.getFeature(feature_id1)
                feature2 = self.landuseTalsim.getFeature(feature_id2)

                overlappingName1 = str(feature1[field_index])
                overlappingName2 = str(feature2[field_index])
                item1 = QTableWidgetItem(overlappingName1) #add first name
                self.dlg.tableLanduseDelete.setItem(row, 0, item1)
                item2 = QTableWidgetItem(overlappingName2)
                self.dlg.tableLanduseDelete.setItem(row, 1, item2)

                #Styling
                transparent_item = QTableWidgetItem()
                transparent_item.setFlags(Qt.ItemIsEnabled)
                self.dlg.tableLanduseDelete.setItem(row, 2, transparent_item)

                #Add chechboxes to select features in the layer
                radio_button = QRadioButton()
                self.radio_buttons_landuse.append(radio_button)
                self.dlg.tableLanduseDelete.setCellWidget(row, 2, radio_button)
                self.radio_button_to_row_landuse[radio_button] = row

            self.dlg.tableLanduseDelete.itemSelectionChanged.connect(lambda: self.on_selection_change(self.dlg.tableLanduseDelete))
            for radio_button in self.radio_buttons_landuse:
                radio_button.toggled.connect(
                    lambda checked, rb=radio_button: self.on_radio_button_toggled(
                        checked, self.landuseTalsim, self.overlapping_landuse_features, self.radio_button_to_row_landuse, rb
                    )
                )
            self.landuseTalsim.setName(layer_input_name)

            #Add checkmark when process is finished
            current_text = self.dlg.onCheckOverlappingLanduse.text()
            if "✓" not in current_text:  #Avoid duplicate checkmarks
                self.dlg.onCheckOverlappingLanduse.setText(f"{current_text} ✓")

        except Exception as e:
            self.log_to_qtalsim_tab(f"{e}", Qgis.Critical) 

        finally:
            self.end_operation()

    def deleteLanduseFeatures(self):
        '''
            Deletes overlapping land use features, as selected by the user. 
        '''
        try:
            self.start_operation()
            self.log_to_qtalsim_tab("QTalsim is currently loading, editing overlapping land use features.", Qgis.Info)
            layer_input_name = self.landuseTalsim.name()
            
            for radio_button in self.radio_buttons_landuse:
                radio_button.setChecked(False)
            self.landuseTalsim = self.deleteOverlappingFeatures(self.landuseTalsim, self.dlg.tableLanduseDelete, self.overlapping_landuse_features)
            self.checkOverlappingLanduse()
        
            if not layer_input_name.startswith("LanduseLayerEdited"):
                layer_input_name = "LanduseLayerEdited"

            layer_name = self.update_layer_name(layer_input_name, function='overlap')
            
            self.landuseTalsim.setName(layer_name)
            QgsProject.instance().addMapLayer(self.landuseTalsim)

            #Add checkmark when process is finished
            current_text = self.dlg.onLanduseTypeDelete.text()
            if "✓" not in current_text:  #Avoid duplicate checkmarks
                self.dlg.onLanduseTypeDelete.setText(f"{current_text} ✓")

            self.log_to_qtalsim_tab(f"Deleting overlapping parts finished.", Qgis.Info)
        
        except Exception as e:
            self.log_to_qtalsim_tab(f"{e}", Qgis.Critical) 

        finally:
            self.end_operation()

    def deleteOverlappingLanduseFeatures(self):
        '''
            Deletes all overlapping features of land use layer. Multiple runs necessary because removing overlapping features 
            can result in more overlapping features.
        '''
        try:
            self.start_operation()
            self.log_to_qtalsim_tab("QTalsim is currently loading, deleting overlapping land use features.", Qgis.Info)
            layer_input_name = self.landuseTalsim.name()

            self.last_logged_progress = 0
            self.landuseTalsim, _ = self.editOverlappingFeatures(self.landuseTalsim)
            
            if not layer_input_name.startswith("LanduseLayerEdited"):
                layer_input_name = "LanduseLayerEdited"

            layer_name = self.update_layer_name(layer_input_name, function='overlap')
            self.landuseTalsim.setName(layer_name)

            QgsProject.instance().addMapLayer(self.landuseTalsim)

            #Add checkmark when process is finished
            current_text = self.dlg.onDeleteOverlapsLanduse.text()
            if "✓" not in current_text:  #Avoid duplicate checkmarks
                self.dlg.onDeleteOverlapsLanduse.setText(f"{current_text} ✓")
        except Exception as e:
            self.log_to_qtalsim_tab(f"{e}", Qgis.Critical) 

        finally:
            self.end_operation()

    def checkGapsLanduse(self):
        '''
            Checks for gaps in the land use layer and adds a layer representing the gaps.
        '''
        try:
            self.log_to_qtalsim_tab("QTalsim is currently loading, checking for gaps in soil layer.", Qgis.Info)
            self.start_operation()
            self.landuseGaps = self.checkGaps(self.landuseTalsim, self.clippingEZG)

            #Add checkmark when process is finished
            current_text = self.dlg.onCheckGapsLanduse.text()
            if "✓" not in current_text:  #Avoid duplicate checkmarks
                self.dlg.onCheckGapsLanduse.setText(f"{current_text} ✓")
        except Exception as e:
            self.log_to_qtalsim_tab(f"{e}", Qgis.Critical) 

        finally:
            self.end_operation()

    def fillGapsLanduse(self):
        '''
            Fills gaps in the land use area by the elimination mode selected by the user.
        '''
        try:
            self.start_operation()
            self.log_to_qtalsim_tab("QTalsim is currently loading, filling gaps of land use layer.", Qgis.Info)
            layer_input_name = self.landuseTalsim.name()
            mode = 0 
            if self.dlg.comboboxModeEliminateLanduse.currentText() == 'Smallest Area':
                mode = 1
            elif self.dlg.comboboxModeEliminateLanduse.currentText() == 'Largest Common Boundary':
                mode = 2
            elif self.dlg.comboboxModeEliminateLanduse.currentText() == 'Largest Area':
                mode = 0
            layerWithoutGaps = self.fillGaps(self.landuseTalsim,self.clippingEZG, mode)

            if self.landuseGaps:
                QgsProject.instance().removeMapLayer(self.landuseGaps)
                self.landuseGaps = None
            if self.landuseTalsim:
                QgsProject.instance().removeMapLayer(self.landuseTalsim)
            
            self.landuseTalsim = layerWithoutGaps

            if not layer_input_name.startswith("LanduseLayerEdited"):
                layer_input_name = "LanduseLayerEdited"

            layer_name = self.update_layer_name(layer_input_name, function='gaps')
            self.landuseTalsim.setName(layer_name)
            
            QgsProject.instance().addMapLayer(self.landuseTalsim)

            #Add checkmark when process is finished
            current_text = self.dlg.onFillGapsLanduse.text()
            if "✓" not in current_text:  #Avoid duplicate checkmarks
                self.dlg.onFillGapsLanduse.setText(f"{current_text} ✓")

            self.log_to_qtalsim_tab(f"Filled gaps of layer {self.landuseTalsim.name()}.", Qgis.Info) 

        except Exception as e:
            self.log_to_qtalsim_tab(f"{e}", Qgis.Critical) 

        finally:
            self.end_operation()

    def createLanduseLayer(self):
        '''
            Creates the Talsim land use Layer that is used for intersecting.
                Dissolves the Talsim land use layer.
        '''
        try:
            self.start_operation()
            if not self.landuseTalsim:
                self.log_to_qtalsim_tab("Land use layer was deleted or not yet created. Please create the land use layer.", Qgis.Critical)
                self.end_operation()
                return
            #Dissolve the layer using the talsim landuse parameters
            try:
                resultDissolve = processing.run("native:dissolve", {'INPUT':self.landuseTalsim,'FIELD': self.selected_landuse_parameters,'SEPARATE_DISJOINT':True,'OUTPUT':'TEMPORARY_OUTPUT'})
                self.landuseTalsim = resultDissolve['OUTPUT']
            except:
                self.landuseTalsim, _ = self.make_geometries_valid(self.landuseTalsim)
                resultDissolve = processing.run("native:dissolve", {'INPUT':self.landuseTalsim,'FIELD': self.selected_landuse_parameters,'SEPARATE_DISJOINT':True,'OUTPUT':'TEMPORARY_OUTPUT'})
                self.landuseTalsim = resultDissolve['OUTPUT']

            all_fields = [field.name() for field in self.landuseTalsim.fields()]
            fields_to_delete_indices = [self.landuseTalsim.fields().indexFromName(field)  for field in all_fields if field not in self.selected_landuse_parameters]
            self.landuseTalsim.startEditing()
            self.landuseTalsim.dataProvider().deleteAttributes(fields_to_delete_indices)
            self.landuseTalsim.commitChanges()
            self.landuseTalsim.updateFields()
            
            if self.landuseLayer:
                QgsProject.instance().removeMapLayer(self.landuseLayer)
                self.landuseLayer = None
            self.landuseTalsim.setName("Talsim Landuse")
            QgsProject.instance().addMapLayer(self.landuseTalsim)

            #Add checkmark when process is finished
            current_text = self.dlg.onCreateLanduseLayer.text()
            if "✓" not in current_text:  #Avoid duplicate checkmarks
                self.dlg.onCreateLanduseLayer.setText(f"{current_text} ✓")
                current_text_groupbox = self.dlg.landuseGroupBox.title()
                self.dlg.landuseGroupBox.setTitle(f"{current_text_groupbox} ✓")

            if self.soilTalsim:
                self.dlg.groupboxIntersect.setEnabled(True)
                self.dlg.groupboxIntersect.setChecked(True)

            self.log_to_qtalsim_tab(f"Created land use layer with Talsim Parameters: {self.landuseTalsim.name()}.", Qgis.Info)

        except Exception as e:
            self.log_to_qtalsim_tab(f"{e}", Qgis.Critical) 

        finally:
            self.end_operation()

    def deletePolygonsBelowThreshold(self, inputLayer, dissolve_list, id_field):
        '''
            Deletes Polygons that are below the user's thresholds.
        '''
        try:
            self.log_to_qtalsim_tab("Eliminating polygons below elimination thresholds...", Qgis.Info)
            all_fields = [field.name() for field in self.ezgLayer.fields()]
            fields_to_delete_indices = [self.ezgLayer.fields().indexFromName(field) for field in all_fields if field != self.ezgUniqueIdentifier]
            self.ezgLayer.startEditing()
            self.ezgLayer.dataProvider().deleteAttributes(fields_to_delete_indices)
            self.ezgLayer.commitChanges()
            self.ezgLayer.updateFields()

            '''
                Intersection
            '''
            try:
                intermediateResultIntersect = processing.run("native:intersection", {
                    'INPUT': inputLayer,
                    'OVERLAY': self.ezgLayer,
                    'OUTPUT': 'TEMPORARY_OUTPUT'
                })['OUTPUT']
            except:
                #QgsProject.instance().addMapLayer(inputLayer)
                inputLayer = processing.run("native:fixgeometries", {'INPUT': inputLayer,'OUTPUT': 'TEMPORARY_OUTPUT'}, feedback=None)['OUTPUT']
                intermediateResultIntersect = processing.run("native:intersection", {
                    'INPUT': inputLayer,
                    'OVERLAY': self.ezgLayer,
                    'OUTPUT': 'TEMPORARY_OUTPUT'
                })['OUTPUT']

            intermediateIntersectSingleparts = processing.run("native:multiparttosingleparts", {
                'INPUT': intermediateResultIntersect,
                'OUTPUT': 'TEMPORARY_OUTPUT'
            })['OUTPUT']

            #Calculate and store area of every catchment area
            ezgAreas = {}
            for feature in self.ezgLayer.getFeatures():
                ezgAreas[feature[self.ezgUniqueIdentifier]] = feature.geometry().area()

            #Dissolve Layer 1
            dissolve_list.append(self.ezgUniqueIdentifier)
            try:
                resultDissolve = processing.run("native:dissolve", {'INPUT': intermediateIntersectSingleparts,'FIELD': dissolve_list,'SEPARATE_DISJOINT':True,'OUTPUT':'TEMPORARY_OUTPUT'}, feedback = None)
                intersectedDissolvedLayer = resultDissolve['OUTPUT']
            except:
                intermediateIntersectSingleparts, _ = self.make_geometries_valid(intermediateIntersectSingleparts)
                resultDissolve = processing.run("native:dissolve", {'INPUT': intermediateIntersectSingleparts,'FIELD': dissolve_list,'SEPARATE_DISJOINT':True,'OUTPUT':'TEMPORARY_OUTPUT'}, feedback = None)
                intersectedDissolvedLayer = resultDissolve['OUTPUT']
            
            ezgDissolved = processing.run("native:dissolve", {'INPUT': self.ezgLayer,'FIELD': [],'SEPARATE_DISJOINT':True,'OUTPUT':'TEMPORARY_OUTPUT'}, feedback=None)['OUTPUT']
            
            try:
                intersectedDissolvedLayer = self.clipLayer(intersectedDissolvedLayer, ezgDissolved)            
            except:
                pass
            #Split the intersected areas and create own layer for each catchment area
                # --> necessary for eliminating: deleted areas (e.g. area too small) should only take the attributes of features in the same catchment area
            all_fields = [field.name() for field in intersectedDissolvedLayer.fields()]
            fields_to_delete_indices = [intersectedDissolvedLayer.fields().indexFromName(field)  for field in all_fields if field not in dissolve_list]
            intersectedDissolvedLayer.startEditing()
            intersectedDissolvedLayer.dataProvider().deleteAttributes(fields_to_delete_indices)
            intersectedDissolvedLayer.commitChanges()
            intersectedDissolvedLayer.updateFields()

            features_to_delete = []
            for feature in intersectedDissolvedLayer.getFeatures():
                if feature.geometry().isEmpty():
                    features_to_delete.append(feature.id())                    
            
            intersectedDissolvedLayer.startEditing()
            for feature_id in features_to_delete:
                intersectedDissolvedLayer.deleteFeature(feature_id)
            intersectedDissolvedLayer.commitChanges()

            field_index = intersectedDissolvedLayer.fields().indexFromName('fid')
            if field_index != -1:
                intersectedDissolvedLayer.startEditing()
                intersectedDissolvedLayer.renameAttribute(field_index, 'old_fid')
                intersectedDissolvedLayer.commitChanges()

            resultSplit = processing.run("native:splitvectorlayer", {
                    'INPUT': intersectedDissolvedLayer,
                    'FIELD': self.ezgUniqueIdentifier,
                    'PREFIX_FIELD': True,
                    'FILE_TYPE': 0,
                    'OUTPUT': 'TEMPORARY_OUTPUT'
                }, feedback=None)
            outputDirSplit = resultSplit['OUTPUT']
            
            #EFL-Dissolve-List
            eflFieldList = []
            eflFieldList.append(self.ezgUniqueIdentifier) #ID of catchment area
            if isinstance(id_field, list):
                #If id_field is a list, append each item to eflFieldList
                eflFieldList.extend(id_field)
            else:
                #If id_field is a single string, append it directly
                eflFieldList.append(id_field) #ID Soil/Landuse
            splitLayers = []

            total_features = len([name for name in os.listdir(outputDirSplit) if os.path.isfile(os.path.join(outputDirSplit, name))])
            last_logged_progress = 0
            analysed_features = 0
            for filename in os.listdir(outputDirSplit):
                #Logging Progress
                analysed_features += 1
                progress = (analysed_features/total_features)*100
                if progress - last_logged_progress >= 10:
                    self.log_to_qtalsim_tab(f"Progress: {progress:.2f}% done", Qgis.Info)
                    last_logged_progress = progress

                ids_to_eliminate = []
                file_path = os.path.join(outputDirSplit, filename)
                tempLayersplit = QgsVectorLayer(file_path, filename, 'ogr')
                
                #'Multipart to singleparts' necessary because 'mergevectorlayer' does not allow Geometry Collections
                if tempLayersplit.isValid():
                    tempLayersplit = processing.run("native:multiparttosingleparts", {
                        'INPUT': tempLayersplit,
                        'OUTPUT': 'memory:'
                    },feedback=None)['OUTPUT']
                fieldAreaEFL = QgsField(self.fieldNameAreaEFL, QVariant.Double)
                tempLayersplit.dataProvider().addAttributes([fieldAreaEFL]) #create new field
                tempLayersplit.updateFields()

                area_sums = defaultdict(float)
                ezg_values = {}
                #Select features to eliminate
                for feature in tempLayersplit.getFeatures():
                    attributes_key = tuple(feature[field] for field in eflFieldList)
                    ezg_values[attributes_key] = feature[self.ezgUniqueIdentifier]
                    # Sum the area for this group
                    area_sums[attributes_key] += feature.geometry().area()

                percentage_sums = {}
                # Get a representative feature to extract the ezg value
                for attributes_key, summed_area in area_sums.items():
                #This assumes all features in the same attributes_key group have the same ezg value
                    ezg = ezg_values[attributes_key]
                    ezgArea = ezgAreas[ezg]
                    percentage = (summed_area / ezgArea) * 100
                    percentage_sums[attributes_key] = percentage

                features_to_delete = []
                for feature in tempLayersplit.getFeatures():
                    attributes_key = tuple(feature[field] for field in eflFieldList)
                    area = area_sums[attributes_key]
                    percentage = percentage_sums[attributes_key]
                    if feature.geometry().isEmpty():
                        features_to_delete.append(feature.id())
                    else:   
                        if self.dlg.checkboxIntersectMinSizeArea.isChecked() and area < self.dlg.spinboxIntersectMinSizeArea.value(): # if area of feature < minimum accepted area specified by user
                            ids_to_eliminate.append(feature.id())
                        if self.dlg.checkboxIntersectShareofArea.isChecked() and feature.id() not in ids_to_eliminate: #if the percentage-chechbox is chosen
                            if percentage < self.dlg.spinboxIntersectShareofArea.value():
                                ids_to_eliminate.append(feature.id()) #eliminate
                        if area == 0 and feature.id() not in ids_to_eliminate: #also eliminate features with area = 0
                            ids_to_eliminate.append(feature.id())
                tempLayersplit.startEditing()
                for feature_id in features_to_delete:
                    tempLayersplit.deleteFeature(feature_id)
                tempLayersplit.commitChanges()
                #Eliminate with mode specified by user
                tempLayersplit.selectByIds(ids_to_eliminate)
                mode = 0 
                if self.dlg.comboboxEliminateModes.currentText() == 'Smallest Area':
                    mode = 1
                elif self.dlg.comboboxEliminateModes.currentText() == 'Largest Common Boundary':
                    mode = 2
                elif self.dlg.comboboxEliminateModes.currentText() == 'Largest Area':
                    mode = 0

                tempLayerSplitEliminated = processing.run("qgis:eliminateselectedpolygons", {'INPUT':tempLayersplit,'MODE':mode,'OUTPUT':'TEMPORARY_OUTPUT'}, feedback=None)['OUTPUT']
                tempLayerSplitEliminated = processing.run("native:multiparttosingleparts", {'INPUT': tempLayerSplitEliminated,'OUTPUT': 'TEMPORARY_OUTPUT'}, feedback=None)['OUTPUT']
                tempLayerSplitEliminated, _ = self.make_geometries_valid(tempLayerSplitEliminated)
                #tempLayerSplitEliminated = processing.run("native:fixgeometries", {'INPUT': tempLayerSplitEliminated,'OUTPUT': 'TEMPORARY_OUTPUT'}, feedback=None)['OUTPUT']
                splitLayers.append(tempLayerSplitEliminated)

            #Merge all of the split layers
            resultMerge = processing.run("native:mergevectorlayers", {'LAYERS':splitLayers,'CRS':intersectedDissolvedLayer.crs(),'OUTPUT':'TEMPORARY_OUTPUT'}, feedback=None)['OUTPUT']
            dissolve_list.remove(self.ezgUniqueIdentifier)
            return resultMerge
        except Exception as e:
            self.log_to_qtalsim_tab(f"{e}", Qgis.Critical) 
    
    def calculateSlopeHRUs(self, hruLayer):
        '''
            Calculates the slope for each polygon in hruLayer. 
                Triggered in performIntersect
        '''

        #Get DEM Layer
        selected_layer_name = self.dlg.comboboxDEMLayer.currentText()
        self.demLayer = QgsProject.instance().mapLayersByName(selected_layer_name)[0]
        
        #Calculate Slope Layer
        slope_layer_path = processing.run("native:slope", {'INPUT':self.demLayer, 'Z_FACTOR':1,'OUTPUT':'TEMPORARY_OUTPUT'})['OUTPUT']
        self.slopeLayer = QgsRasterLayer(slope_layer_path, 'Slope Layer')

        #Calculate the mean slope for each HRU
        statsLayer = processing.run("native:zonalstatisticsfb", {'INPUT':hruLayer,'INPUT_RASTER':self.slopeLayer,'RASTER_BAND':1,'COLUMN_PREFIX':'_','STATISTICS':[2],'OUTPUT':'TEMPORARY_OUTPUT'}).get('OUTPUT')

        #Add field 'Slope'
        statsLayer.startEditing()
        statsLayer.addAttribute(QgsField(self.slopeFieldName, QVariant.Double))
        statsLayer.commitChanges()

        statsLayer.startEditing()

        #Get the index of the '_mean' field and 'Slope' field
        mean_field_index = statsLayer.fields().indexOf('_mean') #field _mean created by zonalstatistics
        slope_field_index = statsLayer.fields().indexOf(self.slopeFieldName)

        #Copy the data from the '_mean' field to the 'slope' field
        for feature in statsLayer.getFeatures():
            statsLayer.changeAttributeValue(feature.id(), slope_field_index, feature[mean_field_index])

        #Remove the old '_mean' field that was created by "zonalstatisticsfb"
        statsLayer.deleteAttribute(mean_field_index)
        statsLayer.commitChanges()

        return statsLayer

    def performIntersect(self):
        '''
            Performs intersection of the three input layers after processing them in the previous steps.
        '''
        print(f"starting performIntersect")
        try:
            print(f"starting performIntersect")
            self.start_operation()
            if self.ezgLayer is None:
                self.log_to_qtalsim_tab("Sub-basins Layer does not exist.", Qgis.Critical)
                raise Exception("Sub-basins Layer does not exist.")
            elif self.landuseTalsim is None:
                self.log_to_qtalsim_tab("Land use Talsim Layer does not exist.", Qgis.Critical)
                raise Exception("Land use Talsim Layer does not exist.")
            elif self.soilTalsim is None:
                self.log_to_qtalsim_tab("Soil Talsim Layer does not exist.", Qgis.Critical)
                raise Exception("Soil Talsim Layer does not exist.")
            else:
                self.log_to_qtalsim_tab(f"Starting the intersecting process of layers: {self.ezgLayer.name()}, {self.landuseTalsim.name()} and {self.soilTalsim.name()}.", Qgis.Info)

            all_fields = [field.name() for field in self.ezgLayer.fields()]
            fields_to_delete_indices = [self.ezgLayer.fields().indexFromName(field) for field in all_fields if field != self.ezgUniqueIdentifier]
            self.ezgLayer.startEditing()
            self.ezgLayer.dataProvider().deleteAttributes(fields_to_delete_indices)
            self.ezgLayer.commitChanges()
            self.ezgLayer.updateFields()
            try:
                #Create a copy of the sub-basins layer to not edit the input layer
                ezgLayer1 = QgsVectorLayer(f"Polygon?crs={self.ezgLayer.crs().authid()}", "EZG", "memory")
                feats = [feat for feat in self.ezgLayer.getFeatures()]

                mem_layer_data = ezgLayer1.dataProvider()
                attr = self.ezgLayer.dataProvider().fields().toList()
                mem_layer_data.addAttributes(attr)
                ezgLayer1.updateFields()
                mem_layer_data.addFeatures(feats)
            except Exception as e:
                self.log_to_qtalsim_tab(f"{e}", Qgis.Critical)

            '''
                Intersection
            '''
            intermediateResultIntersect = processing.run("native:intersection", {
                'INPUT': self.landuseTalsim,
                'OVERLAY': ezgLayer1,
                'OUTPUT': 'TEMPORARY_OUTPUT'
            })['OUTPUT']
            
            intermediateIntersectSingleparts = processing.run("native:multiparttosingleparts", {
                'INPUT': intermediateResultIntersect,
                'OUTPUT': 'TEMPORARY_OUTPUT'
            })['OUTPUT']
            
            intersectedLayer = processing.run("native:intersection", {
                'INPUT': intermediateIntersectSingleparts,
                'OVERLAY': self.soilTalsim,
                'OUTPUT': 'TEMPORARY_OUTPUT'
            })['OUTPUT']

            #Calculate and store area of every catchment area
            intersectedLayer, _ = self.make_geometries_valid(intersectedLayer)
            #self.dlg.progressbar.setValue(10)
            self.log_to_qtalsim_tab(f"Progress: 10.00% done", Qgis.Info)
            #Get the area of each sub-basin
            ezgAreas = {}
            for feature in ezgLayer1.getFeatures():
                ezgAreas[feature[self.ezgUniqueIdentifier]] = feature.geometry().area()

            #Dissolve intersected layer by sub-basin's, soil's and land use's parameters
            dissolve_list = []
            dissolve_list.append(self.ezgUniqueIdentifier)
            dissolve_list.extend(self.selected_landuse_parameters)
            dissolve_list.extend(self.soilFieldNames)
            print(dissolve_list)
            resultDissolve = processing.run("native:dissolve", {'INPUT': intersectedLayer,'FIELD': dissolve_list,'SEPARATE_DISJOINT':True,'OUTPUT':'TEMPORARY_OUTPUT'}, feedback = None)
            intersectedDissolvedLayer = resultDissolve['OUTPUT']
            
            #Intersecting layers can result in further overlaps/gaps --> fill gaps, edit overlaps 
            intersectedDissolvedLayerFilledGaps = self.fillGaps(intersectedDissolvedLayer, self.clippingEZG, 0)
            ezgDissolved = processing.run("native:dissolve", {'INPUT': self.ezgLayer,'FIELD': [],'SEPARATE_DISJOINT':True,'OUTPUT':'TEMPORARY_OUTPUT'}, feedback=None)['OUTPUT']
            
            self.log_to_qtalsim_tab(f"Progress: 15.00% done", Qgis.Info)
            self.log_to_qtalsim_tab("Deleting overlapping features...", Qgis.Info)
            try:
                intersectedDissolvedLayer = self.clipLayer(intersectedDissolvedLayerFilledGaps, ezgDissolved) #necessary because also wanted gaps (of sub-basins-layer) are filled when performing 'Fill Gaps'
            except:
                pass
                
            intersectedDissolvedLayer, _ = self.editOverlappingFeatures(intersectedDissolvedLayer)
        
            all_fields = [field.name() for field in intersectedDissolvedLayer.fields()]
            fields_to_delete_indices = [intersectedDissolvedLayer.fields().indexFromName(field) for field in all_fields if field not in dissolve_list]
            intersectedDissolvedLayer.startEditing()
            intersectedDissolvedLayer.dataProvider().deleteAttributes(fields_to_delete_indices)
            intersectedDissolvedLayer.commitChanges()
            intersectedDissolvedLayer.updateFields()
            #Features with no geometry or NULL-values may lead to errors: delete those features
            features_to_delete = []
            for feature in intersectedDissolvedLayer.getFeatures():
                if feature.geometry().isEmpty() or (str(feature[self.fieldLanduseID]).strip().upper() == 'NULL' and str(feature[self.soilIDNames[0]]).strip().upper() == 'NULL' and str(feature[self.ezgUniqueIdentifier]).strip().upper() == 'NULL'):
                    features_to_delete.append(feature.id())                    
            
            intersectedDissolvedLayer.startEditing()
            for feature_id in features_to_delete:
                intersectedDissolvedLayer.deleteFeature(feature_id)
            intersectedDissolvedLayer.commitChanges()
            #QgsProject.instance().addMapLayer(intersectedDissolvedLayer)
            #Split the intersected areas and create own layer for each catchment area
                # --> necessary for eliminating: deleted areas (e.g. area too small) should only take the attributes of features in the same catchment area
            resultSplit = processing.run("native:splitvectorlayer", {
                    'INPUT': intersectedDissolvedLayer,
                    'FIELD': self.ezgUniqueIdentifier,
                    'PREFIX_FIELD': True,
                    'FILE_TYPE': 0,
                    'OUTPUT': 'TEMPORARY_OUTPUT'
                }, feedback=None)
            outputDirSplit = resultSplit['OUTPUT']
            #self.dlg.progressbar.setValue(20)
            self.log_to_qtalsim_tab(f"Progress: 20.00% done", Qgis.Info)
            #Logging variables:
            if 'memory:' in outputDirSplit:  # If using in-memory output
                count_all_layers = len([layer for layer in QgsProject.instance().mapLayers().values() if layer.name().startswith(self.ezgUniqueIdentifier)])
            else:  # If using a directory output
                count_all_layers = len([name for name in os.listdir(outputDirSplit) if os.path.isfile(os.path.join(outputDirSplit, name))])
            last_logged_progress = 0
            analysed_features = 0

            #EFL-Dissolve-List
            eflFieldList = []
            eflFieldList.append(self.ezgUniqueIdentifier) #ID of catchment area
            eflFieldList.extend(self.soilIDNames) #ID Soil
            eflFieldList.append(self.fieldLanduseID) #ID LNZ
            splitLayers = []

            self.log_to_qtalsim_tab("Eliminating polygons below elimination thresholds...", Qgis.Info)

            #Loop over all sub-basins to eliminate polygons
            for filename in os.listdir(outputDirSplit):
                
                #Logging the process
                analysed_features += 1
                progress = (analysed_features/count_all_layers)*100
                if progress - last_logged_progress >= 10:
                    self.log_to_qtalsim_tab(f"Progress: {progress:.2f}% done", Qgis.Info)
                    last_logged_progress = progress

                ids_to_eliminate = []
                file_path = os.path.join(outputDirSplit, filename)
                tempLayersplit = QgsVectorLayer(file_path, filename, 'ogr')
                
                #Clean the geometries before performing multipart to singlepart
                tempLayersplit, _ = self.make_geometries_valid(tempLayersplit)

                #'Multipart to singleparts' necessary because 'mergevectorlayer' does not allow Geometry Collections
                if tempLayersplit.isValid():
                    tempLayersplit = processing.run("native:multiparttosingleparts", {
                        'INPUT': tempLayersplit,
                        'OUTPUT': 'memory:'
                    },feedback=None)['OUTPUT']
                
                fieldAreaEFL = QgsField(self.fieldNameAreaEFL, QVariant.Double)
                tempLayersplit.dataProvider().addAttributes([fieldAreaEFL]) #create new field
                tempLayersplit.updateFields()

                area_sums = defaultdict(float)
                ezg_values = {}
                #Select features to eliminate
                for feature in tempLayersplit.getFeatures():
                    attributes_key = tuple(feature[field] for field in eflFieldList)
                    ezg_values[attributes_key] = feature[self.ezgUniqueIdentifier]
                    # Sum the area for this group
                    area_sums[attributes_key] += feature.geometry().area()

                percentage_sums = {}
                # Get a representative feature to extract the ezg value
                for attributes_key, summed_area in area_sums.items():
                    ezg = ezg_values[attributes_key]
                    ezgArea = ezgAreas[ezg]
                    percentage = (summed_area / ezgArea) * 100
                    percentage_sums[attributes_key] = percentage

                features_to_delete = [] #features without/empty geometries are deleted
                for feature in tempLayersplit.getFeatures():
                    attributes_key = tuple(feature[field] for field in eflFieldList)
                    area = area_sums[attributes_key]
                    percentage = percentage_sums[attributes_key]
                    if feature.geometry().isEmpty() or feature.geometry() is None or feature.geometry().area() == 0:
                        features_to_delete.append(feature.id())
                    else:   
                        if self.dlg.checkboxIntersectMinSizeArea.isChecked() and area < self.dlg.spinboxIntersectMinSizeArea.value(): # if area of feature < minimum accepted area specified by user
                            ids_to_eliminate.append(feature.id())
                        if self.dlg.checkboxIntersectShareofArea.isChecked() and feature.id() not in ids_to_eliminate: #if the percentage-chechbox is chosen
                            if percentage < self.dlg.spinboxIntersectShareofArea.value():
                                ids_to_eliminate.append(feature.id()) #eliminate
                        if area == 0 and feature.id() not in ids_to_eliminate: #also eliminate features with area = 0
                            ids_to_eliminate.append(feature.id())

                tempLayersplit.startEditing()
                for feature_id in features_to_delete:
                    tempLayersplit.deleteFeature(feature_id)
                tempLayersplit.commitChanges()

                #Eliminate with mode specified by user
                tempLayersplit.selectByIds(ids_to_eliminate)
                mode = 0 
                if self.dlg.comboboxEliminateModes.currentText() == 'Smallest Area':
                    mode = 1
                elif self.dlg.comboboxEliminateModes.currentText() == 'Largest Common Boundary':
                    mode = 2
                elif self.dlg.comboboxEliminateModes.currentText() == 'Largest Area':
                    mode = 0
                tempLayerSplitEliminated = processing.run("qgis:eliminateselectedpolygons", {'INPUT':tempLayersplit,'MODE':mode,'OUTPUT':'TEMPORARY_OUTPUT'}, feedback=None)['OUTPUT']
                tempLayerSplitEliminated = processing.run("native:multiparttosingleparts", {'INPUT': tempLayerSplitEliminated,'OUTPUT': 'TEMPORARY_OUTPUT'}, feedback=None)['OUTPUT']
                #tempLayerSplitEliminated = processing.run("native:fixgeometries", {'INPUT': tempLayerSplitEliminated,'OUTPUT': 'TEMPORARY_OUTPUT'}, feedback=None)['OUTPUT']
                tempLayerSplitEliminated, _ = self.make_geometries_valid(tempLayerSplitEliminated)
                splitLayers.append(tempLayerSplitEliminated)
            #self.dlg.progressbar.setValue(30)
            self.log_to_qtalsim_tab(f"Progress: 30.00% done", Qgis.Info)
            #Merge all of the split layers
            resultMerge = processing.run("native:mergevectorlayers", {'LAYERS':splitLayers,'CRS':intersectedDissolvedLayer.crs(),'OUTPUT':'TEMPORARY_OUTPUT'}, feedback=None)['OUTPUT']
            #QgsProject.instance().addMapLayer(resultMerge)
            #Check if 
            invalid_features = False
            resultMerge, invalid_features = self.make_geometries_valid(resultMerge)
            if invalid_features:
                original_features = {feature.id(): feature for feature in resultMerge.getFeatures()}
                resultMerge = processing.run("native:fixgeometries", {'INPUT': resultMerge, 'METHOD': 1, 'OUTPUT': 'TEMPORARY_OUTPUT'}, feedback=None).get('OUTPUT')
                fixed_features = {feature.id(): feature for feature in resultMerge.getFeatures()}
                deleted_features = set(original_features.keys()) - set(fixed_features.keys())

            #Dissolve Layer 2 
            try:
                resultDissolve = processing.run("native:dissolve", {'INPUT': resultMerge,'FIELD': dissolve_list,'SEPARATE_DISJOINT':True,'OUTPUT':'TEMPORARY_OUTPUT'}, feedback=None)
                self.finalLayer = resultDissolve['OUTPUT']
            except:
                resultMerge, _ = self.make_geometries_valid(resultMerge)
                self.finalLayer = processing.run("native:dissolve", {'INPUT': resultMerge,'FIELD': dissolve_list,'SEPARATE_DISJOINT':True,'OUTPUT':'TEMPORARY_OUTPUT'}, feedback=None)['OUTPUT']  
            #QgsProject.instance().addMapLayer(self.finalLayer)
            self.log_to_qtalsim_tab("Deleting overlapping features...", Qgis.Info)
            try:
                self.finalLayerAfterGaps = self.fillGaps(self.finalLayer, self.clippingEZG, 0)
                self.finalLayerAfterGaps, _ = self.make_geometries_valid(self.finalLayerAfterGaps)
                self.finalLayerClipped = self.clipLayer(self.finalLayerAfterGaps, ezgDissolved)
                self.finalLayer, _ = self.editOverlappingFeatures(self.finalLayerClipped)
            except Exception as e:
                self.log_to_qtalsim_tab(f"Operation did not work due to too complex features or other issues: {e}", Qgis.Warning)
            
            #Delete features without geometry
            features_to_delete = []
            for feature in self.finalLayer.getFeatures():
                # Check if all attribute values are 'NULL'
                if feature.geometry().isEmpty() or feature.geometry() is None or feature.geometry().area() == 0 or (str(feature[self.fieldLanduseID]).strip().upper() == 'NULL' and str(feature[self.soilIDNames[0]]).strip().upper() == 'NULL' and str(feature[self.ezgUniqueIdentifier]).strip().upper() == 'NULL'):
                    features_to_delete.append(feature.id())
            if len(features_to_delete) > 0:
                self.log_to_qtalsim_tab(f"{len(features_to_delete)} features are deleted as they are empty polygons. ", Qgis.Info)

            self.finalLayer.startEditing()
            for feature_id in features_to_delete:
                self.finalLayer.deleteFeature(feature_id)
            self.finalLayer.commitChanges()

            self.finalLayer.setName("Talsim Layer")
            QgsProject.instance().addMapLayer(self.finalLayer) #Add final layer to inspect results
            
            #Delete unwanted fields
            self.finalLayer.startEditing()
            dissolve_fields_indices = [self.finalLayer.fields().indexFromName(field) for field in dissolve_list]
            for i in range(self.finalLayer.fields().count() - 1, -1, -1):
                if i not in dissolve_fields_indices:
                    self.finalLayer.deleteAttribute(i)
            self.finalLayer.commitChanges()
            #self.dlg.progressbar.setValue(40)
            self.log_to_qtalsim_tab(f"Progress: 40.00% done", Qgis.Info)

            '''
                Create .LNZ
            '''
            fields_to_remove = [self.ezgUniqueIdentifier]
            for field in fields_to_remove:
                if field in self.selected_landuse_parameters:
                    self.selected_landuse_parameters.remove(field)
            try:
                resultDissolve = processing.run("native:dissolve", {'INPUT': self.finalLayer,'FIELD': self.selected_landuse_parameters,'SEPARATE_DISJOINT':False,'OUTPUT':'TEMPORARY_OUTPUT'}, feedback=None)
                self.landuseFinal = resultDissolve['OUTPUT']
            except:
                self.landuseFinal = processing.run("native:multiparttosingleparts", {'INPUT': self.finalLayer,'OUTPUT': 'TEMPORARY_OUTPUT'}, feedback=None)['OUTPUT']
                self.landuseFinal, _ = self.make_geometries_valid(self.landuseFinal)
                self.landuseFinal = processing.run("native:dissolve", {'INPUT': self.landuseFinal,'FIELD': self.selected_landuse_parameters,'SEPARATE_DISJOINT':True,'OUTPUT':'TEMPORARY_OUTPUT'}, feedback=None)['OUTPUT']
                self.landuseFinal = processing.run("native:dissolve", {'INPUT': self.landuseFinal,'FIELD': self.selected_landuse_parameters,'SEPARATE_DISJOINT':False,'OUTPUT':'TEMPORARY_OUTPUT'}, feedback=None)['OUTPUT']

            self.landuseFinal.startEditing()
            dissolve_fields_indices = [self.landuseFinal.fields().indexFromName(field) for field in self.selected_landuse_parameters]
            for i in range(self.landuseFinal.fields().count() - 1, -1, -1):
                if i not in dissolve_fields_indices:
                    self.landuseFinal.deleteAttribute(i)
            self.landuseFinal.commitChanges()
            self.landuseFinal.setName("LNZ")

            field_index = self.landuseFinal.fields().indexFromName(self.fieldLanduseID)
            self.landuseFinal.startEditing()
            self.landuseFinal.renameAttribute(field_index, 'Id')
            self.landuseFinal.commitChanges()

            QgsProject.instance().addMapLayer(self.landuseFinal) 
            #self.dlg.progressbar.setValue(50)
            self.log_to_qtalsim_tab(f"Progress: 50.00% done", Qgis.Info)

            '''
                Create .BOA - SoilTexture
            '''

            new_fields = QgsFields()
            
            #Get the field names of the first soil layer in the final layer
            for field in self.finalLayer.fields():
                if field.name().endswith(f"soillayer1"):  #Ends with 'soillayer1', but this will handle all similar layers
                    #Remove the 'soillayer1' suffix to create the new field name
                    base_field_name = field.name().rsplit('_soillayer', 1)[0]  #Remove the suffix like '_soillayer1'
                    if base_field_name != self.soilTypeThickness:
                        new_fields.append(QgsField(base_field_name, field.type()))

            #Create the final soil texture layer
            crs = self.finalLayer.crs()
            self.soilTextureFinal = QgsVectorLayer(f"Polygon?crs={crs}", "BOA", "memory", crs=crs)
            soil_texture_data_provider = self.soilTextureFinal.dataProvider()
            soil_texture_data_provider.addAttributes(new_fields)
            self.soilTextureFinal.updateFields()

            unique_combinations = set()
            unique_feature_index = {}
            geometries = {}
            j = 1
            for feature in self.finalLayer.getFeatures():
                for i in range(1, self.number_soilLayers + 1):
                    combination = tuple(feature[f"{field.name()}_soillayer{i}"] for field in new_fields if field.name() != self.IDSoil) #combination without id (id for each combination of soil layers)
                    if str(feature[f"{self.nameSoil}_soillayer{i}"]).strip().upper() != 'NULL' and str(feature[f"{self.nameSoil}_soillayer{i}"]).strip().upper() != '':
                        if combination not in unique_combinations: 
                            unique_combinations.add(combination)
                            #Create new feature for each soil layer
                            new_feature = QgsFeature(self.soilTextureFinal.fields())
                            for field in new_fields:
                                if field.name() != self.IDSoil:
                                    #Construct the field name for this soil layer (e.g., 'soil_layer1_soilid')
                                    original_field_name = f"{field.name()}_soillayer{i}"

                                    #Add the corresponding value to the new feature if the field exists
                                    if self.finalLayer.fields().indexOf(original_field_name) != -1:
                                        new_feature.setAttribute(field.name(), feature[original_field_name])
                            new_feature.setAttribute(self.IDSoil, j)
                            j += 1
                            #Add the new feature (row) to the new layer
                            soil_texture_data_provider.addFeature(new_feature)
                            unique_feature_index[combination] = new_feature['ID_Soil'] #new_feature.id()
                        if combination not in geometries:
                            geometries[combination] = [feature.geometry()]  #Start with a list containing this feature's geometry
                        else:
                            geometries[combination].append(feature.geometry())
            self.soilTextureFinal.commitChanges()
            self.soilTextureFinal.startEditing()
            #Add the geomtries to the layer
            for combination, geom_list in geometries.items():
                #Combine geometries for this combination
                combined_geometry = QgsGeometry.unaryUnion(geom_list)
                
                #Find the corresponding feature based on the combination
                for feature in self.soilTextureFinal.getFeatures():
                    #Compare with unique_feature_index to find the corresponding feature
                    if feature['ID_Soil'] == unique_feature_index[combination]:
                        #Set the combined geometry to the feature
                        self.soilTextureFinal.startEditing()
                        feature.setGeometry(combined_geometry)
                        self.soilTextureFinal.updateFeature(feature)  
            

            self.soilTextureFinal.commitChanges()  
            QgsProject.instance().addMapLayer(self.soilTextureFinal)
            #self.dlg.progressbar.setValue(65)
            self.log_to_qtalsim_tab(f"Progress: 65.00% done", Qgis.Info)

            '''
                BOD
            '''
            bod_fields = QgsFields()

            #Create reference fields to soil textures
            bod_fields.append(QgsField(f"ID", QVariant.Int))
            for i in range(1, self.number_soilLayers + 1):
                bod_fields.append(QgsField(f"soillayer{i}_id_boa", QVariant.Int))  #Reference to the soil_id in the first layer
                bod_fields.append(QgsField(f"soillayer{i}_{self.soilTypeThickness}", QVariant.Double)) #layer thickness for every soil layer
            bod_fields.append(QgsField("Description", QVariant.String)) #Add description field only once
            self.soilTypeFinal = QgsVectorLayer(f"Polygon?crs={crs}", "BOD", "memory", crs=crs)
            bod_data_provider = self.soilTypeFinal.dataProvider()
            bod_data_provider.addAttributes(bod_fields)
            self.soilTypeFinal.updateFields()
            
            for feature in self.finalLayer.getFeatures():
                new_feature = QgsFeature(self.soilTypeFinal.fields())
                geometry = feature.geometry()
                new_feature.setGeometry(geometry)
                
                descriptionSoilLayers = str()
                descriptionSoilLayersList = []
                for i in range(1, self.number_soilLayers + 1):
                    if str(feature[f'ID_Soil_soillayer{i}']).strip().upper() != 'NULL':
                        combination = tuple(feature[f"{field.name()}_soillayer{i}"] for field in new_fields if field.name() != self.IDSoil)
                        
                        if combination in unique_feature_index:
                            #Find the index of the corresponding feature in the new_layer and set the reference
                            new_feature.setAttribute(f"soillayer{i}_id_boa", unique_feature_index[combination])
                            new_feature.setAttribute(f"soillayer{i}_{self.soilTypeThickness}", feature[f'{self.soilTypeThickness}_soillayer{i}'])
                        if str(feature[f'{self.soilDescription}_soillayer{i}']).strip().upper() != 'NULL':
                            value = feature[f'{self.soilDescription}_soillayer{i}']
                            if value:
                                descriptionSoilLayersList.append(str(value))
                                
                if isinstance(descriptionSoilLayersList, (list, tuple)):
                    descriptionSoilLayers = ', '.join(map(str, descriptionSoilLayersList))
                else:
                    descriptionSoilLayers = str(descriptionSoilLayersList)

                new_feature.setAttribute(self.soilDescription, descriptionSoilLayers) #take the combination of all soil layer's description
                bod_data_provider.addFeature(new_feature)

            bod_field_names = [field.name() for field in bod_fields]

            try:
                self.soilTypeFinal = processing.run("native:dissolve", {'INPUT': self.soilTypeFinal,'FIELD': bod_field_names, 'SEPARATE_DISJOINT':False,'OUTPUT':'TEMPORARY_OUTPUT'}, feedback=None)['OUTPUT']
            except:
                self.soilTypeFinal = processing.run("native:multiparttosingleparts", {'INPUT': self.soilTypeFinal,'OUTPUT': 'TEMPORARY_OUTPUT'}, feedback=None)['OUTPUT']
                self.soilTypeFinal, _ = self.make_geometries_valid(self.soilTypeFinal)

                self.soilTypeFinal = processing.run("native:dissolve", {'INPUT': self.soilTypeFinal,'FIELD': bod_field_names, 'SEPARATE_DISJOINT':True,'OUTPUT':'TEMPORARY_OUTPUT'}, feedback=None)['OUTPUT']
                self.soilTypeFinal = processing.run("native:dissolve", {'INPUT': self.soilTypeFinal,'FIELD': bod_field_names, 'SEPARATE_DISJOINT':False,'OUTPUT':'TEMPORARY_OUTPUT'}, feedback=None)['OUTPUT']

            #Fill ID-column with the (internal) feature-id
            self.soilTypeFinal.startEditing()
            #Iterate through each feature in the dissolved layer and assign a new unique ID
            for feature in self.soilTypeFinal.getFeatures():
                feature_id = feature.id() 
                feature.setAttribute("ID", feature_id)  
                #Update the feature in the layer
                self.soilTypeFinal.updateFeature(feature)
            self.soilTypeFinal.commitChanges()

            self.soilTypeFinal.setName("BOD")
            QgsProject.instance().addMapLayer(self.soilTypeFinal)
            #self.dlg.progressbar.setValue(75)
            self.log_to_qtalsim_tab(f"Progress: 75.00% done", Qgis.Info)

            '''
                Create .EFL
            '''
            #Join the soil id (BOD) to the EFL-layer
            self.finalLayer = processing.run("native:multiparttosingleparts", {'INPUT': self.finalLayer,'OUTPUT': 'TEMPORARY_OUTPUT'}, feedback=None)['OUTPUT']
            self.finalLayer, _ = self.make_geometries_valid(self.finalLayer)
            soilTypeFinalSingleParts = processing.run("native:multiparttosingleparts", {'INPUT': self.soilTypeFinal,'OUTPUT': 'TEMPORARY_OUTPUT'}, feedback=None)['OUTPUT']
            processing.run("native:createspatialindex", {'INPUT': self.finalLayer})
            processing.run("native:createspatialindex", {'INPUT': soilTypeFinalSingleParts})
            
            try:
                #Define the parameters for the spatial join
                params = {
                    'INPUT': self.finalLayer,  
                    'JOIN': soilTypeFinalSingleParts,  
                    'PREDICATE': [0],  
                    'JOIN_FIELDS': ['ID'],  
                    'METHOD': 2,  
                    'DISCARD_NONMATCHING': False,  
                    'OUTPUT': 'memory:'  
                }

                #Run the spatial join
                result = processing.run('native:joinattributesbylocation', params)
            except:
                soilTypeFinalSingleParts, _ = self.make_geometries_valid(soilTypeFinalSingleParts)
                self.finalLayer, _ = self.make_geometries_valid(self.finalLayer)
                #Define the parameters for the spatial join
                params = {
                    'INPUT': self.finalLayer,
                    'JOIN': soilTypeFinalSingleParts,  
                    'PREDICATE': [0],  
                    'JOIN_FIELDS': ['ID'],  
                    'METHOD': 2,  
                    'DISCARD_NONMATCHING': False,  
                    'OUTPUT': 'memory:'  
                }

                #Run the spatial join
                result = processing.run('native:joinattributesbylocation', params)
            
            #Get the resulting layer (joined output)
            self.finalLayer = result['OUTPUT']

            #Now rename the joined 'ID' column to the desired self.hruSoilTypeId
            self.finalLayer.startEditing()
            for field in self.finalLayer.fields():
                if field.name() == 'ID': 
                    self.finalLayer.renameAttribute(self.finalLayer.fields().indexOf('ID'), self.hruSoilTypeId)  #Rename it
            self.finalLayer.commitChanges()
            
            #Delete features without geometry
            features_to_delete = []
            for feature in self.finalLayer.getFeatures():
                #Check if all attribute values are 'NULL'
                if feature.geometry().isEmpty() or feature.geometry() is None or feature.geometry().area() == 0 or (str(feature[self.fieldLanduseID]).strip().upper() == 'NULL' or str(feature[self.hruSoilTypeId]).strip().upper() == 'NULL' or str(feature[self.ezgUniqueIdentifier]).strip().upper() == 'NULL'):
                    features_to_delete.append(feature.id())
            if len(features_to_delete) > 0:
                self.log_to_qtalsim_tab(f"{len(features_to_delete)} features are deleted as they are empty polygons. ", Qgis.Info)

            self.finalLayer.startEditing()
            for feature_id in features_to_delete:
                self.finalLayer.deleteFeature(feature_id)
            self.finalLayer.commitChanges()

            #Log catchment areas where size of all HRUs != size of catchment area
            sum_areas = {key: 0 for key in ezgAreas.keys()}
            for feature in self.finalLayer.getFeatures():
                area = feature.geometry().area()
                ezg = feature[self.ezgUniqueIdentifier]
                sum_areas[ezg] += area

            for key in ezgAreas:
                if key in sum_areas:
                    if round(ezgAreas[key], -2) != round(sum_areas[key], -2):
                        self.log_to_qtalsim_tab(f'Sub-basin with Unique-Identifier {key} has a different area {ezgAreas[key]} than the sum of all features in this sub-basin {sum_areas[key]}.', Qgis.Warning)
            #self.dlg.progressbar.setValue(85)
            self.log_to_qtalsim_tab(f"Progress: 85.00% done", Qgis.Info)
            eflFieldList.append(self.hruSoilTypeId)

            eflFieldList = [item for item in eflFieldList if item not in self.soilIDNames]
            try:
                #Dissolve the final Layer
                self.eflLayer = processing.run("native:dissolve", {'INPUT': self.finalLayer,'FIELD': eflFieldList, 'SEPARATE_DISJOINT':False,'OUTPUT':'TEMPORARY_OUTPUT'}, feedback=None)['OUTPUT']
            except:
                #Complex geometries can lead to errors when dissolving - cleaning the geometries might be necessary
                self.finalLayer = processing.run("native:multiparttosingleparts", {'INPUT': self.finalLayer,'OUTPUT': 'TEMPORARY_OUTPUT'}, feedback=None)['OUTPUT']
                self.finalLayer, _ = self.make_geometries_valid(self.finalLayer)

                self.eflLayer = processing.run("native:dissolve", {'INPUT': self.finalLayer,'FIELD': eflFieldList, 'SEPARATE_DISJOINT':True,'OUTPUT':'TEMPORARY_OUTPUT'}, feedback=None)['OUTPUT']
                self.eflLayer = processing.run("native:dissolve", {'INPUT': self.eflLayer,'FIELD': eflFieldList, 'SEPARATE_DISJOINT':False,'OUTPUT':'TEMPORARY_OUTPUT'}, feedback=None)['OUTPUT']

            #Add Fields
            if self.dlg.comboboxDEMLayer.currentText() != "Optional: Upload DEM Layer":
                self.log_to_qtalsim_tab(f"Calculating Slope...", Qgis.Info)
                self.eflLayer = self.calculateSlopeHRUs(self.eflLayer)    
            else:
                self.eflLayer.startEditing()
                #Add the new field 'slope'
                self.eflLayer.addAttribute(QgsField(self.slopeFieldName, QVariant.Double))
                self.eflLayer.commitChanges()

            eflFieldList.append(self.slopeFieldName)
            eflLayerDP = self.eflLayer.dataProvider()
            self.eflLayer.startEditing()
            eflLayerDP.addAttributes([QgsField(self.fieldNameAreaEFL, QVariant.Double)])
            self.eflLayer.commitChanges()
            self.eflLayer.updateFields()
            #self.dlg.progressbar.setValue(90)
            self.log_to_qtalsim_tab(f"Progress: 90.00% done", Qgis.Info)
            #Add data
            self.eflLayer.startEditing()
            features_to_delete = []
            for feature in self.eflLayer.getFeatures():
                area = feature.geometry().area()
                ezg = feature[self.ezgUniqueIdentifier]
                ezgArea = ezgAreas[ezg]
                percentage = (area/ezgArea)*100
                feature[self.fieldNameAreaEFL] = percentage
                if percentage < 0.001: #delete features with area < 0.001%
                    features_to_delete.append(feature.id())
                    continue  
                self.eflLayer.updateFeature(feature)
                if self.dlg.checkboxIntersectMinSizeArea.isChecked() and area < self.dlg.spinboxIntersectMinSizeArea.value(): # if area of feature < minimum accepted area specified by user
                    self.log_to_qtalsim_tab(f"Feature {feature.id()} is not deleted, eventhough it's area is below {self.dlg.spinboxIntersectMinSizeArea.value()} m².", Qgis.Warning)
                if self.dlg.checkboxIntersectShareofArea.isChecked() and percentage < self.dlg.spinboxIntersectShareofArea.value(): #if the percentage-chechbox is chosen
                    self.log_to_qtalsim_tab(f"Feature {feature.id()} is not deleted, eventhough it's percentage is below {self.dlg.spinboxIntersectShareofArea.value()} %.", Qgis.Warning)
            
            for fid in features_to_delete: #delete features with area < 0.001%
                self.eflLayer.deleteFeature(fid)

            self.eflLayer.commitChanges()
            #self.dlg.progressbar.setValue(95)
            self.log_to_qtalsim_tab(f"Progress: 95.00% done", Qgis.Info)
            eflFieldList.append(self.fieldNameAreaEFL) #Area of Elementarfläche
            self.eflLayer.startEditing()
            dissolve_fields_indices = [self.eflLayer.fields().indexFromName(field) for field in eflFieldList]
            for i in range(self.eflLayer.fields().count() - 1, -1, -1):
                if i not in dissolve_fields_indices:
                    self.eflLayer.deleteAttribute(i)
            self.eflLayer.commitChanges()

            #Rename the fieldnames  
            self.eflLayer.startEditing()
            field_index = self.eflLayer.fields().indexFromName(self.fieldLanduseID)
            self.eflLayer.renameAttribute(field_index, self.hruLandUseId)
            field_index = self.eflLayer.fields().indexFromName(self.ezgUniqueIdentifier)
            self.eflLayer.renameAttribute(field_index, self.subBasinUI)
            self.eflLayer.commitChanges()

            #Add the updated eflLayer to the map
            self.eflLayer.setName("EFL")
            QgsProject.instance().addMapLayer(self.eflLayer)
            #self.dlg.progressbar.setValue(0)
            self.log_to_qtalsim_tab(f"Progress: 100.00% done", Qgis.Info)

            self.dlg.finalButtonBox.button(QDialogButtonBox.Save).setEnabled(True)
            #Add checkmark when process is finished
            current_text = self.dlg.onPerformIntersect.text()
            if "✓" not in current_text:  #Avoid duplicate checkmarks
                self.dlg.onPerformIntersect.setText(f"{current_text} ✓")
                current_text_groupbox = self.dlg.groupboxIntersect.title()
                self.dlg.groupboxIntersect.setTitle(f"{current_text_groupbox} ✓")


            self.log_to_qtalsim_tab(f"Finished intersection of layers.", Qgis.Info)
        
        except Exception as e:
            print(f"Error: {e}")
            self.log_to_qtalsim_tab(f"{e}", Qgis.Critical) 
            #self.dlg.progressbar.setValue(0)
        finally:
            self.end_operation()

    def selectOutputFolder(self):
        '''
            Function to select the output folder. 
        ''' 
        self.outputFolder = None
        self.outputFolder = QFileDialog.getExistingDirectory(self.dlg, "Select Folder","")
        if self.outputFolder:
            self.dlg.outputPath.setText(self.outputFolder)

    def saveASCII(self):
        '''
            Saves the final layers to ASCII-files
        '''

        try:
            #filename, _ = QInputDialog.getText(None, 'Input Dialog', 'Enter filename for ASCII-Files:')
            filename = self.dlg.textAsciiFileName.text()
            self.start_operation()
            #The template file of every output file holds a line that defines the field lengths and spaces between lengths
            def parse_definition_linev1(line):
                field_lengths = []
                inside_field = False
                current_length = 0

                for char in line:
                    if char == '<': #new field
                        inside_field = True
                        current_length = 0 
                        current_length += 1
                    
                    elif char == '>': #field closed
                        inside_field = False
                        current_length += 1
                        field_lengths.append(current_length)  # Append length after closing '>'
                    
                    elif inside_field and char == '-':
                        current_length += 1
                    
                    elif char == '+': #fields with only one character
                        field_lengths.append(1)
                        
                return field_lengths
            
            def parse_definition_linev2(line):
                field_lengths = []
                inside_field = False
                current_length = 0

                for char in line:
                    if char == '|': #new field
                        if inside_field == True:
                            field_lengths.append(current_length)
                        inside_field = True
                        current_length = 0 

                    elif inside_field and char == '-':
                        current_length += 1
                    
                    elif char == '+': #fields with only one character
                        field_lengths.append(1)
                        
                return field_lengths
            
            #Define the characters between the fields 
            def parse_inter_field_charactersv1(definition_line):
                inter_field_characters = []
                temp_str = ""
                collecting = True

                for char in definition_line:
                    if char == '>':
                        collecting = True  # Start collecting characters after '>'
                    elif char == '<':
                        if collecting:
                            # Add the collected characters to the list
                            inter_field_characters.append(temp_str)
                            temp_str = ""
                        collecting = False  # Stop collecting characters
                    elif char == '+' and collecting:
                        inter_field_characters.append(temp_str)
                        temp_str = ""
                        collecting = True
                    elif collecting:
                        if char == '*':
                            char = " "  # Replace '*' with space
                        if char == '-':
                            char = " "  # Replace '-' with space
                        temp_str += char
                return inter_field_characters

            #Defines the length of the fields
            def format_field(value, length):
                if isinstance(value, float):
                    # Round the float to a maximum of 3 decimal places
                    # Adjust the precision to ensure the total length fits within 'length'
                    precision = min(3, length - 2)  # Subtract 2 for the digit and decimal point
                    value_str = f"{value:.{precision}f}"
                elif value is None or str(value).strip().upper() == 'NULL':
                    value_str = ""
                else:
                    value_str = str(value)
                return value_str[:length].rjust(length)
        
            if filename:
                self.log_to_qtalsim_tab("Exporting ASCII-files.", Qgis.Info)
                
                '''
                    EFL
                '''

                current_path = os.path.dirname(os.path.abspath(__file__))
                eflPath = os.path.join(current_path, "talsim_parameter", "template.EFL")
                
                with open(eflPath, 'r', encoding='iso-8859-1') as eflFile:
                    templateEflContent = eflFile.readlines()
                    definition_line = templateEflContent[-1].strip()
                field_lengths = parse_definition_linev1(definition_line)

                outputPathEfl = os.path.join(self.outputFolder, f"{filename}.EFL")

                data = []

                clause = QgsFeatureRequest.OrderByClause(self.subBasinUI, ascending=True)
                orderby = QgsFeatureRequest.OrderBy([clause])

                request = QgsFeatureRequest()
                request.setOrderBy(orderby)

                features = self.eflLayer.getFeatures(request)

                for feature in features:
                    
                    layer_data = {self.subBasinUI : feature[self.subBasinUI], self.slopeFieldName : feature[self.slopeFieldName],
                                self.fieldNameAreaEFL : feature[self.fieldNameAreaEFL], self.hruSoilTypeId : feature[self.hruSoilTypeId], 
                                self.hruLandUseId : feature[self.hruLandUseId], 'Irr' : None, 'CN' : None, 'Sim-Type' : 2,
                                'stream' : 0, 'elev' : 0, 'hru' : 0, 'out' : 0, 'inlet': 0, 'outlet' : 0, 'Branch' : 0}
                    data.append(layer_data)
                
                formatted_data = []
                inter_field_characters = parse_inter_field_charactersv1(definition_line)  
                for row in data:
                    formatted_row = ""
                    for i, (length, inter_char) in enumerate(zip(field_lengths, inter_field_characters + [''])):
                        field_name = list(row.keys())[i] if i < len(row) else ""
                        field_value = row.get(field_name,"")
                        formatted_row += inter_char  # Add inter-field characters
                        formatted_row += format_field(field_value, length)
                    formatted_row += "\n"
                    formatted_data.append(formatted_row)

                completeContentEfl = templateEflContent + ['\n'] + formatted_data + [definition_line]

                with open(outputPathEfl, 'w', encoding='iso-8859-1', errors='replace') as outputEfl:
                    outputEfl.writelines(completeContentEfl)

                '''
                    BOD
                '''
                bodPath = os.path.join(current_path, "talsim_parameter", "template.BOD")
                
                with open(bodPath, 'r') as bodFile:
                    templateBodContent = bodFile.readlines()
                    definition_line = templateBodContent[-1].strip()
                field_lengths = parse_definition_linev1(definition_line)

                outputPathBod = os.path.join(self.outputFolder, f"{filename}.BOD")

                clause = QgsFeatureRequest.OrderByClause("Id", ascending=True)
                orderby = QgsFeatureRequest.OrderBy([clause])

                request = QgsFeatureRequest()
                request.setOrderBy(orderby)

                features = self.soilTypeFinal.getFeatures(request)
                def get_feature_value(feature, field_name):
                    return feature[field_name] if field_name in feature.fields().names() else None
                data = []
                for feature in features:
                    #Create the layer data dictionary
                    layer_data = {
                        "ID": feature["Id"],
                        "anzsch": self.number_soilLayers,
                        "d1": get_feature_value(feature, f"soillayer1_{self.soilTypeThickness}"),
                        "boa1": get_feature_value(feature, 'soillayer1_id_boa'),
                        "d2": get_feature_value(feature, f"soillayer2_{self.soilTypeThickness}"),
                        "boa2": get_feature_value(feature, 'soillayer2_id_boa'),
                        "d3": get_feature_value(feature, f"soillayer3_{self.soilTypeThickness}"),
                        "boa3": get_feature_value(feature, 'soillayer3_id_boa'),
                        "d4": get_feature_value(feature, f"soillayer4_{self.soilTypeThickness}"),
                        "boa4": get_feature_value(feature, 'soillayer4_id_boa'),
                        "d5": get_feature_value(feature, f"soillayer5_{self.soilTypeThickness}"),
                        "boa5": get_feature_value(feature, 'soillayer5_id_boa'),
                        "d6": get_feature_value(feature, f"soillayer6_{self.soilTypeThickness}"),
                        "boa6": get_feature_value(feature, 'soillayer6_id_boa'),
                        "Beschreibung": get_feature_value(feature, self.soilDescription)
                    }
                    
                    data.append(layer_data)

                formatted_data = []
                inter_field_characters = parse_inter_field_charactersv1(definition_line)  
                for row in data:
                    formatted_row = ""
                    for i, (length, inter_char) in enumerate(zip(field_lengths, inter_field_characters + [''])):
                        field_name = list(row.keys())[i] if i < len(row) else ""
                        field_value = row.get(field_name,"")
                        formatted_row += inter_char  # Add inter-field characters
                        formatted_row += format_field(field_value, length)

                    formatted_row += ' |'
                    formatted_row += "\n"
                    formatted_data.append(formatted_row)

                completeContentBod = templateBodContent + ['\n'] + formatted_data + [definition_line]
                with open(outputPathBod, 'w', encoding='iso-8859-1', errors='replace') as outputBod:
                    outputBod.writelines(completeContentBod)

                '''
                    BOA
                '''
                boaPath = os.path.join(current_path, "talsim_parameter", "template.BOA")
                
                with open(boaPath, 'r') as boaFile:
                    templateBoaContent = boaFile.readlines()
                    definition_line = templateBoaContent[-1].strip()
                field_lengths = parse_definition_linev1(definition_line)

                outputPathBoa = os.path.join(self.outputFolder, f"{filename}.BOA")

                clause = QgsFeatureRequest.OrderByClause('ID_Soil', ascending=True)
                orderby = QgsFeatureRequest.OrderBy([clause])

                request = QgsFeatureRequest()
                request.setOrderBy(orderby)

                features = self.soilTextureFinal.getFeatures(request)

                data = []
                for feature in features:
                   
                    layer_data = {"ID" : feature['ID_Soil'], "Soil" : feature[self.nameSoil], "BD": feature["BulkDensityClass"],
                                "Typ" : feature["Category"], "WP" : feature["WiltingPoint"], "FK" : feature["FieldCapacity"],
                                "GPV" : feature["TotalPoreVolume"], "Kf" : feature["KfValue"], "maxInf" : feature["MaxInfiltration"],
                                "maxKap" : feature["MaxCapillarySuction"], "Beschreibung" : feature[self.soilDescription]}
                    data.append(layer_data)
                
                formatted_data = []
                inter_field_characters = parse_inter_field_charactersv1(definition_line)  
                for row in data:
                    formatted_row = ""
                    for i, (length, inter_char) in enumerate(zip(field_lengths, inter_field_characters + [''])):
                        field_name = list(row.keys())[i] if i < len(row) else ""
                        field_value = row.get(field_name,"")
                        formatted_row += inter_char  # Add inter-field characters
                        formatted_row += format_field(field_value, length)
                    formatted_row += ' |'
                    formatted_row += "\n"
                    formatted_data.append(formatted_row)

                completeContentBoa = templateBoaContent + ['\n'] + formatted_data + [definition_line]
                
                with open(outputPathBoa, 'w', encoding='iso-8859-1', errors='replace') as outputBoa:
                    outputBoa.writelines(completeContentBoa)
            
                '''
                    LNZ (template file v2)
                '''
                lnzPath = os.path.join(current_path, "talsim_parameter", "template.LNZ")
                
                with open(lnzPath, 'r') as lnzFile:
                    templateLnzContent = lnzFile.readlines()
                    definition_line = templateLnzContent[-1].strip()
                field_lengths = parse_definition_linev2(definition_line)

                outputPathLnz = os.path.join(self.outputFolder, f"{filename}.LNZ")

                clause = QgsFeatureRequest.OrderByClause("Id", ascending=True)
                orderby = QgsFeatureRequest.OrderBy([clause])

                request = QgsFeatureRequest()
                request.setOrderBy(orderby)

                features = self.landuseFinal.getFeatures(request)

                data = []
                for feature in features:
                    fields = ["Id", "RootDepth", "RootDepthAnnualPatternId", "PlantCoverage", "PlantCoverageAnnualPatternId",
                            "LeafAreaIndex", "LeafAreaIndexAnnualPatternId", "KcCoeffAnnualPatternId",
                            "KyYieldAnnualPatternId", "Imp", "RoughnessCoefficient", "BulkDensityChange", "pTAW", "Name"]

                    # Initialize the dictionary for this feature
                    layer_data = {}

                    # Attempt to add each field to the dictionary
                    for field in fields:
                        try:
                            # Add field value or empty string if None
                            layer_data[field] = feature[field] if feature[field] is not None else ""
                        except KeyError:
                            # Handle the case where the field does not exist
                            layer_data[field] = "" 

                    # Append the data
                    data.append(layer_data)
                    
                formatted_data = []
                inter_field_characters = parse_inter_field_charactersv1(definition_line)  
                for row in data:
                    formatted_row = " "
                    for i, length in enumerate(field_lengths):
                        field_name = list(row.keys())[i] if i < len(row.keys()) else ""
                        field_value = row.get(field_name,"")
                        formatted_row += "|"  
                        formatted_row += format_field(field_value, length)
                    formatted_row += "|"
                    formatted_row += "\n"
                    formatted_data.append(formatted_row)
                    
                completeContentLnz = templateLnzContent + ['\n'] + formatted_data + [definition_line]
                with open(outputPathLnz, 'w', encoding='iso-8859-1', errors='replace') as outputLnz:
                    outputLnz.writelines(completeContentLnz)

                '''
                    JGG
                '''
                jggPath = os.path.join(current_path, "talsim_parameter", "talsim.JGG")
                outputPathLnz = os.path.join(self.outputFolder, f"{filename}.JGG")
                pattern_fields = [
                    "RootDepthAnnualPatternId",
                    "PlantCoverageAnnualPatternId",
                    "LeafAreaIndexAnnualPatternId",
                    "KcCoeffAnnualPatternId",
                    "KyYieldAnnualPatternId"
                ]

                clause = QgsFeatureRequest.OrderByClause("Id", ascending=True)
                orderby = QgsFeatureRequest.OrderBy([clause])
                request = QgsFeatureRequest()
                request.setOrderBy(orderby)

                ids_to_keep = set()  # use a set to avoid duplicates

                for f in self.landuseFinal.getFeatures(request):
                    for field in pattern_fields:
                        val = f[field]
                        if val not in (None, ''):
                            ids_to_keep.add(int(val))

                id_pattern = re.compile(r'^\s*\|(\d+)\s+(\d+)\s*\|') 

                with open(jggPath, "r", encoding='iso-8859-1') as f:
                    lines = f.readlines()

                keep_lines = []
                current_id = None
                keep_block = False

                for line in lines:
                    match = id_pattern.match(line)
                    if match:
                        current_id = int(match.group(2)) # Id Jahresgang
                        keep_block = current_id in ids_to_keep
                    if keep_block or line.strip().startswith('*'):  
                        keep_lines.append(line)

                with open(outputPathLnz, "w", encoding="utf-8") as f:
                    f.writelines(keep_lines)

                self.log_to_qtalsim_tab(f"ASCII-files were saved to this folder: {self.outputFolder}",Qgis.Info)
                
                #Add checkmark when process is finished
                current_text_groupbox = self.dlg.groupboxASCIIExport.title()
                if "✓" not in current_text_groupbox:  #Avoid duplicate checkmarks
                    self.dlg.groupboxASCIIExport.setTitle(f"{current_text_groupbox} ✓")

        except Exception as e:
            self.log_to_qtalsim_tab(f"{e}", Qgis.Critical) 

        finally:
            self.end_operation()
    
    def selectInputDB(self):
        '''
            Function to select the Talsim DB. 
        ''' 
        options = QFileDialog.Options()
        options |= QFileDialog.ReadOnly
        self.file_path_db, _ = QFileDialog.getOpenFileName(self.dlg, "Select Talsim Database", "", "Databases (*.db);;All Files (*)", options=options)
        if self.file_path_db:
            self.dlg.inputDBPath.setText(self.file_path_db)
        
        self.safeConnect(self.dlg.inputDBPath.textChanged, self.on_input_db_changed)
        self.on_input_db_changed()

    def DBExport(self):
        '''
            Function that inserts landuse, soiltexture, soiltype and hydrological response units to existing database.
        '''
        try:
            if not self.file_path_db:
                self.log_to_qtalsim_tab("Please select a Talsim Database first.", Qgis.Warning)
                return

            def check_and_delete_existing_data(scenario_id):
                if scenario_id is None:
                    QMessageBox.warning(None, "No Scenario Selected", "Please select a scenario before continuing.")
                    return False

                conn = sqlite3.connect(self.file_path_db)
                cur = conn.cursor()

                #Tables and queries with Scenario filtering
                table_queries = {
                    "SoilType": f"SELECT COUNT(*) FROM SoilType WHERE ScenarioId = ?",
                    "SoilTexture": f"SELECT COUNT(*) FROM SoilTexture WHERE ScenarioId = ?",
                    "Landuse": f"SELECT COUNT(*) FROM Landuse WHERE ScenarioId = ?",
                    "HydrologicalResponseUnit": """
                        SELECT COUNT(*) 
                        FROM HydrologicalResponseUnit hru
                        JOIN SystemElement se ON hru.SystemElementId = se.Id
                        WHERE se.ScenarioId = ?
                    """,
                    "PatternNumberValue": f"SELECT COUNT(*) FROM PatternNumberValue pn JOIN Pattern p ON pn.PatternId = p.Id WHERE p.ScenarioId = ?",
                    "Pattern": f"SELECT COUNT(*) FROM Pattern WHERE ScenarioId = ?"
                }

                tables_with_data = []

                for table, query in table_queries.items():
                    cur.execute(query, (scenario_id,))
                    count = cur.fetchone()[0]
                    if count > 0:
                        tables_with_data.append(table)

                if not tables_with_data:
                    conn.close()
                    return True 

                #Ask user for confirmation to delete table entries
                msg = QMessageBox()
                msg.setIcon(QMessageBox.Warning)
                msg.setWindowTitle("Existing Data Found")
                msg.setText("There are existing entries in the following tables for the selected scenario:\n\n" +
                            "\n".join(tables_with_data) +
                            "\n\nYou can only continue if all entries are deleted.")
                msg.setInformativeText("Would you like to delete all entries in these tables?")
                msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
                msg.setDefaultButton(QMessageBox.No)
                response = msg.exec_()

                if response == QMessageBox.No:
                    conn.close()
                    return False

                #Delete entries with the matching scenario_id
                for table in tables_with_data:
                    if table == "HydrologicalResponseUnit":
                        #Delete HRUs by joining with SystemElement
                        cur.execute("""
                            DELETE FROM HydrologicalResponseUnit
                            WHERE SystemElementId IN (
                                SELECT Id FROM SystemElement WHERE ScenarioId = ?
                            )
                        """, (scenario_id,))
                    elif table == "PatternNumberValue":
                        #Delete PatternNumberValue by joining with Pattern
                        cur.execute("""
                            DELETE FROM PatternNumberValue
                            WHERE PatternId IN (
                                SELECT Id FROM Pattern WHERE ScenarioId = ?
                            )
                        """, (scenario_id,))
                    else:
                        cur.execute(f"DELETE FROM {table} WHERE ScenarioId = ?", (scenario_id,))

                    #Check if the table is now empty
                    cur.execute(f"SELECT COUNT(*) FROM {table}")
                    remaining = cur.fetchone()[0]
                    #If the table is empty, reset AUTOINCREMENT counter
                    if remaining == 0:
                        cur.execute("DELETE FROM sqlite_sequence WHERE name = ?", (table,))

                conn.commit()
                conn.close()

                QMessageBox.information(None, "Data Deleted", "All data for the selected scenario has been deleted.")
                return True

            def safe_cast(value, to_type):
                #Safely cast value to a given type (int or float), returning None if invalid.
                if value is None or (isinstance(value, QVariant) and value.isNull()):
                    return None 
                try:
                    return to_type(value)
                except (ValueError, TypeError):
                    return None

            def get_feature_value(feature, field_name, to_type=str):
                #Returns the safely cast feature value if field exists, otherwise None.
                return safe_cast(feature[field_name], to_type) if field_name in feature.fields().names() else None

            def check_subbasins():
                #Get unique UI sub-basins from layer
                subbasins = set()
                for feature in self.eflLayer.getFeatures():
                    subbasins.add(feature[self.subBasinUI])
                #Get sub-basins from the database
                conn = sqlite3.connect(self.file_path_db)
                cur = conn.cursor()
                cur.execute("SELECT DISTINCT ElementTypeCharacter || ElementIdentifier FROM SystemElement WHERE ElementType = 2")
                db_subbasins = set([row[0] for row in cur.fetchall()])
                conn.close()

                #Check if there are differences in the sub-basins of layer and DB
                if subbasins == db_subbasins:
                    return True
                else:
                    missing_in_layer = db_subbasins - subbasins
                    missing_in_db = subbasins - db_subbasins

                    if missing_in_layer:
                        self.log_to_qtalsim_tab(f"The following sub-basins are in the database but missing in the layer: {', '.join(missing_in_layer)}", Qgis.Critical)
                    if missing_in_db:
                        self.log_to_qtalsim_tab(f"The following sub-basins are in the layer but missing in the database: {', '.join(missing_in_db)}. Therefore, the HRUs cannot be inserted in DB.", Qgis.Critical)
                    return False

            #Get selected Scenario ID from the combobox
            scenario_id = self.dlg.comboboxScenarios.currentData()
            
            check_and_delete_existing_data(scenario_id) #Check if there is data in the relevant tables
            check_subbasins()

            #Connect to DB
            conn = sqlite3.connect(self.file_path_db)
            cur = conn.cursor()

            clause = QgsFeatureRequest.OrderByClause("Id", ascending=True)
            orderby = QgsFeatureRequest.OrderBy([clause])

            request = QgsFeatureRequest()
            request.setOrderBy(orderby)

            '''
                SoilType
            '''
            features = self.soilTypeFinal.getFeatures(request)

            for feature in features:
                cur.execute("""
                    INSERT INTO SoilType (
                        Id, Name, LayerThickness1, SoilTextureId1, LayerThickness2, SoilTextureId2, 
                        LayerThickness3, SoilTextureId3, LayerThickness4, SoilTextureId4, 
                        LayerThickness5, SoilTextureId5, LayerThickness6, SoilTextureId6, ScenarioId
                    )
                    VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                """, (
                    get_feature_value(feature, "Id", int),
                    get_feature_value(feature, "Description", str), 
                    get_feature_value(feature, f"soillayer1_{self.soilTypeThickness}", float),
                    get_feature_value(feature, "soillayer1_id_boa", int),
                    get_feature_value(feature, f"soillayer2_{self.soilTypeThickness}", float),
                    get_feature_value(feature, "soillayer2_id_boa", int),
                    get_feature_value(feature, f"soillayer3_{self.soilTypeThickness}", float),
                    get_feature_value(feature, "soillayer3_id_boa", int),
                    get_feature_value(feature, f"soillayer4_{self.soilTypeThickness}", float),
                    get_feature_value(feature, "soillayer4_id_boa", int),
                    get_feature_value(feature, f"soillayer5_{self.soilTypeThickness}", float),
                    get_feature_value(feature, "soillayer5_id_boa", int),
                    get_feature_value(feature, f"soillayer6_{self.soilTypeThickness}", float),
                    get_feature_value(feature, "soillayer6_id_boa", int),
                    scenario_id
                ))

            conn.commit()
            conn.close()

            '''
                SoilTexture
            '''
            conn = sqlite3.connect(self.file_path_db)
            cur = conn.cursor()

            clause = QgsFeatureRequest.OrderByClause('ID_Soil', ascending=True)
            orderby = QgsFeatureRequest.OrderBy([clause])
            request = QgsFeatureRequest()
            request.setOrderBy(orderby)

            #Retrieve sorted features
            features = self.soilTextureFinal.getFeatures(request)

            #Loop through features and insert directly
            for feature in features:
                cur.execute("""
                    INSERT INTO SoilTexture (
                        Id, BulkDensityClass, Category, FieldCapacity, KfValue, MaxCapillarySuction,
                        MaxInfiltration, Name, ScenarioId, TotalPoreVolume, WiltingPoint
                    )
                    VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                """, (
                    get_feature_value(feature, "ID_Soil", int),
                    get_feature_value(feature, "BulkDensityClass", int),
                    get_feature_value(feature, "Category", int),
                    get_feature_value(feature, "FieldCapacity", float),
                    get_feature_value(feature, "KfValue", float),
                    get_feature_value(feature, "MaxCapillarySuction", float),
                    get_feature_value(feature, "MaxInfiltration", float),
                    get_feature_value(feature, self.nameSoil, str), 
                    scenario_id,  #Use the latest ScenarioId
                    get_feature_value(feature, "TotalPoreVolume", float),
                    get_feature_value(feature, "WiltingPoint", float)
                ))

            conn.commit()
            conn.close()
            self.log_to_qtalsim_tab(f"Finished inserting soil data into Talsim DB", Qgis.Info)

            '''
                LNZ
            '''

            conn = sqlite3.connect(self.file_path_db)
            cur = conn.cursor()

            clause = QgsFeatureRequest.OrderByClause("Id", ascending=True)
            orderby = QgsFeatureRequest.OrderBy([clause])
            request = QgsFeatureRequest()
            request.setOrderBy(orderby)

            #Retrieve sorted features
            features = self.landuseFinal.getFeatures(request)

            for feature in features:
                cur.execute("""
                    INSERT INTO Landuse (
                        Id, BulkDensityChange, Name, KcCoeffAnnualPatternId, KyYieldAnnualPatternId,
                        LeafAreaIndex, LeafAreaIndexAnnualPatternId, PlantCoverage, PlantCoverageAnnualPatternId, 
                        RootDepth, RootDepthAnnualPatternId, RoughnessCoefficient, ScenarioId, pTAW
                    )
                    VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                """, (
                    safe_cast(feature["Id"], int), 
                    get_feature_value(feature, "BulkDensityChange", int),
                    get_feature_value(feature, "Name", str),  
                    get_feature_value(feature, "KcCoeffAnnualPatternId", int),
                    get_feature_value(feature, "KyYieldAnnualPatternId", int),
                    get_feature_value(feature, "LeafAreaIndex", float),
                    get_feature_value(feature, "LeafAreaIndexAnnualPatternId", int),
                    get_feature_value(feature, "PlantCoverage", float),
                    get_feature_value(feature, "PlantCoverageAnnualPatternId", int),
                    get_feature_value(feature, "RootDepth", float),
                    get_feature_value(feature, "RootDepthAnnualPatternId", int),
                    get_feature_value(feature, "RoughnessCoefficient", float),
                    scenario_id,
                    get_feature_value(feature, "pTAW", float) if get_feature_value(feature, "pTAW", float) is not None else 0.5
                ))

            conn.commit()
            conn.close()
            self.log_to_qtalsim_tab(f"Finished inserting land use data into Talsim DB", Qgis.Info)

            '''
                Insert land use patterns to DB
            '''
            current_path = os.path.dirname(os.path.abspath(__file__))
            jggPath = os.path.join(current_path, "talsim_parameter", "talsim.JGG")
            with open(jggPath, "r", encoding='iso-8859-1') as f:
                lines = f.readlines()

            pattern_fields = [
                "RootDepthAnnualPatternId",
                "PlantCoverageAnnualPatternId",
                "LeafAreaIndexAnnualPatternId",
                "KcCoeffAnnualPatternId",
                "KyYieldAnnualPatternId"
            ]

            clause = QgsFeatureRequest.OrderByClause("Id", ascending=True)
            orderby = QgsFeatureRequest.OrderBy([clause])
            request = QgsFeatureRequest()
            request.setOrderBy(orderby)

            ids_to_keep = set()

            for f in self.landuseFinal.getFeatures(request):
                for field in pattern_fields:
                    val = f[field]
                    if val not in (None, ''):
                        ids_to_keep.add(int(val))
                            
            header_re = re.compile(r'^\s*\|(\d+)\s+(\d+)\s*\|\s*([0-9.,]+)?\s*\|\s*(.*)$')
            cont_re   = re.compile(r'^\s*\|\s*\|\s*([0-9.,]+)\s*\|\s*(.*)$')

            records = []
            current_id = None
            line_no = 0
            group_no = None

            for line in lines:
                line = line.rstrip("\n")

                # Match header (|group id|)
                m = header_re.match(line)
                if m:
                    group_no = int(m.group(1))
                    pattern_id = int(m.group(2))
                    raw_val = m.group(3) or ""
                    val = float(raw_val.replace(",", ".")) if raw_val.strip() else None
                    name = m.group(4).strip()

                    # Skip if not in our ID list
                    if ids_to_keep and pattern_id not in ids_to_keep:
                        current_id = None
                        continue

                    current_id = pattern_id
                    line_no = 1
                    records.append({
                        "Id": current_id,
                        "line_no": line_no,
                        "value": val,
                        "name": name,
                        "group": group_no,
                    })
                    continue

                m2 = cont_re.match(line)
                if m2 and current_id is not None:
                    line_no += 1
                    raw_val = m2.group(1) or ""
                    val = float(raw_val.replace(",", ".")) if raw_val.strip() else None
                    name = m2.group(2).strip()
                    records.append({
                        "Id": current_id,
                        "line_no": line_no,
                        "value": val,
                        "name": name,
                        "group": group_no,
                    })
            df = pd.DataFrame(records, columns=["Id", "line_no", "value", "name", "group"])

            df["name"] = df.groupby("Id")["name"].transform(lambda x: x.replace("", x.iloc[0]))

            # Insert into Pattern table
            conn = sqlite3.connect(self.file_path_db)
            cur = conn.cursor()

            df_pattern = df.drop_duplicates(subset=["Id"]).copy()

            pattern_rows = [
                (
                    int(row["Id"]),
                    str(row["name"]),
                    0,          # IsFlexible
                    0,          # IsInterpolated
                    1,          # PatternType
                    scenario_id
                )
                for _, row in df_pattern.iterrows()
            ]

            cur.executemany("""
                INSERT OR REPLACE INTO Pattern
                (Id, Description, IsFlexible, IsInterpolated, PatternType, ScenarioId)
                VALUES (?, ?, ?, ?, ?, ?)
            """, pattern_rows)

            # Insert into PatternNumberValue table
            pattern_values_rows = [
                (
                    int(row["Id"]),
                    int(row["line_no"]),
                    float(row["value"]) if row["value"] is not None else None
                )
                for _, row in df.iterrows()
            ]
            cur.executemany("""
                INSERT INTO PatternNumberValue
                (PatternId, Number, Value)
                VALUES (?, ?, ?)
            """, pattern_values_rows)

            conn.commit()
            conn.close()

            '''
                EFL
            '''
            conn = sqlite3.connect(self.file_path_db)
            cur = conn.cursor()

            #Define sorting order (ascending by subBasinUI)
            clause = QgsFeatureRequest.OrderByClause(self.subBasinUI, ascending=True)
            orderby = QgsFeatureRequest.OrderBy([clause])
            request = QgsFeatureRequest()
            request.setOrderBy(orderby)

            #Retrieve sorted features
            features = self.eflLayer.getFeatures(request)

            calculation_order_index = 1
            last_subbasin_ui = None
            #Loop through features and insert directly
            for feature in features:
                
                #Get the SystemElementId from the SystemElement table
                subBasinUI = feature[self.subBasinUI]

                cur.execute("SELECT Id FROM SystemElement WHERE (ElementTypeCharacter || ElementIdentifier) = ?", (subBasinUI,))
                result = cur.fetchone()
                systemElementId = result[0]

                if last_subbasin_ui is None or subBasinUI != last_subbasin_ui:
                    calculation_order_index = 1
                else:
                    calculation_order_index += 1
                #Insert into the HydrologicalResponseUnit table
                cur.execute("""
                    INSERT INTO HydrologicalResponseUnit (
                        LandUseId, PercentageShare, Slope, SoilTypeId, SystemElementId, CalculationOrderIndex
                    )
                    VALUES (?, ?, ?, ?, ?, ?)
                """, (
                    get_feature_value(feature, self.hruLandUseId, int),
                    get_feature_value(feature, self.fieldNameAreaEFL, float),
                    get_feature_value(feature, self.slopeFieldName, float),
                    get_feature_value(feature, self.hruSoilTypeId, int),
                    systemElementId,
                    calculation_order_index
                ))

                last_subbasin_ui = subBasinUI
            
            conn.commit()
            conn.close()


            self.log_to_qtalsim_tab(f"Finished inserting HRUs into Talsim DB", Qgis.Info)

            current_text_groupbox = self.dlg.groupboxASCIIExport.title()
            if "✓" not in current_text_groupbox:  #Avoid duplicate checkmarks
                self.dlg.groupboxASCIIExport.setTitle(f"{current_text_groupbox} ✓")
            self.log_to_qtalsim_tab(f"All Data was exported to the Talsim Database: {self.file_path_db}", Qgis.Info)
        
        except Exception as e:
            self.log_to_qtalsim_tab(f"Error: {e}", Qgis.Critical)

        finally:
            self.end_operation()

    def saveFiles(self):
        '''
            Saves all layers to files.
        '''
        try:
            geopackage_name, ok = QInputDialog.getText(None, "GeoPackage Name", "Enter the name of the GeoPackage:")

            self.start_operation()

            if self.dlg.groupboxDBExport.isChecked(): 
                self.DBExport()
            if self.dlg.groupboxASCIIExport.isChecked():
                self.saveASCII()

            #Save Geopackage
            self.geopackage_path = os.path.join(self.outputFolder, f"{geopackage_name}.gpkg")
            self.log_to_qtalsim_tab(f"Saving the layers to {self.outputFolder}.", Qgis.Info)
            def create_gpkg_save_layer(layer, gpkg_path, layer_name):
                params = {
                    'INPUT': layer,
                    'OUTPUT': gpkg_path,
                    'LAYER_NAME': layer_name,
                    'OVERWRITE': True,
                }
                processing.run("native:savefeatures", params)
                
            def add_layers_to_gpkg(layer, gpkg_path, layer_name):    
                params = {'INPUT': layer,
                        'OPTIONS': f'-update -nln {layer_name} -nlt MULTIPOLYGON', #added -nlt MULTIPOLYGON
                        'OUTPUT': gpkg_path}
                processing.run("gdal:convertformat", params)
            
            gpkg_path = self.geopackage_path
            if os.path.exists(gpkg_path): 
                try:
                    os.remove(gpkg_path)
                except Exception as e:
                    self.log_to_qtalsim_tab(f"Failed to delete existing GeoPackage: {e}",Qgis.Critical)
            create_gpkg_save_layer(self.eflLayer, gpkg_path,'hru') 
            add_layers_to_gpkg(self.landuseFinal, gpkg_path, 'landuse')
            add_layers_to_gpkg(self.soilTextureFinal, gpkg_path, 'soiltexture') 
            add_layers_to_gpkg(self.soilTypeFinal, gpkg_path, 'soiltype') 
            self.log_to_qtalsim_tab(f"File was saved to this folder: {self.outputFolder}", Qgis.Info)

        except Exception as e:
            
            self.log_to_qtalsim_tab(f"Error: {e}", Qgis.Critical)

        finally:
            self.end_operation() 


    def connectButtontoFunction(self, button, function):
        '''
            Needed to only connect the button to the function once.
        '''
        try:
            button.clicked.disconnect()
        except TypeError:
            pass  # No connection exists yet 
        button.clicked.connect(function)

    def safeConnect(self, signal: pyqtSignal, slot):
        '''
        Safely connects a signal to a slot by first disconnecting existing connections.
        '''

        try:
            signal.disconnect(slot)
        except TypeError:
            # If the disconnect fails, it means there was no connection, which is fine.
            pass

        # Connect the signal to the slot.
        signal.connect(slot)
    
    def safeDisconnect(self, signal: pyqtSignal, slot):
        '''
        Safely disconnects a signal from a slot if it is connected.
        '''

        try:
            signal.disconnect(slot)
        except TypeError:
            # If the disconnect fails, it means there was no connection, which is fine.
            pass

    def reloadPlugin(self):
        '''
            Runs when user resets the plugin. 
        '''
        self.initialize_parameters()
        self.dialog_status = 'Reset'
        push_buttons = self.dlg.findChildren(QPushButton)
        for button in push_buttons:
            #Remove ticks from buttons
            current_text = button.text()
            if current_text.endswith("✓"):  #Check if "✓" is at the end
                #Remove the last character ("✓") and update the button's text
                button.setText(current_text[:-1])
        
        #Remove ticks from group boxes
        group_boxes = self.dlg.findChildren(QGroupBox)
        #Iterate through each group box and get its title
        for group_box in group_boxes:
            groupbox_title = group_box.title()
            if groupbox_title.endswith("✓"):
                group_box.setTitle(groupbox_title[:-1])

        #Disable group boxes
        self.dlg.groupboxIntersect.setEnabled(False)
        self.dlg.groupboxIntersect.setChecked(False)
        self.dlg.groupboxLanduseOptional.setEnabled(False)
        self.dlg.soilGroupBox.setEnabled(False)
        self.dlg.landuseGroupBox.setEnabled(False)
        self.dlg.groupboxSoilOptional.setEnabled(False)

        self.dlg.groupboxASCIIExport.setChecked(False)

        self.dlg.groupboxDBExport.setChecked(False)

        #Clear Boxes
        self.dlg.comboboxUICatchment.clear()
        self.dlg.tableSoilMapping.clear()        
        self.dlg.tableSoilTypeDelete.clear()
        self.dlg.tableLanduseMapping.clear()

        self.dlg.checkboxIntersectMinSizeArea.setChecked(False)
        self.dlg.spinboxIntersectMinSizeArea.setValue(self.dlg.spinboxIntersectMinSizeArea.minimum())
        self.dlg.checkboxIntersectShareofArea.setChecked(False)
        self.dlg.spinboxIntersectShareofArea.setValue(self.dlg.spinboxIntersectShareofArea.minimum())
        
        self.dlg.outputPath.clear()

        self.run()
        self.dialog_status = None

    def openDocumentation(self):
        '''
            Opens documentation of HRUS calculation.
        '''
        webbrowser.open('https://sydroconsult.github.io/QTalsim/doc_qtalsim.html')

    
    def getAllLayers(self, root):
        '''
            Load all Layers
        '''
        layers = []
        rasterLayers = []
        for child in root.children():
            if isinstance(child, QgsLayerTreeGroup):
                # If the child is a group, recursively get layers from this group
                sub_layers, sub_raster_layers = self.getAllLayers(child)
                layers.extend(sub_layers)
                rasterLayers.extend(sub_raster_layers)
            elif isinstance(child, QgsLayerTreeLayer):
                layer = child.layer()
                if layer and layer.type() == QgsMapLayer.VectorLayer:
                    # If the child is a layer, add it to the list
                    if layer.geometryType() == QgsWkbTypes.PolygonGeometry:
                        layers.append(layer)
                elif layer and layer.type() == QgsMapLayer.RasterLayer:
                    rasterLayers.append(layer)
        return layers, rasterLayers
    
    def init_dialog(self):
        if not hasattr(self, 'dlg'):
            self.dlg = QTalsimDialog()

    def run(self):
        '''
            Run method that performs all the real work.
        '''
        #Only create GUI ONCE in callback, so that it will only load when the plugin is started
        self.init_dialog()
        #self.iface.mainWindow().setWindowTitle("HRU Calculation")

        #Define the group boxes and their custom messages
        current_path = os.path.dirname(os.path.abspath(__file__))
        path = os.path.join(current_path, "talsim_parameter", "textGroupBoxesHRU.csv") 
        self.group_boxes = self.load_group_boxes_from_csv(path)

        default_message = (
            "This plugin is designed to create hydrological response units (HRUs) suitable for Talsim. "
            "The plugin processes three layers, including a sub-basin layer, soil layer, and land use layer. "
            "It clips the layers in accordance with the sub-basin layer’s boundaries. The plugin then intersects those "
            "three layers and creates HRUs. Additionally, the plugin offers functionality to remove duplicate geometries, "
            "overlapping features, and unwanted gaps."
        )
        
        #Install the event filter on each group box
        self.custom_event_filter = CustomEventFilter(self.update_text_overview, self.group_boxes, default_message)
        
        # Install the event filter on each group box
        for widget in self.group_boxes.keys():
            widget.installEventFilter(self.custom_event_filter)

        if self.first_start == True:
            self.first_start = False
            self.dlg.onCreateSoilLayer.setVisible(False)
            #self.dlg.onLanduseConfirm2.setVisible(False)
            self.dlg.onCreateLanduseLayer.setVisible(False)
            self.dlg.finalButtonBox.button(QDialogButtonBox.Save).setEnabled(False)
        self.dlg.progressBar.setVisible(False)

        #Translating the buttons to English for consistency
        if self.dlg.finalButtonBox:
            #Set button texts in English
            self.dlg.finalButtonBox.button(QDialogButtonBox.Save).setText('Save')
            self.dlg.finalButtonBox.button(QDialogButtonBox.Cancel).setText('Cancel')
            self.dlg.finalButtonBox.button(QDialogButtonBox.Reset).setText('Reset')
            self.dlg.finalButtonBox.button(QDialogButtonBox.Help).setText('Help')

        #Reload Function
        self.connectButtontoFunction(self.dlg.finalButtonBox.button(QDialogButtonBox.Save), self.saveFiles) #write function

        #Reload Function
        self.connectButtontoFunction(self.dlg.finalButtonBox.button(QDialogButtonBox.Reset), self.reloadPlugin)

        #Open help - documentation
        self.connectButtontoFunction(self.dlg.finalButtonBox.button(QDialogButtonBox.Help), self.openDocumentation)
    
        self.fillPolygonsCombobox()

        self.feedback = self.CustomFeedback(self.log_to_qtalsim_tab)
        
        #EZG
        self.connectButtontoFunction(self.dlg.onEZG, self.selectEZG)
        
        #Soil
        self.connectButtontoFunction(self.dlg.onSoil, self.selectSoil)
        self.connectButtontoFunction(self.dlg.onConfirmSoilMapping, self.confirmSoilMapping)
        self.connectButtontoFunction(self.dlg.onSoilTypeDelete, self.deleteSoilTypes)
        self.connectButtontoFunction(self.dlg.onOverlappingSoils, self.checkOverlappingSoil)
        self.dlg.tableSoilTypeDelete.setSelectionBehavior(QAbstractItemView.SelectItems)
        self.dlg.tableSoilTypeDelete.setSelectionMode(QAbstractItemView.MultiSelection)
        self.connectButtontoFunction(self.dlg.onDeleteOverlappingSoilFeatures, self.deleteOverlappingSoilFeatures)
        self.connectButtontoFunction(self.dlg.onCheckGapsSoil, self.checkGapsSoil)
        self.dlg.comboboxModeEliminateSoil.clear()
        self.dlg.comboboxModeEliminateSoil.addItems(['Largest Area', 'Smallest Area','Largest Common Boundary']) 
        self.connectButtontoFunction(self.dlg.onFillGapsSoil, self.fillGapsSoil)
        
        self.connectButtontoFunction(self.dlg.onCreateSoilLayer, self.createSoilLayer)

        #Landuse
        self.connectButtontoFunction(self.dlg.onLanduseLayer, self.selectLanduse)
        self.connectButtontoFunction(self.dlg.onConfirmLanduseMapping, self.confirmLanduseMapping)
        self.connectButtontoFunction(self.dlg.onLanduseTypeDelete, self.deleteLanduseFeatures)
        self.connectButtontoFunction(self.dlg.onCheckOverlappingLanduse, self.checkOverlappingLanduse)
        self.connectButtontoFunction(self.dlg.onDeleteOverlapsLanduse, self.deleteOverlappingLanduseFeatures)
        self.connectButtontoFunction(self.dlg.onCheckGapsLanduse, self.checkGapsLanduse)
        
        self.dlg.comboboxModeEliminateLanduse.clear()
        self.dlg.comboboxModeEliminateLanduse.addItems(['Largest Area', 'Smallest Area','Largest Common Boundary'])  
        
        self.connectButtontoFunction(self.dlg.onFillGapsLanduse, self.fillGapsLanduse)
        self.connectButtontoFunction(self.dlg.onCreateLanduseLayer, self.createLanduseLayer) 
        
        #Intersect
        #self.dlg.groupboxIntersect.setVisible(False)
        self.connectButtontoFunction(self.dlg.onPerformIntersect, self.performIntersect) 

        QgsProject.instance().layersAdded.connect(self.layersAddedHandler)
        QgsProject.instance().layersRemoved.connect(self.layersAddedHandler)

        self.dlg.comboboxEliminateModes.clear()
        self.dlg.comboboxEliminateModes.addItems(['Largest Area', 'Smallest Area','Largest Common Boundary'])

        #Outputfile
        self.connectButtontoFunction(self.dlg.onOutputFolder, self.selectOutputFolder) 
        self.connectButtontoFunction(self.dlg.onInputDB, self.selectInputDB) 

        #show the dialog
        self.dlg.show()

        #Run the dialog event loop
        result = self.dlg.exec_()
        if self.dialog_status == 'Reset':
            return

        #See if OK was pressed
        '''
        if result:
            
                Saving to Geopackage.
            
            try:
                geopackage_name, ok = QInputDialog.getText(None, "GeoPackage Name", "Enter the name of the GeoPackage:")
                self.geopackage_path = os.path.join(self.outputFolder, f"{geopackage_name}.gpkg")
                self.start_operation()
                self.log_to_qtalsim_tab(f"Saving the layers to {self.outputFolder}.", Qgis.Info)
                def create_gpkg_save_layer(layer, gpkg_path, layer_name):
                    params = {
                        'INPUT': layer,
                        'OUTPUT': gpkg_path,
                        'LAYER_NAME': layer_name,
                        'OVERWRITE': True,
                    }
                    processing.run("native:savefeatures", params)
                    
                def add_layers_to_gpkg(layer, gpkg_path, layer_name):    
                    params = {'INPUT': layer,
                            'OPTIONS': f'-update -nln {layer_name} -nlt MULTIPOLYGON', #added -nlt MULTIPOLYGON
                            'OUTPUT': gpkg_path}
                    processing.run("gdal:convertformat", params)
                
                gpkg_path = self.geopackage_path
                if os.path.exists(gpkg_path): 
                    try:
                        os.remove(gpkg_path)
                    except Exception as e:
                        self.log_to_qtalsim_tab(f"Failed to delete existing GeoPackage: {e}",Qgis.Critical)
                create_gpkg_save_layer(self.eflLayer, gpkg_path,'hru') 
                add_layers_to_gpkg(self.landuseFinal, gpkg_path, 'landuse')
                add_layers_to_gpkg(self.soilTextureFinal, gpkg_path, 'soiltexture') 
                add_layers_to_gpkg(self.soilTypeFinal, gpkg_path, 'soiltype') 
                self.log_to_qtalsim_tab(f"File was saved to this folder: {self.outputFolder}", Qgis.Info)

            except Exception as e:
                self.log_to_qtalsim_tab(f"Error: {e}", Qgis.Critical)

            finally:
                self.end_operation()
        '''

    def open_sql_connect_dock(self):
        '''
        Method to open or show the SQL Connect dock.
        '''
        if self.first_start:
            self.first_start = False
        if not hasattr(self, 'sqlConnectDock'):
            #Create the dock widget if it doesn't exist
            self.sqlConnectDock = CustomDockWidget("Connect to Talsim DB", self.iface.mainWindow())

            self.sqlConnect = SQLConnectDialog(self.iface.mainWindow(), self)
            self.sqlConnectDock.setWidget(self.sqlConnect)
            self.iface.mainWindow().addDockWidget(Qt.RightDockWidgetArea, self.sqlConnectDock)
        else:
            self.sqlConnect.initialize_parameters()
        self.sqlConnectDock.raise_()
        self.sqlConnectDock.show()
        self.sqlConnectDock.destroyed.connect(self.onDockDestroyed)

    def onDockDestroyed(self, obj=None):
        self.sqlConnectDock = None
    
    def open_sub_basin_window(self):
        if self.first_start:
            self.first_start = False
        if not hasattr(self, 'subBasinWindow'):
            self.subBasinWindow = QMainWindow(self.iface.mainWindow())
            self.subBasinWindow.setWindowTitle("Sub-basin preprocessing")
            self.subBasinDialog = SubBasinPreprocessingDialog(self.iface.mainWindow(), self)
            self.subBasinWindow.setCentralWidget(self.subBasinDialog)
        else:
            self.subBasinDialog.initialize_parameters()
        
        self.subBasinWindow.raise_()
        self.subBasinWindow.show()
        self.subBasinWindow.destroyed.connect(self.onWindowDestroyedSubbasin)

    def open_soil_window(self):
        if self.first_start:
            self.first_start = False
        if not hasattr(self, 'soilWindow'):
            self.soilWindow = QMainWindow(self.iface.mainWindow())
            self.soilWindow.setWindowTitle("ISRIC Soil Type Converter")
            self.soilDialog = SoilPreprocessingDialog(self.iface.mainWindow(), self)
            self.soilWindow.setCentralWidget(self.soilDialog)
        else:
            self.soilDialog.initialize_parameters()
        
        self.soilWindow.raise_()
        self.soilWindow.show()
        self.soilWindow.destroyed.connect(self.onWindowDestroyedSoil)

    def open_landuse_window(self):
        if self.first_start:
            self.first_start = False
        if not hasattr(self, 'landuseWindow'):
            self.landuseWindow = QMainWindow(self.iface.mainWindow())
            self.landuseWindow.setWindowTitle("Land use mapping")
            self.landuseDialog = LanduseAssignmentDialog(self.iface.mainWindow(), self)
            self.landuseWindow.setCentralWidget(self.landuseDialog)
        else:
            self.landuseDialog.initialize_parameters()
        
        self.landuseWindow.raise_()
        self.landuseWindow.show()
        self.landuseWindow.destroyed.connect(self.onWindowDestroyedLanduse)

    def onWindowDestroyedSubbasin(self, obj=None):
        self.subBasinWindow = None

    def onWindowDestroyedSoil(self, obj=None):
        self.soilWindow = None

    def onWindowDestroyedLanduse(self, obj=None):
        self.landuseWindow = None
    
'''
    Custom Dock Widget for 'Connect to Talsim DB' to overwrite the closeEvent-Action with a custom function. 
'''
class CustomDockWidget(QDockWidget):
    def __init__(self, title, parent=None):
        super(CustomDockWidget, self).__init__(title, parent)

    def closeEvent(self, event):
        
        reply = QMessageBox.question(self, 'Confirm Close',
                                     "Warning: Closing this window will disconnect you from the Talsim DB and any unsaved changes will be lost. Are you sure you want to proceed?",
                                     QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
        if reply == QMessageBox.Yes:
            QTalsim.log_to_qtalsim_tab(self, "Talsim DB was disconnected.", Qgis.Info)
            super(CustomDockWidget, self).closeEvent(event)
            '''
            if self.layerGroup:
                root = QgsProject.instance().layerTreeRoot()
                root.removeChildNode(self.layerGroup)
                self.layerGroup = None
            '''
        else:
            event.ignore()

class CustomEventFilter(QObject):
    def __init__(self, callback, group_boxes, default_message):
        """
        :param callback: Function to call when an area is clicked.
        :param group_boxes: Dictionary mapping group boxes to custom messages.
        :param default_message: Message to show when clicking outside group boxes.
        """
        super().__init__()
        self.callback = callback
        self.group_boxes = group_boxes  #Dictionary of group box widgets and their custom messages
        self.default_message = default_message  #Default message for clicks outside group boxes

    def eventFilter(self, source, event):
        if event.type() == QEvent.MouseButtonPress:
            for group_box, message in self.group_boxes.items():
                if source == group_box or group_box.isAncestorOf(source):
                    # Trigger the specific message for this group box
                    self.callback(message)
                    return True  # Stop event propagation after handling

        return super().eventFilter(source, event)