# -*- coding: utf-8 -*-
"""
/***************************************************************************
 VolumeCalculationTool
                                 A QGIS plugin
 Calculates volume based on DEM height layers
 Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/
                              -------------------
        begin                : 2020-09-23
        git sha              : $Format:%H$
        copyright            : (C) 2020 by REDcatch GmbH.
        email                : support@redcatch.at
 ***************************************************************************/

/***************************************************************************
    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 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.

 ***************************************************************************/
"""

import processing
from processing.core.Processing import Processing
Processing.initialize()

from qgis.PyQt.QtCore import *
from qgis.PyQt.QtGui import *
from qgis.PyQt.QtWidgets import *
from qgis.gui import QgsMessageBar
from qgis.core import *

# Initialize Qt resources from file resources.py
from .resources import *
# Import the code for the dialog
from .volume_calculation_tool_dialog import VolumeCalculationToolDialog
from .volume_calculation_tool_dialog import BaseLevelOptions, CountOptions, DEFAULT_ATTRIBUTE_NAME, DEFAULT_ATTRIBUTE_NAME_NEG
import os.path
import copy
from datetime import datetime

MESSAGE_CATEGORY = "VolumeTask"
ACCURATE_WORKFLOW_DESCRIPTION = "ACCURATE_VOL_CALCULATION"
SIMPLE_WORKFLOW_BASE_PREFIX = "_VB"
SIMPLE_WORKFLOW_HEIGHT_PREFIX = "_VH"
SIMPLE_WORKFLOW_BASE_HEIGHT_COLUMN_NAME = "_VBmean"
SIMPLE_WORKFLOW_HEIGHT_COLUMN_NAME = "_VHmean"
CONSTANT_DEFAULT_SAMPLE_MULTIPLIER = 2
DEFAULT_SAMPLE_STEP_SIZE = 1.00

ICON_PATH = ":/plugins/volume_calculation_tool/icon.svg"


class PolygonWrapper():
    def __init__(self, polygon_ref, identifier_string, base_line=0):
        self.polygon_ref = polygon_ref
        self.identifier_string = identifier_string
        self.base_line = base_line
    
    def copy_constructor(self):
        return PolygonWrapper(QgsGeometry(self.polygon_ref), self.identifier_string, self.base_line)
        

class VolumeTaskOptions():
    def __init__(self, polygons, sampling_height_layer, step_size_x, step_size_y, counting_option, base_line_option, base_line_height_layer, column_name, column_name_neg
                 , height_dem_band, base_dem_band, vector_layer=None):
        self.polygons = polygons
        self.height_layer = sampling_height_layer
        self.step_size_x = step_size_x
        self.step_size_y = step_size_y
        self.base_line_height_layer = base_line_height_layer
        self.counting_option = counting_option
        self.base_line_option = base_line_option
        self.vector_layer = vector_layer
        self.column_name = column_name
        self.column_name_neg = column_name_neg
        self.height_dem_band = height_dem_band
        self.base_dem_band = base_dem_band
        self.isInAccurateWorkflow = False
        
    def copy_constructor(self):
        cloned_p = []
        for p in self.polygons:
            cloned_p.append(p.copy_constructor())
        if self.base_line_height_layer != None:
            return VolumeTaskOptions(cloned_p, 
                                     self.height_layer.clone(), 
                                     self.step_size_x, self.step_size_y, 
                                     self.counting_option, 
                                     self.base_line_option,
                                     self.base_line_height_layer.clone(),
                                     self.column_name,
                                     self.column_name_neg,
                                     self.height_dem_band,
                                     self.base_dem_band)
        return VolumeTaskOptions(cloned_p, 
                                 self.height_layer.clone(),
                                 self.step_size_x, self.step_size_y, 
                                 self.counting_option, 
                                 self.base_line_option,
                                 None,
                                 self.column_name,
                                 self.column_name_neg,
                                 self.height_dem_band,
                                 self.base_dem_band)
    
    def __str__(self):
        str_repr = "Polygon Layer: "+ self.vector_layer.name() + "\n"
        str_repr += "Height Layer: " + self.height_layer.name() + "\n"
        str_repr += "Base Line Option: " + str(self.base_line_option) + "\n"
        if self.isInAccurateWorkflow:
            str_repr += "Counting Option: " + str(self.counting_option) + "\n"
            str_repr += "Step Size X: " + str(self.step_size_x) + "\n"
            str_repr += "Step Size Y: " + str(self.step_size_y) + "\n"
            return str_repr
        return str_repr

class AccurateVolumeCalculationTask(QgsTask):
    
    def __init__(self, description, task_options):
        super().__init__(description, QgsTask.CanCancel)
        self.exception = None
        self.step_size_x = task_options.step_size_x
        self.step_size_y = task_options.step_size_y
        self.results = {}
        self.task_options = task_options
    
    def run(self):
        QgsMessageLog.logMessage('Started task "{}"'.format(self.description()), MESSAGE_CATEGORY, Qgis.Info)
        progress_bar_total_points = 0.0
        for poly in self.task_options.polygons:
            bounded_box = poly.polygon_ref.boundingBox()
            max_x = bounded_box.xMaximum()
            max_y = bounded_box.yMaximum()
            progress_bar_total_points += float(((bounded_box.xMaximum() - bounded_box.xMinimum()) * (bounded_box.yMaximum() - bounded_box.yMinimum()))/(self.step_size_x * self.step_size_y))
        current_progress = 0.0
        for poly in self.task_options.polygons:
            bounded_box = poly.polygon_ref.boundingBox()
            max_x = bounded_box.xMaximum()
            max_y = bounded_box.yMaximum()
            current_x = bounded_box.xMinimum() + self.step_size_x
            current_y = bounded_box.yMinimum() + self.step_size_y
            fix_x_start = current_x
            volume = 0
            neg_volume = 0
            iterations = 0
            while(current_y <= max_y):
                if self.isCanceled():
                    return False
                while(current_x <= max_x):
                    iterations += 1 
                    if poly.polygon_ref.contains(QgsPointXY(current_x, current_y)):
                        val, res = self.task_options.height_layer.dataProvider().sample(QgsPointXY(current_x, current_y), self.task_options.height_dem_band)
                        if res:
                            base_height = self.determineBaseHeight(current_x, current_y, poly, val)
                            volume, neg_volume = self.determineVolumeBasedOnOptions(volume, neg_volume, val, base_height)
                    current_x += self.step_size_x
                current_y += self.step_size_y
                current_x = fix_x_start
                current_progress += iterations
                iterations = 0
                self.setProgress(round((current_progress/progress_bar_total_points)*100, 2))
            self.results[poly.identifier_string] = (volume, neg_volume)
        return True
                
    def determineVolumeBasedOnOptions(self, current_volume, current_neg_volume, sampled_height, base_height):
        vol = current_volume
        n_vol = current_neg_volume
        absolute_height = abs(sampled_height - base_height)
        prism = (self.step_size_x * self.step_size_y * absolute_height)
        if self.task_options.counting_option == CountOptions.COUNT_ONLY_ABOVE:
            if sampled_height >= base_height:
                vol += prism
            return vol, n_vol
        if self.task_options.counting_option == CountOptions.COUNT_ONLY_BELOW:
            if sampled_height < base_height:
                vol += prism
            return vol, n_vol
        if self.task_options.counting_option == CountOptions.SUBTRACT_VOL_BELOW:
            if sampled_height >= base_height:
                vol += prism
            else:
                vol -= prism
            return vol, n_vol
        if self.task_options.counting_option == CountOptions.ADD_VOL_BELOW_TO_ABOVE:
            vol += prism
            return vol, n_vol
        if self.task_options.counting_option == CountOptions.COUNT_ABOVE_AND_BELOW:
            if sampled_height >= base_height:
                vol += prism
            else:
                n_vol += prism
            return vol, n_vol
        return vol, n_vol
    
    def determineBaseHeight(self, current_x, current_y, poly, height_layer_sample):
        if self.task_options.base_line_option == BaseLevelOptions.USE_DEM_LAYER:
            val, res = self.task_options.base_line_height_layer.dataProvider().sample(QgsPointXY(current_x, current_y), self.task_options.base_dem_band)
            if res:
                return val
            else:
                # we return the sampled height of the height layer in order to force the prism to volume 0 i.e. we skip it
                return height_layer_sample
        else:
            return poly.base_line
        
    
    def finished(self, result):
         QgsMessageLog.logMessage('Task "{name}" completed'.format(name=self.description()), MESSAGE_CATEGORY, Qgis.Success)
                                  
    
    def cancel(self):
        QgsMessageLog.logMessage('Task "{name}" was canceled'.format(name=self.description()), MESSAGE_CATEGORY, Qgis.Info) 
        super().cancel()


class VolumeCalculationTool:
    """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',
            'VolumeCalculationTool_{}.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'&Volume Calculation Tool')

        # 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.isInAccurateWorkflow = False
        self.results = None
        self.height_layer = None
        self.vector_layer = None
        self.polygons = None
        self.identifiers = None
        self.base_line = None
        self.step_size_x = None
        self.step_size_y = None
        self.task_manager = QgsTaskManager()
        self.task_manager.allTasksFinished.connect(self.calculationFinished)
        self.task_manager.progressChanged.connect(self.updateProgressBar)
        self.current_task_options = None
        

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

        icon = QIcon(icon_path)
        action = QAction(icon, text, parent)
        action.triggered.connect(callback)
        action.setEnabled(enabled_flag)

        if status_tip is not None:
            action.setStatusTip(status_tip)

        if whats_this is not None:
            action.setWhatsThis(whats_this)

        if add_to_toolbar:
            # Adds plugin icon to Plugins toolbar
            self.iface.addToolBarIcon(action)

        if add_to_menu:
            self.iface.addPluginToRasterMenu(
                self.menu,
                action)

        self.actions.append(action)

        return action

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

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

        # will be set False in run()
        self.first_start = True


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


    def isRasterLayer(self, x):
        if type(x) == QgsRasterLayer:
            return True
        return False
 
 
    def isVectorLayer(self, x):
        if type(x) == QgsVectorLayer:
            return True
        return False
    
    def calculationFinished(self):
        for task in self.task_manager.tasks():
            self.results = task.results
        self.updateOutputLog()
        self.writeResultsToLayer()
        self.dlg.unlockGUI()

    def updateProgressBar(self, task_id, progress):
        if progress >= 0.0:
            self.dlg.progressBar.setValue(int(progress))
            
    def populateBandListForHeight(self, layer_name):
        self.dlg.mFieldComboBand.clear()
        try:
            band_count = QgsProject.instance().mapLayersByName(layer_name)[0].bandCount()
            band_list = []
            for i in range(1,band_count+1):
                self.dlg.mFieldComboBand.addItem(str(i))
        except IndexError:
            self.dlg.popFatalErrorBox("No DEM layers found ! Restart the plugin and make sure DEM layers are available")
            return 
        
    def populateBandListForBase(self, layer_name):
        try:
            self.dlg.mFieldComboBandBase.clear()
            band_count = QgsProject.instance().mapLayersByName(layer_name)[0].bandCount()
            band_list = []
            for i in range(1,band_count+1):
                self.dlg.mFieldComboBandBase.addItem(str(i))
        except IndexError:
            self.dlg.popFatalErrorBox("No polygon layers found ! Restart the plugin and make sure polygon layers are available")
            return
        
    def determineBandListForHeight(self, index):
        self.populateBandListForHeight(self.dlg.mFieldComboHeightLayer.currentText())
    
    def determineBandListForBase(self, index):
        self.populateBandListForBase(self.dlg.mFieldComboHeightLayerBase.currentText())
    
    def populateInputOptions(self):
        layers = QgsProject.instance().mapLayers()
        self.dlg.mFieldComboHeightLayer.clear()
        self.dlg.mFieldComboPolygon.clear()
        self.dlg.mFieldComboHeightLayerBase.clear()
        for key in layers:
            if self.isRasterLayer(layers[key]):
                self.dlg.mFieldComboHeightLayer.addItem(layers[key].name())
                self.dlg.mFieldComboHeightLayerBase.addItem(layers[key].name())
            if self.isVectorLayer(layers[key]):
                self.dlg.mFieldComboPolygon.addItem(layers[key].name())
        self.populateBandListForHeight(self.dlg.mFieldComboHeightLayer.currentText())
        self.populateBandListForBase(self.dlg.mFieldComboHeightLayerBase.currentText())
    
    def run(self):
        """Run method that performs all the real work"""

        # Create the dialog with elements (after translation) and keep reference
        # Only create GUI ONCE in callback, so that it will only load when the plugin is started
        #if self.first_start == True:
        #    self.first_start = False
        self.dlg = VolumeCalculationToolDialog(self.updateDefaultSampleStepOnHeightLayerChange, self.determineBandListForHeight, self.determineBandListForBase, self.workflow, self.cancelLongWorkflow)
        
        self.populateInputOptions()
        self.updateDefaultSampleStepOnHeightLayerChange(0)
        # show the dialog
        self.dlg.show()
        # Run the dialog event loop
        result = self.dlg.exec_()
        
    def cancelLongWorkflow(self):
        self.task_manager.cancelAll()
        self.dlg.unlockGUI()
    
    def writeResultsToLayer(self):
        if self.dlg.checkBox_add_field.isChecked():
            index = 0
            n_index = 0
            index = self.current_task_options.vector_layer.fields().lookupField(self.current_task_options.column_name)
            if (index == -1):
                self.current_task_options.vector_layer.dataProvider().addAttributes([QgsField(self.current_task_options.column_name, QVariant.Double)])
                self.current_task_options.vector_layer.updateFields()
            if self.current_task_options.isInAccurateWorkflow and self.current_task_options.counting_option == CountOptions.COUNT_ABOVE_AND_BELOW:
                    n_index = self.current_task_options.vector_layer.fields().lookupField(self.current_task_options.column_name_neg)
                    if (n_index == -1):
                        self.current_task_options.vector_layer.dataProvider().addAttributes([QgsField(self.current_task_options.column_name_neg, QVariant.Double)])
                        self.current_task_options.vector_layer.updateFields()
            index = self.current_task_options.vector_layer.fields().lookupField(self.current_task_options.column_name)
            if self.current_task_options.isInAccurateWorkflow and self.current_task_options.counting_option == CountOptions.COUNT_ABOVE_AND_BELOW:
                n_index = self.current_task_options.vector_layer.fields().lookupField(self.current_task_options.column_name_neg)
            with edit(self.current_task_options.vector_layer):
                for feature in self.current_task_options.vector_layer.getFeatures():
                    if feature == None:
                        continue
                    if self.current_task_options.isInAccurateWorkflow:
                        pos, neg = self.results[feature.id()]
                        feature.setAttribute(index, round(pos,1))
                        if self.current_task_options.counting_option == CountOptions.COUNT_ABOVE_AND_BELOW:
                            feature.setAttribute(n_index, round(neg,1))
                    else:
                        feature.setAttribute(index, round(self.results[feature.id()],1))
                    self.current_task_options.vector_layer.updateFeature(feature)
                    
    def workflow(self):
        self.gatherInputInfo()
        self.validateVectorConstraints()
        if self.dlg.radioButtonAccurate.isChecked():
            self.current_task_options.isInAccurateWorkflow = True
            self.doAccurateWorkflow()
        else:
            self.current_task_options.isInAccurateWorkflow = False
            self.doSimpleWorkflow()
    
    def doSimpleWorkflow(self):
        self.getVolumeSimple()
        self.updateOutputLog()
        self.writeResultsToLayer()
        self.cleanUpFields()
    
    def validateMinimumInput(self):
        dem_layers = self.dlg.mFieldComboHeightLayer.count()
        vector_layers = self.dlg.mFieldComboPolygon.count()
        if vector_layers == 0 or dem_layers == 0:
            self.dlg.popFatalErrorBox("No DEM layers or polygon layers found/selected ! Closing plugin, please make sure both are available")
            self.dlg.closeIt()
        
    def validateVectorConstraints(self):
        caps = self.current_task_options.vector_layer.dataProvider().capabilities()
        if (not (caps & QgsVectorDataProvider.AddAttributes)) and self.dlg.checkBox_add_field.isChecked():
            self.dlg.popFatalErrorBox("Cannot add attributes to vector layer, change input option or check layer. Closing plugin !")
            self.dlg.closeIt()
        if (not (caps & QgsVectorDataProvider.ChangeAttributeValues)) and self.dlg.checkBox_add_field.isChecked():
            self.dlg.popFatalErrorBox("Cannot change attribute values of vector layer, change input option or check layer. Closing plugin !")
            self.dlg.closeIt()
        if (not (caps & QgsVectorDataProvider.DeleteAttributes)) and (not self.current_task_options.isInAccurateWorkflow):
            self.dlg.popFatalErrorBox("Cannot change delete attribute values of vector layer, change volume calculation type or check layer. Closing plugin !")
            self.dlg.closeIt()
        
    def gatherInputInfo(self):
        self.validateMinimumInput()
        height_layer = QgsProject.instance().mapLayersByName(self.dlg.mFieldComboHeightLayer.currentText())[0]
        vector_layer = QgsProject.instance().mapLayersByName(self.dlg.mFieldComboPolygon.currentText())[0]
        height_base_band = int(self.dlg.mFieldComboBandBase.currentText())
        height_band = int(self.dlg.mFieldComboBand.currentText())
        polygon_list = self.wrapPolygons(vector_layer)
        counting_option = self.determineCountingOption()
        base_line_height_layer, base_line_option = self.determineBaseLineBasedOnOptions(polygon_list, height_layer)
        step_size_x = self.dlg.doubleSpinBoxSampleStepX.value()
        step_size_y = self.dlg.doubleSpinBoxSampleStepY.value()
        column_name, column_name_neg = self.getFieldName()
        self.current_task_options = VolumeTaskOptions(polygon_list, 
                                                      height_layer, 
                                                      step_size_x, step_size_y, 
                                                      counting_option, 
                                                      base_line_option, 
                                                      base_line_height_layer, 
                                                      column_name,
                                                      column_name_neg,
                                                      height_band,
                                                      height_base_band,
                                                      vector_layer)
    
    def getFieldName(self):
        name_pos = self.dlg.fieldName.text()
        if name_pos == "":
            name_pos = DEFAULT_ATTRIBUTE_NAME
        name_neg = self.dlg.fieldName_2.text()
        if name_neg == "":
            name_neg = DEFAULT_ATTRIBUTE_NAME_NEG
        return name_pos, name_neg
    
    def determineCountingOption(self):
        current_option = self.dlg.mFieldComboCountingMethod.currentIndex()
        if current_option == 0:
            return CountOptions.COUNT_ABOVE_AND_BELOW
        if current_option == 1:
            return CountOptions.COUNT_ONLY_ABOVE
        if current_option == 2:
            return CountOptions.COUNT_ONLY_BELOW
        if current_option == 3:
            return CountOptions.SUBTRACT_VOL_BELOW
        if current_option == 4:
            return CountOptions.ADD_VOL_BELOW_TO_ABOVE
        return None
    
    def wrapPolygons(self, vector_layer):
        polygon_list = []
        for geom in vector_layer.getFeatures():
            polygon_list.append(PolygonWrapper(geom.geometry(), geom.id()))
        return polygon_list
    
    def determineBaseLineBasedOnOptions(self, polygon_list, height_layer):
        current_option = self.dlg.mFieldComboBaseLevelMethod.currentIndex()
        base_line_option = None
        base_line_layer = None
        if current_option == 1:
            self.getMinHeightOfPolygonVertices(polygon_list, height_layer)
            base_line_option = BaseLevelOptions.APPROXIMATE_VIA_MIN
        if current_option == 0:
            self.getAvgHeightOfPolygonVertices(polygon_list, height_layer)
            base_line_option = BaseLevelOptions.APPROXIMATE_VIA_AVG
        if current_option == 2:
            base_line_option = BaseLevelOptions.USE_DEM_LAYER
            base_line_layer = QgsProject.instance().mapLayersByName(self.dlg.mFieldComboHeightLayerBase.currentText())[0]
        if current_option == 3:
            base_line_option = BaseLevelOptions.MANUAL_BASE_LEVEL
            self.setBaseLevelManually(polygon_list)
        return base_line_layer, base_line_option

    def setBaseLevelManually(self, polygon_list):
        base_val = self.dlg.doubleSpinBoxBaseLevel.value()
        for poly in polygon_list:
            poly.base_line = base_val

    def getAvgHeightOfPolygonVertices(self, polygon_list, height_layer):
        for poly in polygon_list:
            vertices = poly.polygon_ref.vertices()
            count = 0
            average_height = 0
            for vertex in vertices:
                count += 1
                val, res = height_layer.dataProvider().sample(QgsPointXY(vertex.x(), vertex.y()), 1)
                if res:
                    average_height += val
            poly.base_line = average_height/count
            print(average_height)
    
    def getMinHeightOfPolygonVertices(self, polygon_list, height_layer):
        for poly in polygon_list:
            vertices = poly.polygon_ref.vertices()
            min_height = 0
            have_reference_value = True
            for vertex in vertices:
                val, res = height_layer.dataProvider().sample(QgsPointXY(vertex.x(), vertex.y()), 1)
                if res:
                    if have_reference_value:
                        min_height = val
                        have_reference_value = False
                    if val < min_height:
                        min_height = val
            poly.base_line = min_height

    def getVolumeSimple(self):
        #each entry is a list with the following content
        #0 area
        #1 height 
        #2 base_line
        internal_external_map = {}
        results = {}
        result = processing.run("qgis:zonalstatistics", { 'COLUMN_PREFIX' : SIMPLE_WORKFLOW_HEIGHT_PREFIX, 'INPUT_RASTER' : self.current_task_options.height_layer, 'INPUT_VECTOR': self.current_task_options.vector_layer, 'RASTER_BAND' : self.current_task_options.height_dem_band, 'STATS' : [2] })
        for poly in self.current_task_options.polygons:
            internal_external_map[poly.identifier_string] = []
            internal_external_map[poly.identifier_string].append(poly.polygon_ref.area())
        index = self.current_task_options.vector_layer.fields().lookupField(SIMPLE_WORKFLOW_HEIGHT_COLUMN_NAME)
        for feature in self.current_task_options.vector_layer.getFeatures():
            internal_external_map[feature.id()].append(feature.attributes()[index])
        self.determineBaseHeights(internal_external_map)
        for key in internal_external_map:
            area = internal_external_map[key][0]
            absolute_avg_height = abs(internal_external_map[key][1] - internal_external_map[key][2])
            volume = area * absolute_avg_height
            results[key] = volume
        self.results = results
        
    def cleanUpFields(self):
        index_height = self.current_task_options.vector_layer.fields().lookupField(SIMPLE_WORKFLOW_HEIGHT_COLUMN_NAME)
        index_base =self.current_task_options.vector_layer.fields().lookupField(SIMPLE_WORKFLOW_BASE_HEIGHT_COLUMN_NAME)
        if index_base != -1:
            self.current_task_options.vector_layer.dataProvider().deleteAttributes([index_base])
        if index_height != -1:
            self.current_task_options.vector_layer.dataProvider().deleteAttributes([index_height])
        self.current_task_options.vector_layer.updateFields()
        
        
    def determineBaseHeights(self, internal_external_map):
        if self.current_task_options.base_line_option == BaseLevelOptions.USE_DEM_LAYER:
            settings_dict = { 'COLUMN_PREFIX' : SIMPLE_WORKFLOW_BASE_PREFIX, 'INPUT_RASTER' : self.current_task_options.base_line_height_layer, 'INPUT_VECTOR':self.current_task_options.vector_layer, 'RASTER_BAND' : self.current_task_options.base_dem_band, 'STATS' : [2] }
            processing.run("qgis:zonalstatistics", settings_dict)
            index = self.current_task_options.vector_layer.fields().lookupField(SIMPLE_WORKFLOW_BASE_HEIGHT_COLUMN_NAME)
            for feature in self.current_task_options.vector_layer.getFeatures():
                internal_external_map[feature.id()].append(feature.attributes()[index])
        else:
            for poly in self.current_task_options.polygons:
                internal_external_map[poly.identifier_string].append(poly.base_line)
            
        
    def updateOutputLog(self):
        self.dlg.logOutput.append("===========")
        self.dlg.logOutput.append(datetime.now().strftime('%H:%M:%S'))
        self.dlg.logOutput.append(str(self.current_task_options))
        for key in self.results:
            ident = key
            self.dlg.logOutput.append("Polygon Id: " + str(ident))
            if self.current_task_options.isInAccurateWorkflow:
                val, n_val = self.results[ident]
                self.dlg.logOutput.append("Above Volume (m3):" + str(round(val,1)))
                self.dlg.logOutput.append("Below Volume (m3):" + str(round(n_val,1)))
            else:
                val = self.results[ident]
                self.dlg.logOutput.append("Volume (m3):" + str(round(val,1)))
        sb = self.dlg.logOutput.verticalScrollBar()
        sb.setValue(sb.maximum())

    def doAccurateWorkflow(self):
        self.dlg.progressBar.reset()
        accurate_task = AccurateVolumeCalculationTask(ACCURATE_WORKFLOW_DESCRIPTION,
                                                      self.current_task_options.copy_constructor())
        self.task_manager.addTask(accurate_task)
        self.dlg.progressBar.setValue(0)
        self.dlg.lockupGUIDuringCalculation()
        
    def updateDefaultSampleStepOnHeightLayerChange(self, index):
        step_size = self.determineDefaultSamplingFromHeightLayer()
        self.dlg.doubleSpinBoxSampleStepX.setValue(step_size)
        self.dlg.doubleSpinBoxSampleStepY.setValue(step_size)

    def determineDefaultSamplingFromHeightLayer(self):
        top_entry = self.dlg.mFieldComboHeightLayer.currentText()
        if top_entry == "":
            return DEFAULT_SAMPLE_STEP_SIZE
        x_pixel_size = round(abs((QgsProject.instance().mapLayersByName(top_entry)[0]).rasterUnitsPerPixelX()),2)
        return (x_pixel_size * CONSTANT_DEFAULT_SAMPLE_MULTIPLIER)
