"""
/***************************************************************************
 qgSurf - plugin for Quantum GIS

 Processing of geological planes and surfaces

                              -------------------
        start                : 2011-12-21
        copyright            : (C) 2011-2023 by Mauro Alberti
        email                : alberti.m65@gmail.com

 ***************************************************************************/

# licensed under the terms of GNU GPL 3

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/
"""

import numbers
from collections import OrderedDict

import pickle

from PyQt5 import uic

from qgis.gui import QgsColorButton, QgsOpacityWidget

from gst.core.profiles.geoprofiles import *
from gst.core.profiles.profilers import *
from gst.core.utils.strings import *
from gst.io.rasters.gdal_io import *
from gst.io.vectors.ogr_io import *
from gst.plots.mpl import *
from gst.plots.plotting import *
from gst.qgis.canvas import *
from gst.qgis.lines import *
from gst.qgis.messages import *
from gst.qgis.polygons import *
from gst.qgis.profiles import *
from gst.qgis.rasters import *
from gst.qgis.vectors import *
from gst.qt.filesystem import *
from gst.qt.messages import *
from gst.qt.plots import *
from gst.qt.tools import *

from ..utils.geoprofiler.export import *


ltMarkerStyles = OrderedDict(
    [
        ("circle", "o"),
        ("square", "s"),
        ("diamond", "D"),
        ("triangle", "^"),
        ("star", "*"),
        ("plus", "+")
    ]
)


class GeoprofilerWidget(QWidget):

    def __init__(self,
                 current_directory,
                 plugin_name,
                 canvas
                 ):

        super(GeoprofilerWidget, self).__init__()

        self.plugin_name = plugin_name
        self.canvas = canvas

        self.current_directory = current_directory
        uic.loadUi(f"{self.current_directory}/ui/choices_treewidget.ui", self)

        self.graphical_params = dict()
        self.graphical_params["figure"] = FigurePlotParams()
        self.graphical_params["axis"] = AxisPlotParams()
        self.graphical_params["elevations"] = ElevationPlotParams()
        self.graphical_params["point projections"] = PointPlotParams()
        self.graphical_params["attitude projections"] = AttitudePlotParams()
        self.graphical_params["line intersections"] = PointPlotParams()
        self.graphical_params["polygon intersections"] = dict()

        self.actions_qtreewidget = self.actionsTreeWidget

        self.profile_operations = {
            "load trace from line layer": self.define_profiles_from_line_layer,
            "digitize trace": self.digitize_rubberband_line,
            "clear trace": self.clear_rubberband_line,
            "save trace": self.save_rubberband_line,
            "Elevation source": self.read_elevation_source,
            "Profiles generation": self.define_profiles_parameters,
            "points (projected)": self.project_points,
            "geological attitudes (projected)": self.project_attitudes,
            "lines (intersected)": self.intersect_lines,
            "polygons (intersected)": self.intersect_polygons,
            "define graphical parameters": self.define_total_graphical_parameters,
            "save graphical parameters": self.save_graphic_parameters,
            "load graphical parameters": self.load_graphic_parameters,
            "Plot profiles": self.plot_profiles,
            "topographic profiles": self.export_topographic_profiles,
            "Help": self.open_help,
        }

        self.choose_message = "choose"
        self.polygon_classification_colors = dict()

        self.actions_qtreewidget.itemDoubleClicked.connect(self.activate_action_window)

        self.init_parameters()

        self.reference_crs = None

        QgsProject.instance().crsChanged.connect(self.init_parameters)

    def init_parameters(self):

        self.profile_track_source_type = ProfileSource.UNDEFINED

        self.profile_line = None  # line instance, in the project CRS, undensified
        self.geoprofiles = None  # GeoProfiles()  # main instance for the geoprofiles
        self.grid = None

    def activate_action_window(self):

        current_item_text = self.actions_qtreewidget.currentItem().text(0)

        operation = self.profile_operations.get(current_item_text)

        if operation is not None:
            operation()

    def define_profiles_from_line_layer(self
                                        ):
        """
        Should define:
         - source type -> self.profile_track_source = ProfileSource.LINE_LAYER
         - list of undensified, inverted-in-case, CRS-projected lines

        """

        self.clear_rubberband_line()

        current_line_layers = loaded_line_layers()

        if len(current_line_layers) == 0:
            warn_qgis(
                self.plugin_name,
                "No available line layers"
            )
            return

        dialog = BaseDataSourceLineLayerDialog(
            self.plugin_name,
            current_line_layers
        )

        if dialog.exec_():
            line_qgsvectorlayer = self.extract_topographic_line_layer_params(dialog, current_line_layers)
        else:
            warn_qgis(
                self.plugin_name,
                "No defined line source"
            )
            return

        src_crs = line_qgsvectorlayer.sourceCrs()

        if src_crs.isGeographic():
            warn_qt(self,
                    self.plugin_name,
                    f"Line layer CRS is geographic ('{src_crs}') but planar one required")
            return

        self.reference_crs = src_crs

        qgis_lines, err = extract_selected_geometries(
            layer=line_qgsvectorlayer,
        )

        if err:
            warn_qgis(
                self.plugin_name,
                f"Line layer not read: {e!r} "
            )
            return

        if len(qgis_lines) != 1:
            warn_qgis(
                self.plugin_name,
                f"Just 1 line required ({len(qgis_lines)} found)"
            )
            return

        qgis_profile_line = qgis_lines[0]

        profile_lines, err = convert_line_from_qgis(qgis_profile_line)

        if err:
            warn_qt(
                self,
                self.plugin_name,
                f"{e!r} "
            )
            return

        self.profile_line = MultiLine(profile_lines).to_line().remove_coincident_points()

        self.profile_track_source_type = ProfileSource.LINE_LAYER

        ok_qgis(
            self.plugin_name,
            "Line layer read"
        )

    def extract_point_list(self,
                           dialog
                           ) -> Tuple[bool, Union[str, Ln]]:

        try:

            raw_point_string = dialog.point_list_qtextedit.toPlainText()
            raw_point_list = raw_point_string.split("\n")
            raw_point_list = [clean_string(str(unicode_txt)) for unicode_txt in raw_point_list]
            data_list = [rp for rp in raw_point_list if rp != ""]

            point_list = [to_float(xy_pair.split(",")) for xy_pair in data_list]
            line_2d = xytuple_list_to_line2d(point_list)

            return True, line_2d

        except Exception as e:

            return False, f"{e!r}"

    def track_from_text_window(self):

        self.clear_rubberband_line()

        self.init_topo_labels()

        dialog = BaseDataLoadPointListDialog(self.plugin_name)

        if dialog.exec_():

            success, result = self.extract_point_list(dialog)
            if not success:
                msg = result
                warn_qgis(
                    self.plugin_name,
                    msg
                )
                return
            line2d = result

        else:

            warn_qgis(
                self.plugin_name,
                "No defined text_window source"
            )
            return

        try:

            npts = line2d.num_points()
            if npts < 2:
                warn_qgis(
                    self.plugin_name,
                    "Defined text-window source with less than two points"
                )
                return

        except:

            warn_qgis(
                self.plugin_name,
                "No defined text-window source"
            )
            return

        self.profile_name = "Text input"
        self.profile_line = NamedLines([NamedLine(1, line2d)])
        self.profile_track_source_type = ProfileSource.POINT_LIST

        ok_qgis(
            self.plugin_name,
            "Point list read"
        )

    def qgis_rasterlayer_parameters(self,
        dem: 'qgis.core.QgsRasterLayer'
    ) -> Union[type(None), QGisRasterParameters]:

        result, err = extract_qgsrasterlayer_parameters(dem)

        if err:
            msg = f"Error with {dem.name()} as source: {e!r} "
            warn_qgis(
                self.plugin_name,
                msg
            )
            return None

        return result

    def extract_selected_dem(self,
                             dialog) -> Union[type(None), 'qgis.core.QgsRasterLayer']:

        try:
            return dialog.singleband_raster_layers_in_project[dialog.listDEMs_treeWidget.currentIndex().row()]
        except:
            return None

    def read_elevation_source(self):

        if self.profile_line is None:
            warn_qt(
                self,
                self.plugin_name,
                "Define profile line before DEM choice"
            )
            return

        current_raster_layers = loaded_monoband_raster_layers()
        if len(current_raster_layers) == 0:
            warn_qgis(
                self.plugin_name,
                "No loaded DEM"
            )
            return

        dialog = BaseDataSourceDEMsSelectionDialog(
            self.plugin_name,
            current_raster_layers
        )

        if dialog.exec_():
            selected_dem = self.extract_selected_dem(dialog)
        else:
            warn_qgis(
                self.plugin_name,
                "No chosen DEM"
            )
            return

        if selected_dem is None:
            warn_qgis(
                self.plugin_name,
                "No selected DEM"
            )
            return

        dem_crs = selected_dem.crs()

        if dem_crs != self.reference_crs:
            warn_qt(
                self,
                self.plugin_name,
                f"Chosen DEM CRS is {dem_crs} CRS while source profile has {self.reference_crs}.\nPlease use a DEM with same CRS as profile"
            )
            return

        dem_name = selected_dem.name()
        dem_pth = selected_dem.dataProvider().dataSourceUri()

        result, err = read_raster_band(raster_source=dem_pth)

        if err:
            warn_qgis(
                self.plugin_name,
                f"Unable to read raster band: {e!r} "
            )
            return

        geotransform, epsg, band_params, data = result

        self.grid = Grid(
            array=data,
            geotransform=geotransform,
            epsg_code=epsg
        )

        ok_qgis(
            self.plugin_name,
            f"Read {dem_name} (EPSG: {epsg})"
        )

    def extract_topographic_line_layer_params(
            self,
            dialog,
            current_line_layers):

        line_layer = current_line_layers[dialog.LineLayers_comboBox.currentIndex()]

        return line_layer

    def init_topo_labels(self):
        """
        Initialize topographic label and order parameters.

        :return:
        """

        self.profiles_labels = None
        self.profiles_order = None

    def check_pre_profile(self):

        for geoprofile in self.geoprofiles.geoprofiles:
            if not geoprofile.statistics_calculated:
                warn_qgis(
                    self.plugin_name,
                    "Profile statistics not yet calculated"
                )
                return False

        return True

    def calculate_profile_statistics(self,
        geoprofiles
    ):

        for geoprofile in geoprofiles:

            for name, line3d in geoprofile:

                statistics_elev = [get_statistics(p) for p in line3d.z_array()]
                statistics_dirslopes = [get_statistics(p) for p in line3d.dir_slopes()]
                statistics_slopes = [get_statistics(p) for p in np.absolute(line3d.dir_slopes())]

                profile_length = line3d.incremental_length_2d()[-1] - line3d.incremental_length_2d()[0]
                natural_elev_range = (
                    np.nanmin(np.array([ds_stats["min"] for ds_stats in statistics_elev])),
                    np.nanmax(np.array([ds_stats["max"] for ds_stats in statistics_elev])))

    def extract_graphical_params(self,
                                 dialog,
                                 ) -> None:
        # get profile plot parameters

        try:
            width = float(dialog.figure_width_qlineedit.text())
            if width > 0.0:
                self.graphical_params["figure"].width = width
        except:
            pass

        try:
            height = float(dialog.figure_height_qlineedit.text())
            if height > 0:
                self.graphical_params["figure"].height = height
        except:
            pass

        try:
            self.graphical_params["axis"].z_min = float(dialog.z_min_value_qlineedit.text())
        except:
            pass

        try:
            self.graphical_params["axis"].z_max = float(dialog.z_max_value_qlineedit.text())
        except:
            pass

        try:
            vertical_exaggeration = float(dialog.vertical_exxageration_ratio_qlineedit.text())
            if vertical_exaggeration > 0.0:
                self.graphical_params["axis"].vertical_exaggeration = vertical_exaggeration
        except:
            pass

        if hasattr(dialog, 'elevation_color') and dialog.elevation_color is not None:
            self.graphical_params["elevations"].color = dialog.elevation_color

        if hasattr(dialog, 'point_projections_style'):
            self.graphical_params["point projections"] = dialog.point_projections_style

        if hasattr(dialog, 'attitude_projections_style'):
            self.graphical_params["attitude projections"] = dialog.attitude_projections_style

        if hasattr(dialog, 'line_intersections_style'):
            self.graphical_params["line intersections"] = dialog.line_intersections_style

        if hasattr(dialog, "polygon_classification_colors"):
            self.graphical_params["polygon intersections"] = dialog.polygon_classification_colors

    def define_profiles_parameters(self):

        if self.profile_line is None:
            warn_qt(self,
                    self.plugin_name,
                    f"Profile line has not yet been defined")
            return

        if self.grid is None:
            warn_qt(self,
                    self.plugin_name,
                    f"DEM source has not yet been defined")
            return

        dialog = BaseDataProfilerSettingsDialog(
            self.plugin_name
        )

        if dialog.exec_():

            profiles_spacing = dialog.spacing_wdgt.value()
            num_parallel_profiles = dialog.num_parallel_profiles_wdgt.value()

        else:

            warn_qgis(
                self.plugin_name,
                "No parameters defined for parallel profiles "
            )
            return

        if num_parallel_profiles <= 0 or num_parallel_profiles % 2 != 1:
            warn_qgis(
                self.plugin_name,
                "Number of parallel profiles must be a positive odd number"
            )
            return

        if num_parallel_profiles > 1 and profiles_spacing <= 0.0:
            warn_qgis(
                self.plugin_name,
                "Spacing between parallel profiles cannot be zero when parallel profiles defined"
            )
            return

        profilers = Profilers(
            src_trace=self.profile_line,
            num_profiles=num_parallel_profiles,
            offset=profiles_spacing
        )

        self.geoprofiles = GeoProfiles(profilers=profilers)

        # one or more parallel profilers, one grid

        err = self.geoprofiles.sample_grid(
            grid=self.grid,
        )

        if err:
            error_qt(
                self,
                header=self.plugin_name,
                msg=f"Error: {e!r} "
            )
            return

        # ZTraces instance, stored into self.geoprofiles._topo_profiles

        # pre-process input data for profiles parameters

        profile_length = self.geoprofiles._topo_profiles.s_max()
        natural_elev_min = self.geoprofiles._topo_profiles.z_min()
        natural_elev_max = self.geoprofiles._topo_profiles.z_max()

        if np.isnan(profile_length) or profile_length == 0.0:
            error_qt(
                self,
                self.plugin_name,
                f"Max profile length is {profile_length}.\nCheck profile trace."
            )
            return

        if np.isnan(natural_elev_min) or np.isnan(natural_elev_max):
            error_qt(
                self,
                self.plugin_name,
                f"Max elevation in profile(s) is {natural_elev_max} and min is {natural_elev_min}.\nCheck profile trace location vs. DEM(s). [#1]"
            )
            return

        if natural_elev_max <= natural_elev_min:
            error_qt(
                self,
                self.plugin_name,
                "Error: min elevation greater than max elevation"
            )
            return

        # calculate proposed plot elevation range

        z_padding = 0.5
        delta_z = natural_elev_max - natural_elev_min

        if delta_z < 0.0:

            warn_qt(
                self,
                self.plugin_name,
                "Error: min elevation greater than max elevation"
            )
            return

        elif delta_z == 0.0:

            plot_z_min = floor(natural_elev_min) - 10
            plot_z_max = ceil(natural_elev_max) + 10

        else:

            plot_z_min = floor(natural_elev_min - delta_z * z_padding)
            plot_z_max = ceil(natural_elev_max + delta_z * z_padding)

        delta_plot_z = plot_z_max - plot_z_min

        # suggested exaggeration value

        w_to_h_rat = float(profile_length) / float(delta_plot_z)
        sugg_ve = 0.2 * w_to_h_rat

        self.graphical_params["axis"].z_min = plot_z_min
        self.graphical_params["axis"].z_max = plot_z_max
        self.graphical_params["axis"].vertical_exaggeration = sugg_ve

        ok_qgis(
            self.plugin_name,
            "Parallel profiles parameters defined"
        )

    def autodefine_profiles_parameters(self) -> Error:

        try:

            if self.geoprofiles is not None:
                return Error()

            if self.profile_line is None:
                return Error(
                    True,
                    caller_name(),
                    Exception(f"Profile line has not yet been defined")
                )

            if self.grid is None:
                return Error(
                    True,
                    caller_name(),
                    Exception(f"DEM source has not yet been defined")
                )

            profiles_spacing = 0.0
            num_parallel_profiles = 1

            profilers = Profilers(
                src_trace=self.profile_line,
                num_profiles=num_parallel_profiles,
                offset=profiles_spacing
            )

            self.geoprofiles = GeoProfiles(profilers=profilers)

            # one or more parallel profilers, one grid

            err = self.geoprofiles.sample_grid(
                grid=self.grid,
            )

            if err:
                return err

            # ZTraces instance, stored into self.geoprofiles._topo_profiles

            # pre-process input data for profiles parameters

            profile_length = self.geoprofiles._topo_profiles.s_max()
            natural_elev_min = self.geoprofiles._topo_profiles.z_min()
            natural_elev_max = self.geoprofiles._topo_profiles.z_max()

            if np.isnan(profile_length) or profile_length == 0.0:
                return Error(
                    True,
                    caller_name(),
                    Exception(f"Max profile length is {profile_length}.\nCheck profile trace")
                )

            if np.isnan(natural_elev_min) or np.isnan(natural_elev_max):
                return Error(
                    True,
                    caller_name(),
                    Exception(f"Max elevation in profile(s) is {natural_elev_max} and min is {natural_elev_min}.\nCheck profile trace location vs. DEM(s). [#1]")
                )

            if natural_elev_max <= natural_elev_min:
                return Error(
                    True,
                    caller_name(),
                    Exception(f"Min elevation {natural_elev_min} greater than max elevation {natural_elev_max}")
                )

            # calculate proposed plot elevation range

            z_padding = 0.5
            delta_z = natural_elev_max - natural_elev_min

            if delta_z < 0.0:
                return Error(
                    True,
                    caller_name(),
                    Exception(f"Min elevation {natural_elev_min} greater than max elevation {natural_elev_max}")
                )

            elif delta_z == 0.0:

                plot_z_min = floor(natural_elev_min) - 10
                plot_z_max = ceil(natural_elev_max) + 10

            else:

                plot_z_min = floor(natural_elev_min - delta_z * z_padding)
                plot_z_max = ceil(natural_elev_max + delta_z * z_padding)

            delta_plot_z = plot_z_max - plot_z_min

            # suggested exaggeration value

            w_to_h_rat = float(profile_length) / float(delta_plot_z)
            sugg_ve = 0.2 * w_to_h_rat

            self.graphical_params["axis"].z_min = plot_z_min
            self.graphical_params["axis"].z_max = plot_z_max
            self.graphical_params["axis"].vertical_exaggeration = sugg_ve

            return Error()

        except Exception as e:

            return Error(
                True,
                caller_name(),
                e,
                traceback.format_exc()
            )

    def define_total_graphical_parameters(self):
        "Updates in-place graphical parameters."

        dialog = StylesTotalGraphicalParamsDialog(
            self.plugin_name,
            self.geoprofiles,
            self.graphical_params
        )

        if dialog.exec_():
            self.extract_graphical_params(dialog)
        else:
            return

        ok_qgis(
            self.plugin_name,
            "Graphic parameters for profiles defined"
        )

    def save_graphic_parameters(self):

        try:

            file_pth = define_path_new_file(
                self,
                "Save graphical parameters as file",
                "*.pkl",
                "pickle (*.pkl *.PKL)"
            )

            with open(file_pth, 'wb') as handle:
                pickle.dump(
                    self.graphical_params,
                    handle,
                    protocol=pickle.HIGHEST_PROTOCOL
                )

            info_qgis(
                self.plugin_name,
                f"Graphical parameters saved in {file_pth}"
            )

        except Exception as e:

            warn_qgis(
                self.plugin_name,
                f"Error: {e!r}"
            )

    def load_graphic_parameters(self):

        try:

            file_pth = old_file_path(
                self,
                "Load graphical parameters from file",
                "*.pkl",
                "pickle (*.pkl *.PKL)"
            )

            with open(file_pth, 'rb') as handle:
                self.graphical_params = pickle.load(handle)

            info_qgis(
                self.plugin_name,
                f"Graphical parameters loaded from {file_pth}"
            )

        except Exception as e:

            warn_qgis(
                self.plugin_name,
                f"Error: {e!r}"
            )

    def plot_profiles(self):

        if self.geoprofiles is None:
            err = self.autodefine_profiles_parameters()
            if err:
                warn_qt(
                    self,
                    self.plugin_name,
                    f"{str(err)}"
                )
                return

        fig = profiles(
            self.geoprofiles,
            axis_params=self.graphical_params["axis"],
            linewidth=0.8,
            width=self.graphical_params["figure"].width,
            height=self.graphical_params["figure"].height,
            elevation_params=self.graphical_params["elevations"],
            points=self.graphical_params["point projections"],
            attitudes=self.graphical_params["attitude projections"],
            line_intersections=self.graphical_params["line intersections"],
            polygon_intersections=self.graphical_params["polygon intersections"],
        )

        if fig is None:
            warn_qt(
                self,
                self.plugin_name,
                "Resulting figure is null, so cannot plot it"
            )
            return

        """
        # create a figure and some subplots
        fig, axes = plt.subplots(ncols=4, nrows=5, figsize=(16, 16))
        for ax in axes.flatten():
            ax.plot([2, 3, 5, 1])
        """

        # pass the figure to the custom window

        a = ScrollableWindow(
            fig,
            "Profiles"
        )
        a.exec_()

    def digitize_rubberband_line(self):

        src_crs = QgsProject.instance().crs()
        if src_crs.isGeographic():
            warn_qt(self,
                    self.plugin_name,
                    f"Project CRS is geographic ('{src_crs}') but planar one required for profile digitation")
            return

        self.reference_crs = src_crs

        self.clear_rubberband_line()

        self.previous_maptool = self.canvas.mapTool()  # Save the standard map tool for restoring it at the end

        info_qgis(
            self.plugin_name,
            "Now you can digitize the trace on the map.\nLeft click: add point\nRight click: end adding point"
        )

        self.rubberband = QgsRubberBand(self.canvas)
        self.rubberband.setWidth(2)
        self.rubberband.setColor(QColor(Qt.red))

        self.digitize_maptool = MapDigitizeTool(self.canvas)
        self.canvas.setMapTool(self.digitize_maptool)

        self.digitize_maptool.moved.connect(self.canvas_refresh_profile_line)
        self.digitize_maptool.leftClicked.connect(self.profile_add_point)
        self.digitize_maptool.rightClicked.connect(self.canvas_end_profile_line)

    def canvas_refresh_profile_line(self, position):

        x, y = xy_from_canvas(self.canvas, position)

        self.refresh_rubberband(
            self.profile_canvas_points_x + [x],
            self.profile_canvas_points_y + [y]
        )

    def profile_add_point(self, position):

        x, y = xy_from_canvas(self.canvas, position)
        self.profile_canvas_points_x.append(x)
        self.profile_canvas_points_y.append(y)

    def canvas_end_profile_line(self):

        self.refresh_rubberband(
            self.profile_canvas_points_x,
            self.profile_canvas_points_y,
        )

        self.line_from_digitation = None

        if len(self.profile_canvas_points_x) <= 1:
            warn_qgis(
                self.plugin_name,
                "At least two non-coincident points are required"
            )
            return

        raw_line = Ln(list(zip(self.profile_canvas_points_x, self.profile_canvas_points_y))).remove_coincident_points()

        if raw_line.num_points() <= 1:
            warn_qgis(
                self.plugin_name,
                "Just one non-coincident point"
            )
            return

        self.profile_canvas_points_x = []
        self.profile_canvas_points_y = []
        self.restore_previous_map_tool()

        self.line_from_digitation = raw_line
        self.profile_name = "Digitized line"
        self.profile_line = raw_line
        self.profile_track_source_type = ProfileSource.DIGITATION

    def restore_previous_map_tool(self):

        self.canvas.unsetMapTool(self.digitize_maptool)
        self.canvas.setMapTool(self.previous_maptool)

    def refresh_rubberband(self,
                           x_list,
                           y_list
                           ):

        self.rubberband.reset(QgsWkbTypes.LineGeometry)
        for x, y in zip(x_list, y_list):
            self.rubberband.addPoint(QgsPointXY(x, y))

    def clear_rubberband_line(self):

        self.profile_track_source_type = ProfileSource.UNDEFINED

        self.profile_canvas_points_x = []
        self.profile_canvas_points_y = []

        self.line_from_digitation = None

        try:
            self.rubberband.reset()
        except:
            pass

    def save_rubberband_line(self):

        def output_profile_line(
                output_format,
                output_filepath,
                pts2dt,
                proj_sr
        ):

            points = [[n, pt2dt.x, pt2dt.y] for n, pt2dt in enumerate(pts2dt)]
            if output_format == "csv":
                success, msg = write_generic_csv(
                    output_filepath,
                    ['id', 'x', 'y'],
                    points
                )
                if not success:
                    warn_qgis(
                        self.plugin_name,
                        msg
                    )
            elif output_format == "shapefile - line":
                success, msg = write_rubberband_profile_lnshp(
                    output_filepath,
                    ['id'],
                    points,
                    proj_sr)
                if not success:
                    warn_qgis(
                        self.plugin_name,
                        msg
                    )
            else:
                error_qt(
                    self,
                    self.plugin_name,
                    "Debug: error in export format"
                )
                return

            if success:
                info_qgis(
                    self.plugin_name,
                    "Ln saved"
                )

        def extract_format_type():

            if dialog.outtype_shapefile_line_QRadioButton.isChecked():
                return "shapefile - line"
            elif dialog.outtype_csv_QRadioButton.isChecked():
                return "csv"
            else:
                return ""

        if self.line_from_digitation is None:

            warn_qgis(
                self.plugin_name,
                "No available line to save [1]"
            )
            return

        elif self.line_from_digitation.num_points() < 2:

            warn_qgis(
                self.plugin_name,
                "No available line to save [2]"
            )
            return

        dialog = ExportLineDataDialog(self.plugin_name)
        if dialog.exec_():
            output_format = extract_format_type()
            if output_format == "":
                warn_qgis(
                    self.plugin_name,
                    "Error in output format"
                )
                return
            output_filepath = dialog.outpath_QLineEdit.text()
            if len(output_filepath) == 0:
                warn_qgis(
                    self.plugin_name,
                    "Error in output path"
                )
                return
            add_to_project = dialog.load_output_checkBox.isChecked()
        else:
            warn_qgis(
                self.plugin_name,
                "No export defined"
            )
            return

        # get project CRS information
        project_crs_osr = proj4str()

        output_profile_line(
            output_format,
            output_filepath,
            self.line_from_digitation.pts(),
            project_crs_osr)

        # add theme to QGis project
        if output_format == "shapefile - line" and add_to_project:
            try:

                digitized_line_layer = QgsVectorLayer(output_filepath,
                                                      QFileInfo(output_filepath).baseName(),
                                                      "ogr")
                QgsProject.instance().addMapLayer(digitized_line_layer)

            except:

                error_qt(
                    self,
                    self.plugin_name,
                    "Unable to load layer in project"
                )
                return

    def project_points(self):
        """
        Project the points of a point layer as simple point locations.

        """

        if self.geoprofiles is None:
            err = self.autodefine_profiles_parameters()
            if err:
                warn_qt(
                    self,
                    self.plugin_name,
                    f"{str(err)}"
                )
                return

        current_point_layers = loaded_point_layers()

        dialog = GeologicalDataProjectPointsDialog(
            self.plugin_name,
            current_point_layers
        )

        if dialog.exec_():
            ok_qgis(
                self.plugin_name,
                "Project points defined."
            )
        else:
            warn_qgis(
                    self.plugin_name,
                    "No point projection defined"
            )
            return

        # read parameters

        point_layer, z_field_name, max_distance_value, id_field_name = self.extract_point_projection_params(
            dialog,
            current_point_layers
        )

        # check CRS

        layer_crs = point_layer.crs()

        if layer_crs != self.reference_crs:
            warn_qt(
                self,
                self.plugin_name,
                f"Chosen point layer CRS is {layer_crs} CRS while source profile has {self.reference_crs}.\nPlease use a point layer with same CRS as profile"
            )
            return

        # read input data

        data_points = read_qgis_pt_layer(
            pt_layer=point_layer,
            field_list=[z_field_name, id_field_name]
        )

        # parsing data

        points = [(rec_id, Point(x, y, z)) for x, y, z, rec_id in data_points]

        # calculate projections

        err = self.geoprofiles.project_points(
            data=points,
            max_profile_distance=max_distance_value,  # meters
        )

        if err:
            error_qt(
                self,
                header=self.plugin_name,
                msg=f"Error: {e!r} "
            )
            return

        ok_qgis(
            self.plugin_name,
            "Completed point projections calculation. Now you may plot again."
        )

    def extract_point_projection_params(
            self,
            dialog,
            current_point_layers):

        point_layer = current_point_layers[dialog.point_layer_combobox.currentIndex()]
        z_field_name = dialog.z_fld_comboBox.currentText()
        max_distance_value = dialog.max_profile_distance_qdoublespinbox.value()
        label_field_name = dialog.point_label_fld_comboBox.currentText()

        return point_layer, z_field_name, max_distance_value, label_field_name

    def extract_attitude_projections_params(
            self,
            dialog,
            current_point_layers):

        point_layer = current_point_layers[dialog.point_layer_combobox.currentIndex()]
        max_profile_distance = dialog.max_profile_distance_qdoublespinbox.value()
        label_field_name = dialog.point_label_fld_comboBox.currentText()
        use_dipdir = dialog.use_dipdir_qradiobuttom.isChecked()
        use_rhr_strike = dialog.use_rhr_strike_qradiobutton.isChecked()
        orient_fld_name = dialog.orient_fld_combobox.currentText()
        dipangle_fld_name = dialog.dipangle_fld_combobox.currentText()

        return point_layer, max_profile_distance, label_field_name, use_dipdir, use_rhr_strike, orient_fld_name, dipangle_fld_name

    def project_attitudes(self):
        """
        Project the points of a point layer as simple point locations.

        """

        if self.geoprofiles is None:
            err = self.autodefine_profiles_parameters()
            if err:
                warn_qt(
                    self,
                    self.plugin_name,
                    f"{str(err)}"
                )
                return

        current_point_layers = loaded_point_layers()

        dialog = GeologicalDataProjectAttitudesDialog(
            self.plugin_name,
            current_point_layers
        )

        if dialog.exec_():
            ok_qgis(
                self.plugin_name,
                "Project attitudes defined."
            )
        else:
            warn_qgis(
                    self.plugin_name,
                    "No point attitudes projection defined")
            return

        # read parameters

        point_layer, max_profile_distance, label_field_name, use_dipdir, use_rhr_strike, orient_fld_name, dipangle_fld_name = self.extract_attitude_projections_params(
            dialog,
            current_point_layers
        )

        # check CRS

        layer_crs = point_layer.crs()

        if layer_crs != self.reference_crs:
            warn_qt(
                self,
                self.plugin_name,
                f"Chosen attitude layer CRS is {layer_crs} CRS while source profile has {self.reference_crs}.\nPlease use an attitude layer with same CRS as profile"
            )
            return

        # read input data

        data_points = read_qgis_pt_layer(
            pt_layer=point_layer,
            field_list=[label_field_name, orient_fld_name, dipangle_fld_name]
        )

        parsed_points = []

        for ndx, (x, y, label, orientation, dipangle) in enumerate(data_points):

            pt3d = self.grid.interpolate_bilinear_point(
                pt=Point(x, y)
            )

            if pt3d:

                plane = Plane(
                    azim=orientation,
                    dip_ang=dipangle,
                    is_rhr_strike=use_rhr_strike)

                parsed_points.append((label, pt3d, plane))

        err = self.geoprofiles.project_points(
            data=parsed_points,
            max_profile_distance=max_profile_distance,
            projection_method=ProjectionMethod.NEAREST,
            input_type=PointsInput.ATTITUDES,
            cat_key="attitudes",
        )

        if err:
            error_qt(
                self,
                self.plugin_name,
                str(err)
            )
            return

        ok_qgis(
            self.plugin_name,
            "Attitudes projections calculated. Now you can plot again."
        )

    def intersect_lines(self):
        """
        Intersects a line layer (e.g., faults) with the profiles.
        """

        if self.geoprofiles is None:
            err = self.autodefine_profiles_parameters()
            if err:
                warn_qt(
                    self,
                    self.plugin_name,
                    f"{str(err)}"
                )
                return

        current_line_layers = loaded_line_layers()

        if len(current_line_layers) == 0:
            warn_qgis(
                self.plugin_name,
                "No available line layers"
            )
            return

        dialog = GeologicalDataIntersectLinesDialog(
            self.plugin_name,
            current_line_layers
        )

        if dialog.exec_():
            line_qgsvectorlayer, id_field_ndx = self.extract_intersection_line_layer_params(dialog, current_line_layers)
        else:
            warn_qgis(
                self.plugin_name,
                "No defined line source"
            )
            return

        # check CRS

        layer_crs = line_qgsvectorlayer.crs()

        if layer_crs != self.reference_crs:
            warn_qt(
                self,
                self.plugin_name,
                f"Chosen line layer CRS is {layer_crs} CRS while source profile has {self.reference_crs}.\nPlease use a line layer with same CRS as profile"
            )
            return

        # process data

        success, results = try_line_geoms_with_field_infos(
            line_layer=line_qgsvectorlayer,
            order_field_ndx=id_field_ndx
        )

        if not success:
            msg = results
            warn_qgis(
                self.plugin_name,
                msg
            )
            return

        coordinates, ids = results

        loaded_lines = defaultdict(list)

        for (id, (type, data)) in zip(ids, coordinates):
            if type == "multiline":
                lines = []
                for ln_data in data:
                    ln = Ln.fromCoordinates(ln_data)
                    lines.append(ln)
            elif type == "line":
                lines = [Ln.fromCoordinates(data)]
            else:
                warn_qgis(
                    self.plugin_name,
                    f"Line type is {type}, that is unhandled"
                )
                return

            loaded_lines[id].extend(lines)

        err = self.geoprofiles.intersect_lines(
            loaded_lines
        )

        if err:
            warn_qgis(
                self.plugin_name,
                f"Error: {e!r} "
            )
            return

        info_qgis(
            self.plugin_name,
            "Line intersections calculated. You can plot again."
        )

    def extract_intersection_line_layer_params(
            self,
            dialog,
            current_line_layers):

        line_layer = current_line_layers[dialog.LineLayers_comboBox.currentIndex()]
        id_field_ndx = dialog.inters_input_id_fld_line_comboBox.currentIndex()

        return line_layer, id_field_ndx

    def intersect_polygons(self):
        """
        Intersects a polygonal layer (e.g., geological outcrops) with the profiles.

        """

        if self.geoprofiles is None:
            err = self.autodefine_profiles_parameters()
            if err:
                warn_qt(
                    self,
                    self.plugin_name,
                    f"{str(err)}"
                )
                return

        current_polygonal_layers = loaded_polygon_layers()

        if len(current_polygonal_layers) == 0:
            warn_qgis(
                self.plugin_name,
                "No available polygonal layers"
            )
            return

        dialog = GeologicalDataIntersectPolygonsDialog(
            self.plugin_name,
            current_polygonal_layers
        )

        if dialog.exec_():
            polygon_qgsvectorlayer, id_fld_name = self.extract_intersection_polygon_layer_params(dialog, current_polygonal_layers)
            self.graphical_params["polygon intersections"] = dict()
        else:
            warn_qgis(
                self.plugin_name,
                "No defined polygonal source"
            )
            return

        # check CRS

        layer_crs = polygon_qgsvectorlayer.crs()

        if layer_crs != self.reference_crs:
            warn_qt(
                self,
                self.plugin_name,
                f"Chosen polygon layer CRS is {layer_crs} CRS while source profile has {self.reference_crs}.\nPlease use a polygon layer with same CRS as profile"
            )
            return

        # process

        features = polygon_qgsvectorlayer.getFeatures()

        polygons = defaultdict(list)

        for f in features:

            name = f.attribute(id_fld_name)

            if f.hasGeometry():

                geom = f.geometry()

                feature_polygons = qgismpolygon_to_polygons(geom)

                polygons[name].extend(feature_polygons)

        # regular code for determining intersections

        err = self.geoprofiles.intersect_polygons(
            polygons
        )

        if err:
            warn_qgis(
                self.plugin_name,
                f"Error: {e!r} "
            )
            return

        ok_qgis(
            self.plugin_name,
            "Completed polygons intersections calculation. Now you may plot again."
        )

    def extract_intersection_polygon_layer_params(
            self,
            dialog,
            current_polygon_layers):

        polygon_layer = current_polygon_layers[dialog.PolygonLayers_comboBox.currentIndex()]
        id_field_name = dialog.inters_input_id_fld_polygon_comboBox.currentText()

        return polygon_layer, id_field_name

    def export_topographic_profiles(self):

        def extract_format_type():

            if dialog.outtype_shapefile_line_QRadioButton.isChecked():
                return "lines"
            else:
                return "points"

        if self.geoprofiles is None:
            warn_qt(
                self,
                self.plugin_name,
                "No profile is available for export"
            )
            return

        dialog = ExportTopographicProfileDialog(
            self.plugin_name,
            None
        )

        if dialog.exec_():

            output_format = extract_format_type()

            output_filepath = dialog.outpath_QLineEdit.text()
            if len(output_filepath) < 5:
                warn_qt(
                    self,
                    self.plugin_name,
                    f"Error in output path: only {len(output_filepath)} characters"
                )
                return

            add_to_project = dialog.load_output_checkBox.isChecked()

        else:
            warn_qt(
                self,
                self.plugin_name,
                "No export defined"
            )
            return

        points_l2, err = self.geoprofiles._profilers.profile_grid_as_pts3d(
            self.grid
        )

        if err:
            error_qt(
                self,
                self.plugin_name,
                f"{e!r} "
            )
            return

        if output_format == "lines":

            err = create_write_line_shapefile_from_points(
                shapefile_path=output_filepath,
                id_field=dict(name='id', ogr_type='ogr.OFTInteger'),
                records_values=points_l2,
                epsg_code=None,
                lines_are_3d=True
            )

            if err:
                warn_qt(
                    self,
                    self.plugin_name,
                    f"{e!r} "
                )
                return

        else:  # points

            err = create_write_point_shapefile_from_points(
                shapefile_path=output_filepath,
                id_field=dict(name='id', ogr_type='ogr.OFTInteger'),
                records_values=points_l2,
                epsg_code=None,
                points_are_3d=True
            )

            if err:
                warn_qt(
                    self,
                    self.plugin_name,
                    f"{e!r} "
                )
                return

        # add theme to QGis project

        if add_to_project:

            try:

                qgs_vector_layer = QgsVectorLayer(output_filepath,
                                                      QFileInfo(output_filepath).baseName(),
                                                      "ogr")

                QgsProject.instance().addMapLayer(qgs_vector_layer)

            except:

                error_qt(
                    self,
                    self.plugin_name,
                    "Unable to load layer in project"
                )
                return

    def open_help(self):

        dialog = HelpDialog(
            self.plugin_name,
            self.current_directory
        )
        dialog.exec_()

    def closeEvent(self, evnt):

        self.clear_rubberband_line()

        """
        try:
            
            self.digitize_maptool.moved.disconnect(self.canvas_refresh_profile_line)
            self.digitize_maptool.leftClicked.disconnect(self.profile_add_point)
            self.digitize_maptool.rightClicked.disconnect(self.canvas_end_profile_line)
            self.restore_previous_map_tool()

        except:

            pass
        """


class StylesPolygonIntersectionsParamsDialog(QDialog):

    colors = ["darkseagreen", "darkgoldenrod", "darkviolet", "hotpink", "powderblue", "yellowgreen", "palevioletred",
              "seagreen", "darkturquoise", "beige", "darkkhaki", "red", "yellow", "magenta", "blue", "cyan",
              "chartreuse"]

    def __init__(
            self,
            plugin_name,
            current_graphical_params,
            polygon_classification_set,
            parent=None
    ):

        super(StylesPolygonIntersectionsParamsDialog, self).__init__(parent)

        self.plugin_name = plugin_name
        self.current_graphical_params = current_graphical_params
        self.polygon_classifications = list(polygon_classification_set)

        self.polygon_classifications_treeWidget = QTreeWidget()
        self.polygon_classifications_treeWidget.setColumnCount(2)
        self.polygon_classifications_treeWidget.headerItem().setText(0, "Name")
        self.polygon_classifications_treeWidget.headerItem().setText(1, "Color")
        self.polygon_classifications_treeWidget.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
        self.polygon_classifications_treeWidget.setDragEnabled(False)
        self.polygon_classifications_treeWidget.setDragDropMode(QAbstractItemView.NoDragDrop)
        self.polygon_classifications_treeWidget.setAlternatingRowColors(True)
        self.polygon_classifications_treeWidget.setTextElideMode(Qt.ElideLeft)

        self.update_classification_colors_treewidget()

        self.polygon_classifications_treeWidget.resizeColumnToContents(0)
        self.polygon_classifications_treeWidget.resizeColumnToContents(1)

        okButton = QPushButton("&OK")
        cancelButton = QPushButton("Cancel")

        buttonLayout = QHBoxLayout()
        buttonLayout.addStretch()
        buttonLayout.addWidget(okButton)
        buttonLayout.addWidget(cancelButton)

        layout = QGridLayout()

        layout.addWidget(self.polygon_classifications_treeWidget, 0, 0, 1, 3)
        layout.addLayout(buttonLayout, 1, 0, 1, 3)

        self.setLayout(layout)

        okButton.clicked.connect(self.accept)
        cancelButton.clicked.connect(self.reject)

        self.setWindowTitle("Polygon intersection colors")

    def update_classification_colors_treewidget(self):

        if len(StylesPolygonIntersectionsParamsDialog.colors) < len(self.polygon_classifications):
            dupl_factor = 1 + int(len(self.polygon_classifications) / len(StylesPolygonIntersectionsParamsDialog.colors))
            curr_colors = dupl_factor * StylesPolygonIntersectionsParamsDialog.colors
        else:
            curr_colors = StylesPolygonIntersectionsParamsDialog.colors

        self.polygon_classifications_treeWidget.clear()

        for classification_id, color in zip(self.polygon_classifications, curr_colors):
            tree_item = QTreeWidgetItem(self.polygon_classifications_treeWidget)
            tree_item.setText(0, str(classification_id))

            color_QgsColorButton = QgsColorButton()
            if classification_id in self.current_graphical_params["polygon intersections"]:
                color = QColor.fromRgbF(*self.current_graphical_params["polygon intersections"][classification_id])
            color_QgsColorButton.setColor(QColor(color))
            self.polygon_classifications_treeWidget.setItemWidget(tree_item, 1, color_QgsColorButton)


class BaseDataSourceDEMsSelectionDialog(QDialog):

    def __init__(self,
                 plugin_name,
                 raster_layers,
                 parent=None
                 ):

        super(BaseDataSourceDEMsSelectionDialog, self).__init__(parent)

        self.plugin_name = plugin_name

        self.singleband_raster_layers_in_project = raster_layers

        self.listDEMs_treeWidget = QTreeWidget()
        self.listDEMs_treeWidget.setColumnCount(1)
        self.listDEMs_treeWidget.headerItem().setText(0, "Name")
        self.listDEMs_treeWidget.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
        self.listDEMs_treeWidget.setDragEnabled(False)
        self.listDEMs_treeWidget.setDragDropMode(QAbstractItemView.NoDragDrop)
        self.listDEMs_treeWidget.setAlternatingRowColors(True)
        self.listDEMs_treeWidget.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.listDEMs_treeWidget.setSelectionMode(QAbstractItemView.SingleSelection)
        self.listDEMs_treeWidget.setTextElideMode(Qt.ElideLeft)

        self.populate_raster_layer_treewidget()

        self.listDEMs_treeWidget.resizeColumnToContents(0)

        okButton = QPushButton("&OK")
        cancelButton = QPushButton("Cancel")

        buttonLayout = QHBoxLayout()
        buttonLayout.addStretch()
        buttonLayout.addWidget(okButton)
        buttonLayout.addWidget(cancelButton)

        layout = QGridLayout()

        layout.addWidget(
            self.listDEMs_treeWidget,
            0, 0, 1, 3)
        layout.addLayout(
            buttonLayout,
            1, 0, 1, 3)

        self.setLayout(layout)

        okButton.clicked.connect(self.accept)
        cancelButton.clicked.connect(self.reject)

        self.setWindowTitle("Define elevation source")

    def populate_raster_layer_treewidget(self):

        self.listDEMs_treeWidget.clear()

        for raster_layer in self.singleband_raster_layers_in_project:
            tree_item = QTreeWidgetItem(self.listDEMs_treeWidget)
            tree_item.setText(0, raster_layer.name())


class BaseDataSourceLineLayerDialog(QDialog):

    def __init__(self,
                 plugin_name,
                 current_line_layers,
                 parent=None
                 ):

        super(BaseDataSourceLineLayerDialog, self).__init__(parent)

        self.plugin_name = plugin_name
        self.current_line_layers = current_line_layers

        layout = QGridLayout()

        layout.addWidget(
            QLabel(self.tr("Input line layer:")),
            0, 0, 1, 1)

        self.LineLayers_comboBox = QComboBox()
        layout.addWidget(
            self.LineLayers_comboBox,
            0, 1, 1, 3)
        self.refresh_input_profile_layer_combobox()

        # Ok/Cancel choices

        okButton = QPushButton("&OK")
        cancelButton = QPushButton("Cancel")

        buttonLayout = QHBoxLayout()
        buttonLayout.addStretch()
        buttonLayout.addWidget(okButton)
        buttonLayout.addWidget(cancelButton)

        layout.addLayout(
            buttonLayout,
            1, 0, 1, 3)

        self.setLayout(layout)

        okButton.clicked.connect(self.accept)
        cancelButton.clicked.connect(self.reject)

        self.setWindowTitle("Define source line layer")

    def refresh_input_profile_layer_combobox(self):

        self.LineLayers_comboBox.clear()

        for layer in self.current_line_layers:
            self.LineLayers_comboBox.addItem(layer.name())

        shape_qgis_ndx = self.LineLayers_comboBox.currentIndex()
        self.line_shape = self.current_line_layers[shape_qgis_ndx]

    def refresh_order_field_combobox(self):

        self.Trace2D_order_field_comboBox.clear()
        self.Trace2D_order_field_comboBox.addItem('--optional--')

        shape_qgis_ndx = self.LineLayers_comboBox.currentIndex()
        self.line_shape = self.current_line_layers[shape_qgis_ndx]

        line_layer_field_list = self.line_shape.dataProvider().fields().toList()
        for field in line_layer_field_list:
            self.Trace2D_order_field_comboBox.addItem(field.name())

    def refresh_name_field_combobox(self):

        self.Trace2D_name_field_comboBox.clear()
        self.Trace2D_name_field_comboBox.addItem('--optional--')

        shape_qgis_ndx = self.LineLayers_comboBox.currentIndex()
        self.line_shape = self.current_line_layers[shape_qgis_ndx]

        line_layer_field_list = self.line_shape.dataProvider().fields().toList()
        for field in line_layer_field_list:
            self.Trace2D_name_field_comboBox.addItem(field.name())

    def refresh_label_field_combobox(self):

        self.Trace2D_label_field_comboBox.clear()
        self.Trace2D_label_field_comboBox.addItem('--optional--')

        shape_qgis_ndx = self.LineLayers_comboBox.currentIndex()
        self.line_shape = self.current_line_layers[shape_qgis_ndx]

        line_layer_field_list = self.line_shape.dataProvider().fields().toList()
        for field in line_layer_field_list:
            self.Trace2D_label_field_comboBox.addItem(field.name())


class GeologicalDataIntersectLinesDialog(QDialog):
    """
    Intersects the profiles with a line layer: defines input parameters.
    """

    def __init__(self,
                 plugin_name,
                 current_line_layers,
                 parent=None
                 ):

        super(GeologicalDataIntersectLinesDialog, self).__init__(parent)

        self.plugin_name = plugin_name
        self.current_line_layers = current_line_layers

        # GUI

        layout = QGridLayout()

        layout.addWidget(
            QLabel(self.tr("Input line layer:")),
            0, 0, 1, 1)

        self.LineLayers_comboBox = QComboBox()
        layout.addWidget(
            self.LineLayers_comboBox,
            0, 1, 1, 3)
        self.refresh_input_profile_layer_combobox()

        layout.addWidget(QLabel("Id field"), 1, 0, 1, 1)
        self.inters_input_id_fld_line_comboBox = QComboBox()
        layout.addWidget(
            self.inters_input_id_fld_line_comboBox,
            1, 1, 1, 3
        )

        self.refresh_id_field_combobox()
        self.LineLayers_comboBox.currentIndexChanged.connect(self.refresh_id_field_combobox)

        # Ok/Cancel choices

        okButton = QPushButton("&OK")
        cancelButton = QPushButton("Cancel")

        buttonLayout = QHBoxLayout()
        buttonLayout.addStretch()
        buttonLayout.addWidget(okButton)
        buttonLayout.addWidget(cancelButton)

        layout.addLayout(
            buttonLayout,
            3, 0, 1, 3)

        self.setLayout(layout)

        okButton.clicked.connect(self.accept)
        cancelButton.clicked.connect(self.reject)

        self.setWindowTitle("Intersection line layer")

    def refresh_input_profile_layer_combobox(self):

        self.LineLayers_comboBox.clear()

        for layer in self.current_line_layers:
            self.LineLayers_comboBox.addItem(layer.name())

        shape_qgis_ndx = self.LineLayers_comboBox.currentIndex()
        self.line_shape = self.current_line_layers[shape_qgis_ndx]

    def refresh_id_field_combobox(self):

        self.inters_input_id_fld_line_comboBox.clear()
        #self.inters_input_id_fld_line_comboBox.addItem('--optional--')

        shape_qgis_ndx = self.LineLayers_comboBox.currentIndex()
        self.line_shape = self.current_line_layers[shape_qgis_ndx]

        line_layer_field_list = self.line_shape.dataProvider().fields().toList()
        for field in line_layer_field_list:
            self.inters_input_id_fld_line_comboBox.addItem(field.name())


class GeologicalDataIntersectPolygonsDialog(QDialog):
    """
    Intersects the profiles with a polygon layer:
    defines input parameters.
    """

    def __init__(self,
                 plugin_name,
                 current_polygon_layers,
                 parent=None
                 ):

        super(GeologicalDataIntersectPolygonsDialog, self).__init__(parent)

        self.plugin_name = plugin_name
        self.current_polygon_layers = current_polygon_layers

        # GUI

        layout = QGridLayout()

        layout.addWidget(
            QLabel(self.tr("Input polygon layer:")),
            0, 0, 1, 1)

        self.PolygonLayers_comboBox = QComboBox()
        layout.addWidget(
            self.PolygonLayers_comboBox,
            0, 1, 1, 3)
        self.refresh_input_profile_layer_combobox()

        layout.addWidget(QLabel("Id field"), 1, 0, 1, 1)
        self.inters_input_id_fld_polygon_comboBox = QComboBox()
        layout.addWidget(
            self.inters_input_id_fld_polygon_comboBox,
            1, 1, 1, 3
        )

        self.refresh_id_field_combobox()
        self.PolygonLayers_comboBox.currentIndexChanged.connect(self.refresh_id_field_combobox)

        # Ok/Cancel choices

        okButton = QPushButton("&OK")
        cancelButton = QPushButton("Cancel")

        buttonLayout = QHBoxLayout()
        buttonLayout.addStretch()
        buttonLayout.addWidget(okButton)
        buttonLayout.addWidget(cancelButton)

        layout.addLayout(
            buttonLayout,
            3, 0, 1, 3)

        self.setLayout(layout)

        okButton.clicked.connect(self.accept)
        cancelButton.clicked.connect(self.reject)

        self.setWindowTitle("Intersection polygon layer")

    def refresh_input_profile_layer_combobox(self):

        self.PolygonLayers_comboBox.clear()

        for layer in self.current_polygon_layers:
            self.PolygonLayers_comboBox.addItem(layer.name())

        shape_qgis_ndx = self.PolygonLayers_comboBox.currentIndex()
        self.pol_shape = self.current_polygon_layers[shape_qgis_ndx]

    def refresh_id_field_combobox(self):

        self.inters_input_id_fld_polygon_comboBox.clear()

        shape_qgis_ndx = self.PolygonLayers_comboBox.currentIndex()
        self.pol_shape = self.current_polygon_layers[shape_qgis_ndx]

        polygon_layer_field_list = self.pol_shape.dataProvider().fields().toList()
        for field in polygon_layer_field_list:
            self.inters_input_id_fld_polygon_comboBox.addItem(field.name())


class BaseDataLoadPointListDialog(QDialog):

    def __init__(self, plugin_name, parent=None):

        super(BaseDataLoadPointListDialog, self).__init__(parent)

        self.plugin_name = plugin_name
        layout = QGridLayout()

        layout.addWidget(
            QLabel(self.tr("Point list, with at least two points.")),
            0, 0, 1, 1)
        layout.addWidget(
            QLabel(self.tr("Each point is defined by a comma-separated, x-y coordinate pair (same CRS as project), one for each row")), 1, 0,
            1, 1)
        layout.addWidget(
            QLabel(self.tr("Example:\n549242.7, 242942.2\n578370.3, 322634.5")),
            2, 0, 1, 1)

        self.point_list_qtextedit = QTextEdit()
        layout.addWidget(
            self.point_list_qtextedit,
            3, 0, 1, 1)

        okButton = QPushButton("&OK")
        cancelButton = QPushButton("Cancel")

        buttonLayout = QHBoxLayout()
        buttonLayout.addStretch()
        buttonLayout.addWidget(okButton)
        buttonLayout.addWidget(cancelButton)

        layout.addLayout(
            buttonLayout,
            4, 0, 1, 3)

        self.setLayout(layout)

        okButton.clicked.connect(self.accept)
        cancelButton.clicked.connect(self.reject)

        self.setWindowTitle("Point list")


class StylesElevationsParamsDialog(QDialog):

    def __init__(self,
                 plugin_name,
                 current_graphical_params,
                 parent=None
                 ):

        super(StylesElevationsParamsDialog, self).__init__(parent)

        self.plugin_name = plugin_name
        self.current_graphical_params = current_graphical_params

        self.qtwdElevationLayers = QTreeWidget()
        self.qtwdElevationLayers.setColumnCount(2)
        self.qtwdElevationLayers.headerItem().setText(0, "Type")
        self.qtwdElevationLayers.headerItem().setText(1, "Color")
        self.qtwdElevationLayers.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
        self.qtwdElevationLayers.setDragEnabled(False)
        self.qtwdElevationLayers.setDragDropMode(QAbstractItemView.NoDragDrop)
        self.qtwdElevationLayers.setAlternatingRowColors(True)
        self.qtwdElevationLayers.setSelectionMode(QAbstractItemView.SingleSelection)
        self.qtwdElevationLayers.setTextElideMode(Qt.ElideLeft)

        self.populate_elevation_layer_treewidget()

        self.qtwdElevationLayers.resizeColumnToContents(0)
        self.qtwdElevationLayers.resizeColumnToContents(1)

        okButton = QPushButton("&OK")
        cancelButton = QPushButton("Cancel")

        buttonLayout = QHBoxLayout()
        buttonLayout.addStretch()
        buttonLayout.addWidget(okButton)
        buttonLayout.addWidget(cancelButton)

        layout = QGridLayout()

        layout.addWidget(
            self.qtwdElevationLayers,
            0, 0, 1, 3)
        layout.addLayout(
            buttonLayout,
            1, 0, 1, 3)

        self.setLayout(layout)

        okButton.clicked.connect(self.accept)
        cancelButton.clicked.connect(self.reject)

        self.setWindowTitle("Elevations style")

    def populate_elevation_layer_treewidget(self):

        self.qtwdElevationLayers.clear()

        for ndx, layer_name in enumerate(["Elevation"]):
            tree_item = QTreeWidgetItem(self.qtwdElevationLayers)
            tree_item.setText(0, layer_name)
            color_button = QgsColorButton()
            color_button.setColor(self.current_graphical_params["elevations"].color)
            self.qtwdElevationLayers.setItemWidget(tree_item, 1, color_button)


class GeologicalDataProjectPointsDialog(QDialog):

    def __init__(self,
                 plugin_name,
                 current_point_layers: List,
                 parent=None
                 ):

        super(GeologicalDataProjectPointsDialog, self).__init__(parent)

        self.plugin_name = plugin_name
        self.current_point_layers = current_point_layers

        # gui definition

        main_layout = QGridLayout()

        # input point geological layer

        main_layout.addWidget(QLabel("Layer "),
                                       0, 0, 1, 1)

        self.point_layer_combobox = QComboBox()

        main_layout.addWidget(self.point_layer_combobox, 0, 1, 1, 1)

        main_layout.addWidget(QLabel("Z field "), 1, 0, 1, 1)

        self.z_fld_comboBox = QComboBox()
        main_layout.addWidget(self.z_fld_comboBox, 1, 1, 1, 1)

        main_layout.addWidget(QLabel("Max distance from profile"), 2, 0, 1, 1)
        self.max_profile_distance_qdoublespinbox = QDoubleSpinBox()
        self.max_profile_distance_qdoublespinbox.setMaximum(10000)
        self.max_profile_distance_qdoublespinbox.setValue(200)
        main_layout.addWidget(
            self.max_profile_distance_qdoublespinbox,
            2, 1, 1, 1)

        main_layout.addWidget(QLabel("Label using "), 3, 0, 1, 1)

        self.point_label_fld_comboBox = QComboBox()
        main_layout.addWidget(self.point_label_fld_comboBox, 3, 1, 1, 1)

        self.refresh_point_layer_combobox()
        self.refresh_layer_related_comboboxes()
        self.point_layer_combobox.currentIndexChanged.connect(self.refresh_layer_related_comboboxes)

        # ok/cancel setup

        okButton = QPushButton("&OK")
        cancelButton = QPushButton("Cancel")

        buttonLayout = QHBoxLayout()
        buttonLayout.addStretch()
        buttonLayout.addWidget(okButton)
        buttonLayout.addWidget(cancelButton)

        main_layout.addLayout(buttonLayout, 4, 0, 1, 2)

        okButton.clicked.connect(self.accept)
        cancelButton.clicked.connect(self.reject)

        # widget final setup

        self.setLayout(main_layout)
        self.setWindowTitle("Project points")

    def refresh_point_layer_combobox(
            self,
    ):

        refresh_combobox(
            self.point_layer_combobox,
            None,
            [layer.name() for layer in self.current_point_layers]
        )

    def refresh_layer_related_comboboxes(self):

        if len(self.current_point_layers) == 0:
            warn_qgis(self.plugin_name, "No point layer available")
            return

        layer_ndx = self.point_layer_combobox.currentIndex()

        layer = self.current_point_layers[layer_ndx]
        fields = layer.dataProvider().fields()
        field_names = [field.name() for field in fields.toList()]

        self.point_label_fld_comboBox.clear()
        self.z_fld_comboBox.clear()
        self.point_label_fld_comboBox.addItems(field_names)
        self.z_fld_comboBox.addItems(field_names)


class GeologicalDataProjectAttitudesDialog(QDialog):
    """
    Projects geological attitudes onto profiles.
    """

    def __init__(self,
                 plugin_name,
                 current_point_layers: List,
                 parent=None
                 ):

        super(GeologicalDataProjectAttitudesDialog, self).__init__(parent)

        self.plugin_name = plugin_name
        self.current_point_layers = current_point_layers

        # gui definition

        main_layout = QVBoxLayout()

        ## input section

        input_groupbox = QGroupBox(self)
        input_groupbox.setTitle('Input parameters')

        input_layout = QGridLayout()

        # input point geological layer

        input_layout.addWidget(
            QLabel("Layer"),
            0, 0, 1, 1)

        self.point_layer_combobox = QComboBox()
        input_layout.addWidget(self.point_layer_combobox, 0, 1, 1, 3)

        input_layout.addWidget(QLabel("Max profile distance"), 1, 0, 1, 1)

        self.max_profile_distance_qdoublespinbox = QDoubleSpinBox()
        self.max_profile_distance_qdoublespinbox.setMaximum(10000)
        self.max_profile_distance_qdoublespinbox.setValue(200)
        input_layout.addWidget(
            self.max_profile_distance_qdoublespinbox,
            1, 1, 1, 1)

        input_layout.addWidget(QLabel("Id"), 1, 2, 1, 1)

        self.point_label_fld_comboBox = QComboBox()
        input_layout.addWidget(self.point_label_fld_comboBox, 1, 3, 1, 1)

        self.use_dipdir_qradiobuttom = QRadioButton("Dip dir.")
        self.use_dipdir_qradiobuttom.setChecked(True)
        input_layout.addWidget(self.use_dipdir_qradiobuttom, 2, 0, 1, 1)

        self.use_rhr_strike_qradiobutton = QRadioButton("RHR str.")
        input_layout.addWidget(self.use_rhr_strike_qradiobutton, 3, 0, 1, 1)

        self.orient_fld_combobox = QComboBox()
        input_layout.addWidget(self.orient_fld_combobox, 2, 1, 1, 1)

        input_layout.addWidget(QLabel("Dip"), 2, 2, 1, 1)
        self.dipangle_fld_combobox = QComboBox()
        input_layout.addWidget(self.dipangle_fld_combobox, 2, 3, 1, 1)

        input_groupbox.setLayout(input_layout)
        main_layout.addWidget(input_groupbox)

        self.refresh_point_layer_combobox()
        self.refresh_layer_related_comboboxes()
        self.point_layer_combobox.currentIndexChanged.connect(self.refresh_layer_related_comboboxes)

        """
        ## Plot groupbox

        plot_labels_groupbox = QGroupBox(self.project_attitudes_QWidget)
        plot_labels_groupbox.setTitle('Plot geological attitudes')

        labels_layout = QGridLayout()

        labels_layout.addWidget(QLabel("Labels"), 0, 0, 1, 1)

        self.add_trend_plunge_label_qcheckbox = QCheckBox("or./dip")
        labels_layout.addWidget(self.add_trend_plunge_label_qcheckbox, 0, 1, 1, 1)

        self.add_id_label_qcheckbox = QCheckBox("id")
        labels_layout.addWidget(self.add_id_label_qcheckbox, 0, 2, 1, 1)


        labels_layout.addWidget(QLabel("Color"), 0, 3, 1, 1)

        self.proj_point_color_QgsColorButton = QgsColorButton()
        self.proj_point_color_QgsColorButton.setColor(QColor('orange'))
        labels_layout.addWidget(self.proj_point_color_QgsColorButton, 0, 4, 1, 1)

        self.project_point_pushbutton = QPushButton(self.tr("Plot"))
        self.project_point_pushbutton.clicked.connect(self.create_struct_point_projection)
        labels_layout.addWidget(self.project_point_pushbutton, 1, 0, 1, 3)

        self.reset_point_pushbutton = QPushButton(self.tr("Reset plot"))
        self.reset_point_pushbutton.clicked.connect(self.reset_struct_point_projection)

        labels_layout.addWidget(self.reset_point_pushbutton, 1, 3, 1, 2)
        
        plot_labels_groupbox.setLayout(labels_layout)
        main_layout.addWidget(plot_labels_groupbox)
        """


        '''20220814: possibly useless
        self.flds_prj_point_comboBoxes = [self.point_label_fld_comboBox,
                                          self.orient_fld_combobox,
                                          self.dipangle_fld_combobox
                                          ]
        '''

        # ok/cancel setup

        okButton = QPushButton("&OK")
        cancelButton = QPushButton("Cancel")

        buttonLayout = QHBoxLayout()
        buttonLayout.addStretch()
        buttonLayout.addWidget(okButton)
        buttonLayout.addWidget(cancelButton)

        main_layout.addLayout(buttonLayout, 3)

        okButton.clicked.connect(self.accept)
        cancelButton.clicked.connect(self.reject)

        # widget final setup

        self.setLayout(main_layout)
        self.setWindowTitle("Project geological attitudes")

    def refresh_point_layer_combobox(
            self,
    ):

        refresh_combobox(
            self.point_layer_combobox,
            None,
            [layer.name() for layer in self.current_point_layers]
        )

    def refresh_layer_related_comboboxes(self):

        if len(self.current_point_layers) == 0:
            warn_qgis(self.plugin_name, "No point layer available")
            return

        layer_ndx = self.point_layer_combobox.currentIndex()

        layer = self.current_point_layers[layer_ndx]
        fields = layer.dataProvider().fields()
        field_names = [field.name() for field in fields.toList()]

        self.point_label_fld_comboBox.clear()
        self.orient_fld_combobox.clear()
        self.dipangle_fld_combobox.clear()

        self.point_label_fld_comboBox.addItems(field_names)
        self.orient_fld_combobox.addItems(field_names)
        self.dipangle_fld_combobox.addItems(field_names)


class StylesTotalGraphicalParamsDialog(QDialog):

    def __init__(self,
                 plugin_name: str,
                 geoprofiles: GeoProfiles,
                 current_graphical_params: dict,
                 parent=None
                 ):

        super(StylesTotalGraphicalParamsDialog, self).__init__(parent)

        self.plugin_name = plugin_name
        self.geoprofiles = geoprofiles
        self.current_graphical_params = current_graphical_params

        # Prepare the dialog

        main_layout = QVBoxLayout()

        # Figure parameters

        groupbox_0 = QGroupBox("Figure parameters")

        layout_0 = QGridLayout()

        layout_0.addWidget(
            QLabel(self.tr("Figure width")),
            0, 0, 1, 1)

        self.figure_width_qlineedit = QLineEdit()
        self.figure_width_qlineedit.setText("%f" % self.current_graphical_params["figure"].width)
        layout_0.addWidget(
            self.figure_width_qlineedit,
            0, 1, 1, 1)

        layout_0.addWidget(
            QLabel(self.tr(" inches")),
            0, 2, 1, 1)

        layout_0.addWidget(
            QLabel(self.tr("Figure height")),
            1, 0, 1, 1)

        self.figure_height_qlineedit = QLineEdit()
        self.figure_height_qlineedit.setText("%f" % self.current_graphical_params["figure"].height)
        layout_0.addWidget(
            self.figure_height_qlineedit,
            1, 1, 1, 1)

        layout_0.addWidget(
            QLabel(self.tr(" inches")),
            1, 2, 1, 1)

        # groupbox final settings

        groupbox_0.setLayout(layout_0)

        main_layout.addWidget(groupbox_0)

        # Plot elevation parameters

        groupbox_1 = QGroupBox("Plot elevation parameters")

        layout_1 = QGridLayout()

        layout_1.addWidget(
            QLabel(self.tr("Vertical exaggeration")),
            0, 0, 1, 1)

        self.vertical_exxageration_ratio_qlineedit = QLineEdit()
        self.vertical_exxageration_ratio_qlineedit.setText("%f" % self.current_graphical_params["axis"].vertical_exaggeration)
        layout_1.addWidget(
            self.vertical_exxageration_ratio_qlineedit,
            0, 1, 1, 1)

        layout_1.addWidget(
            QLabel(self.tr("z max value")),
            1, 0, 1, 1)

        self.z_max_value_qlineedit = QLineEdit()
        self.z_max_value_qlineedit.setText("%f" % self.current_graphical_params["axis"].z_max)
        layout_1.addWidget(
            self.z_max_value_qlineedit,
            1, 1, 1, 1)

        layout_1.addWidget(
            QLabel(self.tr("z min value")),
            2, 0, 1, 1)

        self.z_min_value_qlineedit = QLineEdit()
        self.z_min_value_qlineedit.setText("%f" % self.current_graphical_params["axis"].z_min)
        layout_1.addWidget(
            self.z_min_value_qlineedit,
            2, 1, 1, 1)

        # groupbox final settings

        groupbox_1.setLayout(layout_1)

        main_layout.addWidget(groupbox_1)

        # Style parameters

        groupbox_2 = QGroupBox("Plot styles")

        layout_2 = QGridLayout()

        # topographic elevations

        self.topographic_elevations_qpushbutton = QPushButton(self.tr("Topographic elevations"))

        self.topographic_elevations_qpushbutton.clicked.connect(self.define_elevations_style)

        layout_2.addWidget(
            self.topographic_elevations_qpushbutton,
            0, 0, 1, 3)

        # point projections

        self.point_projections_qpushbutton = QPushButton(self.tr("Point projections"))

        self.point_projections_qpushbutton.clicked.connect(self.define_point_projections_style)

        layout_2.addWidget(
            self.point_projections_qpushbutton,
            1, 0, 1, 3)

        # attitudes projections

        self.attitude_projections_qpushbutton = QPushButton(self.tr("Attitude projections"))

        self.attitude_projections_qpushbutton.clicked.connect(self.define_attitude_projections_style)

        layout_2.addWidget(
            self.attitude_projections_qpushbutton,
            2, 0, 1, 3)

        # line intersections

        self.line_intersections_qpushbutton = QPushButton(self.tr("Line intersections"))

        self.line_intersections_qpushbutton.clicked.connect(self.define_line_intersections_style)

        layout_2.addWidget(
            self.line_intersections_qpushbutton,
            3, 0, 1, 3)

        # polygon intersections

        self.polygon_intersections_qpushbutton = QPushButton(self.tr("Polygon intersections"))

        self.polygon_intersections_qpushbutton.clicked.connect(self.define_polygon_intersections_style)

        layout_2.addWidget(
            self.polygon_intersections_qpushbutton,
            4, 0, 1, 3)

        # groupbox final settings

        groupbox_2.setLayout(layout_2)

        main_layout.addWidget(groupbox_2)

        # ok/cancel section

        okButton = QPushButton("&OK")
        cancelButton = QPushButton("Cancel")

        buttonLayout = QHBoxLayout()
        buttonLayout.addStretch()
        buttonLayout.addWidget(okButton)
        buttonLayout.addWidget(cancelButton)

        main_layout.addLayout(buttonLayout)

        self.setLayout(main_layout)

        okButton.clicked.connect(self.accept)
        cancelButton.clicked.connect(self.reject)

        self.setWindowTitle("Graphical parameters")

    def extract_elevation_plot_style(self,
                                     dialog):

        curr_item = dialog.qtwdElevationLayers.topLevelItem(0)
        return dialog.qtwdElevationLayers.itemWidget(curr_item, 1).color()

    def define_elevations_style(self):

        dialog = StylesElevationsParamsDialog(
            self.plugin_name,
            self.current_graphical_params
        )

        if dialog.exec_():
            self.elevation_color = self.extract_elevation_plot_style(dialog)
        else:
            return

    def extract_point_plot_style(self,
                                 dialog
                                 ) -> Union[type(None), PointPlotParams]:

        try:

            marker = ltMarkerStyles[dialog.marker_type_combobox.currentText()]
            marker_size = int(dialog.marker_size_qspinbox.value())
            color = dialog.marker_color_qgscolorbutton.color()
            alpha = dialog.marker_alpha_qgsopacitywidget.opacity()
            labels = dialog.labels.isChecked()

            return PointPlotParams(
                marker=marker,
                markersize=marker_size,
                color=color,
                alpha=alpha,
                labels=labels,
            )

        except:

            return None

    def define_point_projections_style(self):

        dialog = StylesPointDialog(
            self.plugin_name,
            "Point projection style",
            self.current_graphical_params["point projections"]
        )

        if dialog.exec_():
            self.point_projections_style = self.extract_point_plot_style(dialog)
        else:
            return

    def define_line_intersections_style(self):

        dialog = StylesPointDialog(
            self.plugin_name,
            "Line intersections style",
            self.current_graphical_params["line intersections"]
        )

        if dialog.exec_():
            self.line_intersections_style = self.extract_point_plot_style(dialog)
        else:
            return

    def extract_attitude_plot_style(self,
                                    dialog):

        try:

            marker = ltMarkerStyles[dialog.marker_type_combobox.currentText()]
            marker_size = int(dialog.marker_size_qspinbox.value())
            color = dialog.marker_color_qgscolorbutton.color()
            alpha = dialog.marker_alpha_qgsopacitywidget.opacity()
            label_orientations = dialog.label_orientations.isChecked()
            label_ids = dialog.label_ids.isChecked()

            return AttitudePlotParams(
                marker=marker,
                markersize=marker_size,
                color=color,
                alpha=alpha,
                label_orientations=label_orientations,
                label_ids=label_ids
            )

        except:

            pass

    def define_attitude_projections_style(self):

        dialog = StylesAttitudeProjectionsParamsDialog(
            self.plugin_name,
            self.current_graphical_params["attitude projections"]
        )

        if dialog.exec_():
            self.attitude_projections_style = self.extract_attitude_plot_style(dialog)
        else:
            return

    def extract_classification_colors(self, dialog):

        polygon_classification_colors_dict = dict()
        for classification_ndx in range(dialog.polygon_classifications_treeWidget.topLevelItemCount()):
            class_itemwidget = dialog.polygon_classifications_treeWidget.topLevelItem(classification_ndx)
            classification = str(class_itemwidget.text(0))
            # get color
            color = qcolor2rgbmpl(dialog.polygon_classifications_treeWidget.itemWidget(class_itemwidget, 1).color())
            polygon_classification_colors_dict[classification] = color

        return polygon_classification_colors_dict

    def define_polygon_intersections_style(self):

        if not self.geoprofiles or not self.geoprofiles._polygons_intersections:
            warn_qt(self,
                    self.plugin_name,
                    f"Before defining polygon style you must calculate polygon intersections.")
            return

        # create polygon codes lists from intersection with profiles

        polygon_classification_set = set()

                                            #  List[List[IdentifiedArrays]
        for profile_polygonal_intersections in self.geoprofiles._polygons_intersections:
            for identified_array in profile_polygonal_intersections:
                polygon_classification_set.add(identified_array.id)

        # create windows for user_definition of intersection colors in profile

        if polygon_classification_set != set() and polygon_classification_set != set([None]):

            dialog = StylesPolygonIntersectionsParamsDialog(
                self.plugin_name,
                self.current_graphical_params,
                polygon_classification_set
            )

            if dialog.exec_():
                polygon_classification_colors_dict = self.extract_classification_colors(dialog)
            else:
                return

            if len(polygon_classification_colors_dict) == 0:
                warn_qt(self,
                        self.plugin_name,
                        "No defined colors")
                return
            else:
                self.polygon_classification_colors = polygon_classification_colors_dict
        else:
            self.polygon_classification_colors = None


class StylesPointDialog(QDialog):
    """
    Point plot style.
    """

    def __init__(self,
                 plugin_name,
                 window_title,
                 current_point_style,
                 parent=None
                 ):

        super(StylesPointDialog, self).__init__(parent)

        self.plugin_name = plugin_name
        self.current_point_style = current_point_style

        # gui definition

        main_layout = QGridLayout()

        # input point geological layer

        main_layout.addWidget(
            QLabel("Marker type"),
            0, 0, 1, 1)

        self.marker_type_combobox = QComboBox()

        self.marker_type_combobox.insertItems(0, list(ltMarkerStyles.keys()))
        self.marker_type_combobox.setCurrentIndex(list(ltMarkerStyles.values()).index(self.current_point_style.marker))
        main_layout.addWidget(self.marker_type_combobox, 0, 1, 1, 1)

        main_layout.addWidget(
            QLabel("Marker size"),
            1, 0, 1, 1)

        self.marker_size_qspinbox = QSpinBox()
        self.marker_size_qspinbox.setValue(self.current_point_style.markersize)
        main_layout.addWidget(self.marker_size_qspinbox, 1, 1, 1, 1)

        main_layout.addWidget(
            QLabel("Marker color"),
            2, 0, 1, 1)

        self.marker_color_qgscolorbutton = QgsColorButton()
        self.marker_color_qgscolorbutton.setColor(self.current_point_style.color)
        main_layout.addWidget(self.marker_color_qgscolorbutton, 2, 1, 1, 1)

        main_layout.addWidget(
            QLabel("Marker alpha"),
            3, 0, 1, 1)

        self.marker_alpha_qgsopacitywidget = QgsOpacityWidget()
        self.marker_alpha_qgsopacitywidget.setOpacity(self.current_point_style.alpha)
        main_layout.addWidget(self.marker_alpha_qgsopacitywidget, 3, 1, 1, 1)

        self.labels = QCheckBox("Add labels")
        self.labels.setChecked(self.current_point_style.labels)

        main_layout.addWidget(self.labels, 4, 0, 1, 1)

        # ok/cancel setup

        okButton = QPushButton("&OK")
        cancelButton = QPushButton("Cancel")

        buttonLayout = QHBoxLayout()
        buttonLayout.addStretch()
        buttonLayout.addWidget(okButton)
        buttonLayout.addWidget(cancelButton)

        main_layout.addLayout(buttonLayout, 5, 0, 1, 2)

        okButton.clicked.connect(self.accept)
        cancelButton.clicked.connect(self.reject)

        # widget final setup

        self.setLayout(main_layout)
        self.setWindowTitle(window_title)


class StylesAttitudeProjectionsParamsDialog(QDialog):
    """
    Definition of attitude projection style.
    """

    def __init__(self,
                 plugin_name,
                 current_attitude_projections_style_params,
                 parent=None
                 ):

        super(StylesAttitudeProjectionsParamsDialog, self).__init__(parent)

        self.plugin_name = plugin_name
        self.current_attitude_projections_style_params = current_attitude_projections_style_params

        # gui definition

        main_layout = QGridLayout()

        # input point geological layer

        main_layout.addWidget(
            QLabel("Marker type"),
            0, 0, 1, 1)

        self.marker_type_combobox = QComboBox()

        self.marker_type_combobox.insertItems(0, list(ltMarkerStyles.keys()))
        self.marker_type_combobox.setCurrentIndex(list(ltMarkerStyles.values()).index(self.current_attitude_projections_style_params.marker))
        main_layout.addWidget(self.marker_type_combobox, 0, 1, 1, 1)

        main_layout.addWidget(
            QLabel("Marker size"),
            1, 0, 1, 1)

        self.marker_size_qspinbox = QSpinBox()
        self.marker_size_qspinbox.setValue(self.current_attitude_projections_style_params.markersize)
        main_layout.addWidget(self.marker_size_qspinbox, 1, 1, 1, 1)

        main_layout.addWidget(
            QLabel("Marker color"),
            2, 0, 1, 1)

        self.marker_color_qgscolorbutton = QgsColorButton()
        self.marker_color_qgscolorbutton.setColor(self.current_attitude_projections_style_params.color)
        main_layout.addWidget(self.marker_color_qgscolorbutton, 2, 1, 1, 1)

        main_layout.addWidget(
            QLabel("Marker alpha"),
            3, 0, 1, 1)

        self.marker_alpha_qgsopacitywidget = QgsOpacityWidget()
        self.marker_alpha_qgsopacitywidget.setOpacity(self.current_attitude_projections_style_params.alpha)
        main_layout.addWidget(self.marker_alpha_qgsopacitywidget, 3, 1, 1, 1)

        main_layout.addWidget(
            QLabel("Add labels"),
            4, 0, 1, 1)

        self.label_orientations = QCheckBox("Orientations")
        self.label_orientations.setChecked(self.current_attitude_projections_style_params.label_orientations)

        main_layout.addWidget(self.label_orientations, 4, 1, 1, 1)

        self.label_ids = QCheckBox("Ids")
        self.label_ids.setChecked(self.current_attitude_projections_style_params.label_ids)

        main_layout.addWidget(self.label_ids, 5, 1, 1, 1)

        # ok/cancel setup

        okButton = QPushButton("&OK")
        cancelButton = QPushButton("Cancel")

        buttonLayout = QHBoxLayout()
        buttonLayout.addStretch()
        buttonLayout.addWidget(okButton)
        buttonLayout.addWidget(cancelButton)

        main_layout.addLayout(buttonLayout, 6, 0, 1, 2)

        okButton.clicked.connect(self.accept)
        cancelButton.clicked.connect(self.reject)

        # widget final setup

        self.setLayout(main_layout)
        self.setWindowTitle("Project attitude style")


class ExportTopographicProfileDialog(QDialog):

    def __init__(self,
                 plugin_name,
                 parent=None
                 ):

        super(ExportTopographicProfileDialog, self).__init__(parent)

        self.plugin_name = plugin_name

        layout = QVBoxLayout()

        # Output type

        output_type_groupBox = QGroupBox(self.tr("Output format"))

        output_type_layout = QGridLayout()

        self.outtype_shapefile_point_QRadioButton = QRadioButton(self.tr("shapefile - point"))
        output_type_layout.addWidget(self.outtype_shapefile_point_QRadioButton, 0, 0, 1, 1)
        self.outtype_shapefile_point_QRadioButton.setChecked(True)

        self.outtype_shapefile_line_QRadioButton = QRadioButton(self.tr("shapefile - line"))
        output_type_layout.addWidget(
            self.outtype_shapefile_line_QRadioButton,
            1, 0, 1, 1)

        output_type_groupBox.setLayout(output_type_layout)

        layout.addWidget(output_type_groupBox)

        # Output name/path

        output_path_groupBox = QGroupBox(self.tr("Output file"))

        output_path_layout = QGridLayout()

        self.outpath_QLineEdit = QLineEdit()
        output_path_layout.addWidget(
            self.outpath_QLineEdit,
            0, 0, 1, 1)

        self.outpath_QPushButton = QPushButton("....")
        self.outpath_QPushButton.clicked.connect(self.define_outpath)
        output_path_layout.addWidget(
            self.outpath_QPushButton,
            0, 1, 1, 1)

        self.load_output_checkBox = QCheckBox("load output shapefile in project")
        self.load_output_checkBox.setChecked(True)
        output_path_layout.addWidget(
            self.load_output_checkBox,
            1, 0, 1, 2)

        output_path_groupBox.setLayout(output_path_layout)

        layout.addWidget(output_path_groupBox)

        decide_QWiget = QWidget()

        buttonLayout = QHBoxLayout()
        buttonLayout.addStretch()

        okButton = QPushButton("&OK")
        cancelButton = QPushButton("Cancel")

        buttonLayout.addWidget(okButton)
        buttonLayout.addWidget(cancelButton)

        decide_QWiget.setLayout(buttonLayout)

        layout.addWidget(decide_QWiget)

        self.setLayout(layout)

        okButton.clicked.connect(self.accept)
        cancelButton.clicked.connect(self.reject)

        self.setWindowTitle("Export topographic profile")

    def define_outpath(self):

        if self.outtype_shapefile_line_QRadioButton.isChecked() or self.outtype_shapefile_point_QRadioButton.isChecked():
            outfile_path = new_file_path(
                self,
                "Save file",
                "",
                "Shapefiles (*.shp)"
            )
        elif self.outtype_csv_QRadioButton.isChecked():
            outfile_path = new_file_path(
                self,
                "Save file",
                "",
                "Csv (*.csv)"
            )
        else:
            error_qt(
                self,
                self.plugin_name,
                self.tr("Output type definition error")
            )
            return

        self.outpath_QLineEdit.setText(outfile_path)

class ExportLineDataDialog(QDialog):

    def __init__(self,
                 plugin_name,
                 parent=None
                 ):

        super(ExportLineDataDialog, self).__init__(parent)

        self.plugin_name = plugin_name

        layout = QVBoxLayout()

        # Output type

        output_type_groupBox = QGroupBox(self.tr("Output format"))

        output_type_layout = QGridLayout()

        self.outtype_shapefile_line_QRadioButton = QRadioButton(self.tr("shapefile - line"))
        output_type_layout.addWidget(self.outtype_shapefile_line_QRadioButton, 0, 0, 1, 1)
        self.outtype_shapefile_line_QRadioButton.setChecked(True)

        self.outtype_csv_QRadioButton = QRadioButton(self.tr("csv"))
        output_type_layout.addWidget(
            self.outtype_csv_QRadioButton,
            0, 1, 1, 1)

        output_type_groupBox.setLayout(output_type_layout)

        layout.addWidget(output_type_groupBox)

        # Output name/path

        output_path_groupBox = QGroupBox(self.tr("Output file"))

        output_path_layout = QGridLayout()

        self.outpath_QLineEdit = QLineEdit()
        output_path_layout.addWidget(
            self.outpath_QLineEdit,
            0, 0, 1, 1)

        self.outpath_QPushButton = QPushButton("....")
        self.outpath_QPushButton.clicked.connect(self.define_outpath)
        output_path_layout.addWidget(
            self.outpath_QPushButton,
            0, 1, 1, 1)

        self.load_output_checkBox = QCheckBox("load output in project")
        self.load_output_checkBox.setChecked(True)
        output_path_layout.addWidget(
            self.load_output_checkBox,
            1, 0, 1, 2)

        output_path_groupBox.setLayout(output_path_layout)

        layout.addWidget(output_path_groupBox)

        decide_QWiget = QWidget()

        buttonLayout = QHBoxLayout()
        buttonLayout.addStretch()

        okButton = QPushButton("&OK")
        cancelButton = QPushButton("Cancel")

        buttonLayout.addWidget(okButton)
        buttonLayout.addWidget(cancelButton)

        decide_QWiget.setLayout(buttonLayout)

        layout.addWidget(decide_QWiget)

        self.setLayout(layout)

        okButton.clicked.connect(self.accept)
        cancelButton.clicked.connect(self.reject)

        self.setWindowTitle("Export")

    def define_outpath(self):

        if self.outtype_shapefile_line_QRadioButton.isChecked():
            outfile_path = new_file_path(
                self,
                "Save file",
                "",
                "Shapefiles (*.shp)"
            )
        elif self.outtype_csv_QRadioButton.isChecked():
            outfile_path = new_file_path(
                self,
                "Save file",
                "",
                "Csv (*.csv)"
            )
        else:
            error_qt(
                self,
                self.plugin_name,
                self.tr("Output type definition error")
            )
            return

        self.outpath_QLineEdit.setText(outfile_path)

class BaseDataDigitizeLineDialog(QDialog):

    def __init__(
            self,
            plugin_name,
            canvas,
            parent=None
    ):

        super(BaseDataDigitizeLineDialog, self).__init__(parent)

        self.plugin_name = plugin_name
        self.canvas = canvas
        self.previous_maptool = self.canvas.mapTool()  # Save the standard map tool for restoring it at the end

        layout = QVBoxLayout()

        self.qpbtDigitizeLine = QPushButton(self.tr("Digitize trace"))
        self.qpbtDigitizeLine.setToolTip(
            "Digitize the trace on the map.\n"
            "Left click: add point\n"
            "Right click: end adding point"
        )
        self.qpbtDigitizeLine.clicked.connect(self.digitize_line)

        layout.addWidget(self.qpbtDigitizeLine)

        self.qpbtClearLine = QPushButton(self.tr("Clear"))
        self.qpbtClearLine.clicked.connect(self.clear_rubberband)
        layout.addWidget(self.qpbtClearLine)

        self.qpbtClearLine = QPushButton(self.tr("Save"))
        self.qpbtClearLine.clicked.connect(self.save_rubberband)
        layout.addWidget(self.qpbtClearLine)

        self.setLayout(layout)

        self.setWindowTitle("Digitize line")

    '''
    def connect_digitize_maptool(self):

        self.digitize_maptool.moved.connect(self.canvas_refresh_profile_line)
        self.digitize_maptool.leftClicked.connect(self.profile_add_point)
        self.digitize_maptool.rightClicked.connect(self.canvas_end_profile_line)
    '''

    def digitize_line(self):

        self.clear_rubberband()

        info_qgis(
            self.plugin_name,
            "Now you can digitize a line on the map.\nLeft click: add point\nRight click: end adding point"
        )

        self.rubberband = QgsRubberBand(self.canvas)
        self.rubberband.setWidth(2)
        self.rubberband.setColor(QColor(Qt.red))

        self.digitize_maptool = MapDigitizeTool(self.canvas)
        self.canvas.setMapTool(self.digitize_maptool)

        self.digitize_maptool.moved.connect(self.canvas_refresh_profile_line)
        self.digitize_maptool.leftClicked.connect(self.profile_add_point)
        self.digitize_maptool.rightClicked.connect(self.canvas_end_profile_line)

    def canvas_refresh_profile_line(self, position):

        x, y = xy_from_canvas(self.canvas, position)

        self.refresh_rubberband(
            self.profile_canvas_points_x + [x],
            self.profile_canvas_points_y + [y]
        )

    def profile_add_point(self, position):

        x, y = xy_from_canvas(self.canvas, position)

        self.profile_canvas_points_x.append(x)
        self.profile_canvas_points_y.append(y)

    def canvas_end_profile_line(self):

        self.refresh_rubberband(
            self.profile_canvas_points_x,
            self.profile_canvas_points_y)

        self.digitized_profile_line2dt = None
        if len(self.profile_canvas_points_x) > 1:
            raw_line = Ln(list(zip(self.profile_canvas_points_x, self.profile_canvas_points_y))).remove_coincident_points()
            if raw_line.num_points() > 1:
                self.digitized_profile_line2dt = raw_line

        self.profile_canvas_points_x = []
        self.profile_canvas_points_y = []

        self.restore_previous_map_tool()

    def restore_previous_map_tool(self):

        self.canvas.unsetMapTool(self.digitize_maptool)
        self.canvas.setMapTool(self.previous_maptool)

    def refresh_rubberband(self,
                           xy_list
                           ):

        self.rubberband.reset(QgsWkbTypes.LineGeometry)
        for x, y in xy_list:
            self.rubberband.addPoint(QgsPointXY(x, y))

    def clear_rubberband(self):

        self.profile_canvas_points_x = []
        self.profile_canvas_points_y = []

        self.digitized_profile_line2dt = None

        try:

            self.rubberband.reset()

        except:

            pass

    def save_rubberband(self):

        def output_profile_line(
                output_format,
                output_filepath,
                pts2dt,
                proj_sr
        ):

            points = [[n, pt2dt.x, pt2dt.y] for n, pt2dt in enumerate(pts2dt)]
            if output_format == "csv":
                success, msg = write_generic_csv(output_filepath,
                                                 ['id', 'x', 'y'],
                                                 points)
                if not success:
                    warn_qt(
                        self,
                        self.plugin_name,
                        msg
                    )
            elif output_format == "shapefile - line":
                success, msg = write_rubberband_profile_lnshp(
                    output_filepath,
                    ['id'],
                    points,
                    proj_sr)
                if not success:
                    warn_qt(
                        self,
                        self.plugin_name,
                        msg
                    )
            else:
                error_qt(
                    self,
                    self.plugin_name,
                    "Debug: error in export format"
                )
                return

            if success:
                ok_qgis(
                    self.plugin_name,
                    "Line saved"
                )

        def extract_format_type():

            if dialog.outtype_shapefile_line_QRadioButton.isChecked():
                return "shapefile - line"
            elif dialog.outtype_csv_QRadioButton.isChecked():
                return "csv"
            else:
                return ""

        if self.digitized_profile_line2dt is None:

            warn_qt(
                self,
                self.plugin_name,
                "No available line to save [1]"
            )
            return

        elif self.digitized_profile_line2dt.num_points() < 2:

            warn_qt(
                self,
                self.plugin_name,
                "No available line to save [2]"
            )
            return

        dialog = ExportLineDataDialog(self.plugin_name)
        if dialog.exec_():
            output_format = extract_format_type()
            if output_format == "":
                warn_qt(
                    self,
                    self.plugin_name,
                    "Error in output format"
                )
                return
            output_filepath = dialog.outpath_QLineEdit.text()
            if len(output_filepath) == 0:
                warn_qt(
                    self,
                    self.plugin_name,
                    "Error in output path"
                )
                return
            add_to_project = dialog.load_output_checkBox.isChecked()
        else:
            warn_qt(
                self,
                self.plugin_name,
                "No export defined"
            )
            return

        # get project CRS information
        project_crs_osr = proj4str()

        output_profile_line(
            output_format,
            output_filepath,
            self.digitized_profile_line2dt.pts,
            project_crs_osr)

        # add theme to QGis project
        if output_format == "shapefile - line" and add_to_project:
            try:

                digitized_line_layer = QgsVectorLayer(output_filepath,
                                                      QFileInfo(output_filepath).baseName(),
                                                      "ogr")
                QgsProject.instance().addMapLayer(digitized_line_layer)

            except:

                error_qt(
                    self,
                    self.plugin_name,
                    "Unable to load layer in project"
                )
                return


class BaseDataProfilerSettingsDialog(QDialog):

    def __init__(
            self,
            plugin_name,
            parent=None
    ):

        super(BaseDataProfilerSettingsDialog, self).__init__(parent)

        self.plugin_name = plugin_name

        layout = QGridLayout()

        layout.addWidget(QLabel("Number of parallel profiles"), 0, 0, 1, 1)

        self.num_parallel_profiles_wdgt = QSpinBox()
        self.num_parallel_profiles_wdgt.setValue(1)
        self.num_parallel_profiles_wdgt.setMinimum(1)
        self.num_parallel_profiles_wdgt.setSingleStep(2)
        layout.addWidget(self.num_parallel_profiles_wdgt, 0, 1, 1, 1)

        layout.addWidget(QLabel("Spacing between parallel profiles"), 1, 0, 1, 1)

        self.spacing_wdgt = QDoubleSpinBox()
        self.spacing_wdgt.setValue(0.0)
        self.spacing_wdgt.setMinimum(0.0)
        self.spacing_wdgt.setMaximum(10000.0)
        self.spacing_wdgt.setSingleStep(50.0)

        layout.addWidget(self.spacing_wdgt, 1, 1, 1, 1)

        okButton = QPushButton("&OK")
        cancelButton = QPushButton("Cancel")
        okButton.clicked.connect(self.accept)
        cancelButton.clicked.connect(self.reject)

        verticalSpacer = QSpacerItem(20, 30, QSizePolicy.Minimum, QSizePolicy.Expanding)
        layout.addItem(verticalSpacer)

        layout.addWidget(okButton,
                         2, 0, 1, 1)
        layout.addWidget(cancelButton,
                         2, 1, 1, 1)

        self.setLayout(layout)

        self.setWindowTitle("Profiles settings")
        self.setMinimumSize(400, 200)


class HelpDialog(QDialog):

    def __init__(self,
                 plugin_name,
                 plugin_folder,
                 parent=None):

        super(HelpDialog, self).__init__(parent)

        layout = QVBoxLayout()

        # About section

        helpTextBrwsr = QTextBrowser(self)

        url_path = f"file:///{plugin_folder}/help/geoprofiler/help.html"
        helpTextBrwsr.setSource(QUrl(url_path))
        helpTextBrwsr.setSearchPaths([f'{plugin_folder}/help/geoprofiler/images'])
        helpTextBrwsr.setMinimumSize(700, 600)

        layout.addWidget(helpTextBrwsr)

        self.setLayout(layout)

        self.setWindowTitle("{} - Points-plane distances help".format(plugin_name))

