# -*- coding: utf-8 -*-
"""
/***************************************************************************
 VetSamp
                                 A QGIS plugin
 Plugin for areal sampling based on GRTS
 Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/
                              -------------------
        begin                : 2019-09-26
        git sha              : $Format:%H$
        copyright            : (C) 2019 by Márton Papp
        email                : pappmarci95@gmail.com
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/
"""
import sys
import os
import tempfile
from PyQt5.QtCore import QSettings, QTranslator, qVersion, QCoreApplication, QVariant, Qt, QMetaType
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QAction, QDialog, QWidget, QPushButton, QTextBrowser, QLabel, QVBoxLayout, QHBoxLayout, QLineEdit, QListWidget, QComboBox, QRadioButton, QButtonGroup, QScrollArea
from random import uniform, sample, randint, shuffle
from qgis.core import *
import processing
from qgis.analysis import QgsZonalStatistics
import math
import collections
import copy
import time
from decimal import *
import threading


# Initialize Qt resources from file resources.py
from .resources import *
# Import the code for the dialog
from .VetSamp_dialog import VetSampDialog
import os.path


class VetSamp:
    """QGIS Plugin Implementation."""

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

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

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

        # Declare instance attributes
        self.actions = []
        self.menu = self.tr(u'&VetSamp')

        # Check if plugin was started the first time in current QGIS session
        # Must be set in initGui() to survive plugin reloads
        self.first_start = None





        # Creating feedback value for custom quadrant number selection, oversampling and stratified sampling:
        self.quadrchecker = 1
        self.is_oversampling = 1
        self.is_strat = 1

        # Value for checking if GPS (WGS84) coordinates of the sample points are desired (1 means don't display them and 2 means to display them):
        self.isgps = 1



        # STRATIFICATION DATA STORING VARIABLES:

        # Creating a list that stores the ids of the used fields (in lists of the stratification determiner id and the field id):
        self.usedfields = []

        # Creating value that stores stratum data (It is a list of lists containing the data of one stratification determiner,
        # The first value in the list is the serial number, the second is the field index, the third is type (1 if a number, 2 if a discrete variable),
        # And last follows the list of ranges and variables with their identification name (lists again) (ID, VALUE/RANGE_MIN+RANGE_MAX))
        # And if it is a contnuous data there is the maximum of the whole range at the end:
        self.stratdata = []

        # Creating an ID generator for the stratification:
        self.id_numb = 0

        # Value for the selection between continuous and discrete type of field elements in the case of all number attributes (1 is for continuous and 2 is for discrete values):
        self.sel_type = 1

        # Creating a list for used identification names (A lsit of list. The first element is the serial number and the second is a list of used ids):
        self.usedidnames = []



        # LOCAL ASSIGMENT DATA STORING:

        # List for storing the id names inside an assignment cycle (A list of lists. Inside the list there is the serial of the id, and the id itself):
        self.this_usedids = []

        # Value for storing the current minimum of ranges:
        self.current_min = float()

        # List to store current ranges, and the serial of the ranges (These are lists of the serial, the id and the value or the min and max of the range):
        self.current_ranges = []
        self.serial_for_ranges = 1

        # List storing the used serials in range or discrete value id assignments:
        self.used_serials = []



        # Value for storing the qlistwidget for stratum determiner assignning:
        self.stratlist = QListWidget()

        # Creating the window for asking if stratification is desired:
        self.windw_quest = QWidget()
        self.windw_quest.setWindowTitle("Selection confirmation")
        self.label = QLabel("Are you sure you want to use stratified sampling?")
        self.yes_button = QPushButton("YES")
        self.yes_button.clicked.connect(lambda: self.ok_for_strat())
        self.no_button = QPushButton("NO")
        self.no_button.clicked.connect(lambda: self.windw_quest.close())
        self.hbox = QHBoxLayout()
        self.hbox.addWidget(self.yes_button)
        self.hbox.addWidget(self.no_button)
        self.vbox = QVBoxLayout()
        self.vbox.addWidget(self.label)
        self.vbox.addLayout(self.hbox)
        self.windw_quest.setLayout(self.vbox)








    # noinspection PyMethodMayBeStatic
    def tr(self, message):
        """Get the translation for a string using Qt translation API.

        We implement this ourselves since we do not inherit QObject.

        :param message: String for translation.
        :type message: str, QString

        :returns: Translated version of message.
        :rtype: QString
        """
        # noinspection PyTypeChecker,PyArgumentList,PyCallByClass
        return QCoreApplication.translate('VetSamp', message)


    def add_action(
        self,
        icon_path,
        text,
        callback,
        enabled_flag=True,
        add_to_menu=True,
        add_to_toolbar=True,
        status_tip=None,
        whats_this=None,
        parent=None):
        """Add a toolbar icon to the toolbar.

        :param icon_path: Path to the icon for this action. Can be a resource
            path (e.g. ':/plugins/foo/bar.png') or a normal file system path.
        :type icon_path: str

        :param text: Text that should be shown in menu items for this action.
        :type text: str

        :param callback: Function to be called when the action is triggered.
        :type callback: function

        :param enabled_flag: A flag indicating if the action should be enabled
            by default. Defaults to True.
        :type enabled_flag: bool

        :param add_to_menu: Flag indicating whether the action should also
            be added to the menu. Defaults to True.
        :type add_to_menu: bool

        :param add_to_toolbar: Flag indicating whether the action should also
            be added to the toolbar. Defaults to True.
        :type add_to_toolbar: bool

        :param status_tip: Optional text to show in a popup when mouse pointer
            hovers over the action.
        :type status_tip: str

        :param parent: Parent widget for the new action. Defaults None.
        :type parent: QWidget

        :param whats_this: Optional text to show in the status bar when the
            mouse pointer hovers over the action.

        :returns: The action that was created. Note that the action is also
            added to self.actions list.
        :rtype: QAction
        """

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

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

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

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

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

        self.actions.append(action)

        return action

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

        icon_path = ':/plugins/VetSamp/icon.png'
        self.add_action(
            icon_path,
            text=self.tr(u'Areal'),
            callback=self.run,
            parent=self.iface.mainWindow())

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


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





    # ---------------------------- RASTER LAYER AVERAGING INSIDE POLYGONS -----------------------------------------------





    # Error window for raster processing:
    def errorwindow(self, numb, selected_vect_layer):
        text = ""
        if numb == 1:
            text = "There should be a raster layer loaded"
        if numb == 2:
            text = "A layer must be selected"
        if numb == 3:
            text = "The name of the raster average field must be given"
        if numb == 4:
            text = "The selected raster layer must be a valid QGIS raster layer"
        windw = QWidget()
        windw.setWindowTitle("Error window")
        textbox = QTextBrowser()
        textbox.append(text)
        ok_button = QPushButton()
        ok_button.setText("OK")
        if numb == 1:
            ok_button.clicked.connect(lambda: self.errorclose(windw))
        if numb == 2:
            ok_button.clicked.connect(lambda: self.raster(selected_vect_layer, windw, 1))
        if numb == 3:
            ok_button.clicked.connect(lambda: self.raster(selected_vect_layer, windw, 1))
        if numb == 4:
            ok_button.clicked.connect(lambda: self.raster(selected_vect_layer, windw, 1))
        vbox = QVBoxLayout()
        vbox.addWidget(textbox)
        vbox.addWidget(ok_button)
        windw.setLayout(vbox)
        windw.show()


    # Function for error window to close itself and open self.raster():
    def errorclose(self, windw):
        windw.close()


    # Function for selecting the raster layer:
    def raster(self, selected_vect_layer, windw, is_windw):
        if is_windw == 1:
            windw.close()
        loaded_layers = self.iface.mapCanvas().layers()
        raster_layers = []
        for layer in loaded_layers:
            if layer.type() == QgsMapLayer.RasterLayer:
                raster_layers.append(layer)
        loaded_layers = raster_layers
        if not loaded_layers:
            self.errorwindow(1, selected_vect_layer)
        else:
            loaded_layers_list = []
            for lay in loaded_layers:
                layname = lay.name()
                loaded_layers_list.append(layname)
            windw = QWidget()
            windw.setWindowTitle("Raster layer and attribute selection")
            label1 = QLabel()
            label1.setText("Raster layer selection:")
            label2 = QLabel()
            label2.setText("Name of the raster average field (It will be the given name and _mean in the field list!):")
            rastlist = QComboBox()
            rastlist.clear()
            rastlist.addItems(loaded_layers_list)
            nameoffield = QLineEdit()
            ok_button = QPushButton()
            ok_button.setText("OK")
            ok_button.clicked.connect(
                lambda: self.rasterlaycheck(windw, selected_vect_layer, rastlist.currentText(), nameoffield.text()))
            vbox = QVBoxLayout()
            vbox.addWidget(label1)
            vbox.addWidget(rastlist)
            vbox.addWidget(label2)
            vbox.addWidget(nameoffield)
            vbox.addWidget(ok_button)
            windw.setLayout(vbox)
            windw.show()


    # Function for checking raster layer if it has a unique name and getting the selected attributes index:
    def rasterlaycheck(self, windw, selected_vect_layer, selecteditems, nameoffield):
        windw.close()
        nameoffield = str(nameoffield)
        if not nameoffield:
            self.errorwindow(3, selected_vect_layer)
        selected_rast_layer_name = selecteditems
        if not selected_rast_layer_name:
            self.errorwindow(2, selected_vect_layer)
        else:
            selected_rast_layer = QgsProject.instance().mapLayersByName(selected_rast_layer_name)[0]
            if not selected_rast_layer.isValid():
                self.errorwindow(4, selected_vect_layer)
            else:
                self.rastertopolygon(selected_vect_layer, selected_rast_layer, nameoffield)


    # Function to acquire the information for polygons and doing the averaging:
    def rastertopolygon(self, selected_vect_layer, selected_rast_layer, nameoffield):
        nameoffield = str(nameoffield) + str("_")
        zonestat = QgsZonalStatistics(selected_vect_layer, selected_rast_layer, str(nameoffield), 1,
                                      QgsZonalStatistics.Mean)
        zonestat.calculateStatistics(None)





    # ---------------------------------------STRATIFIED SAMPLING-----------------------------------------------------------





    # Error window for layer selection:
    def error_strat_lay_sel(self, error_text):
        windw = QWidget()
        windw.setWindowTitle("ERROR WINDOW FOR LAYER SELECTION")
        label = QLabel("The following error(s) occured:")
        error_text = '\n'.join(error_text)
        textbox = QTextBrowser()
        textbox.append(error_text)
        ok_button = QPushButton("OK")
        ok_button.clicked.connect(lambda: self.error_strat_lay_sel_close(windw))
        vbox = QVBoxLayout()
        vbox.addWidget(label)
        vbox.addWidget(textbox)
        vbox.addWidget(ok_button)
        windw.setLayout(vbox)
        windw.show()


    # Function for closing the previous error window and opening the layer selection:
    def error_strat_lay_sel_close(self, windw):
        windw.close()
        self.stratlaysel()


    # Window for asking if truly stratified sampling is desired:
    def is_strat_sel(self):
        self.windw_quest.show()


    # Function for opening the stratum selection:
    def ok_for_strat(self):
        self.windw_quest.close()
        self.stratlaysel()


    # Function for selecting the layer for sampling:
    def stratlaysel(self):
        # Closing the dialog window, but only if it is open:
        if self.dlg.isVisible():
            self.dlg.close()
        # Setting the stratum determiner fields' id generator number to zero:
        self.id_numb = 0
        # Setting list storing the used fields to an empty one:
        self.usedfields = []
        # Setting the list containing the already used ids to an empty one:
        self.usedidnames = []
        # Setting the stratum determiner data storing list to empty:
        self.stratdata = []
        windw = QWidget()
        windw.setWindowTitle("Layer selection")
        label = QLabel("PLease select the layer for sampling:")
        cbox = QComboBox()
        lays = self.iface.mapCanvas().layers()
        vect_lays = []
        for layer in lays:
            if layer.type() == QgsMapLayer.VectorLayer:
                layer_name = layer.name()
                vect_lays.append(layer_name)
        cbox.addItems(vect_lays)
        ok_button = QPushButton("OK")
        ok_button.clicked.connect(lambda: self.usestrat(windw, cbox.currentText()))
        cancel_button = QPushButton("CANCEL")
        cancel_button.clicked.connect(lambda: windw.close())
        hbox = QHBoxLayout()
        hbox.addWidget(ok_button)
        hbox.addWidget(cancel_button)
        vbox = QVBoxLayout()
        vbox.addWidget(label)
        vbox.addWidget(cbox)
        vbox.addLayout(hbox)
        windw.setLayout(vbox)
        windw.show()


    # Function for the settings of stratified sampling:
    def usestrat(self, windw, layname):
        windw.close()
        error_text = []
        is_error = 0
        if not layname:
            error_text.append("A layer should be selected")
            is_error = is_error + 1
        else:
            layer = QgsProject.instance().mapLayersByName(layname)[0]
            if not layer.isValid():
                error_text.append("The layer must be a valid QGIS vector layer")
                is_error = is_error + 1
            else:
                prob_with_geom = 0
                feats = layer.getFeatures()
                for feat in feats:
                    if prob_with_geom == 0:
                        geom = feat.geometry()
                        if not geom.isGeosValid():
                            prob_with_geom = 1
                    else:
                        break
                if prob_with_geom == 1:
                    error_text.append("All the layer geometries must be valid")
                    is_error = is_error + 1
        if is_error != 0:
            self.error_strat_lay_sel(error_text)
        else:
            layer = QgsProject.instance().mapLayersByName(layname)[0]
            windw = QWidget()
            windw.setWindowTitle("Stratification settings")
            label1 = QLabel("If raster point averaging inside polygons is needed, please press AVERAGE RASTER button:")
            rast_button = QPushButton("AVERAGE RASTER")
            rast_button.clicked.connect(lambda: self.raster(layer, 0, 0))
            self.stratlist = QListWidget()
            self.stratlist.clear()
            plus_button = QPushButton("+")
            plus_button.clicked.connect(lambda: self.stratspec(layer))
            minus_button = QPushButton("-")
            minus_button.clicked.connect(lambda: self.delstratspec(self.stratlist.selectedItems()))
            info_button = QPushButton("INFO")
            info_button.clicked.connect(lambda: self.infostratspec(self.stratlist.selectedItems(), layer))
            ok_button = QPushButton("OK")
            ok_button.clicked.connect(lambda: self.sampperstratum(windw, layer))
            cancel_button = QPushButton("CANCEL")
            cancel_button.clicked.connect(lambda: windw.close())
            hbox1 = QHBoxLayout()
            hbox1.addWidget(label1)
            hbox1.addWidget(rast_button)
            vbox = QVBoxLayout()
            vbox.addLayout(hbox1)
            vbox.addWidget(self.stratlist)
            hbox2 = QHBoxLayout()
            hbox2.addWidget(plus_button)
            hbox2.addWidget(minus_button)
            hbox2.addWidget(info_button)
            vbox.addLayout(hbox2)
            hbox3 = QHBoxLayout()
            hbox3.addWidget(ok_button)
            hbox3.addWidget(cancel_button)
            vbox.addLayout(hbox3)
            windw.setLayout(vbox)
            windw.show()


    # Function for adding new stratum determiner, selecting the field:
    def stratspec(self, layer):
        windw = QWidget()
        windw.setWindowTitle("Stratum determiner selection")
        label = QLabel("Please select the field to use:")
        cbox = QComboBox()
        field_names = []
        fields = layer.fields()
        usedfields = []
        for a in self.usedfields:
            usedfields.append(a[1])
        for field in fields:
            field_index = layer.fields().indexFromName(field.name())
            if field_index in usedfields:
                pass
            else:
                field_names.append(field.name())
        cbox.addItems(field_names)
        ok_button = QPushButton("OK")
        ok_button.clicked.connect(lambda: self.getfielddata(cbox.currentText(), windw, layer))
        cancel_button = QPushButton("CANCEL")
        cancel_button.clicked.connect(lambda: windw.close())
        vbox = QVBoxLayout()
        vbox.addWidget(label)
        vbox.addWidget(cbox)
        vbox.addWidget(ok_button)
        vbox.addWidget(cancel_button)
        windw.setLayout(vbox)
        windw.show()


    # Function for adding the id and field id for the list containing tha data of the determiner and getting the type of it(whether it is a discrete variable or a continuous):
    def getfielddata(self, field, windw, layer):
        windw.close()
        # Checking if there is a field selected:
        is_error = 0
        if not field:
            is_error = is_error + 1
        if is_error != 0:
            self.error_for_field()
        else:
            # Creating a value that is 1 if no non-number element is found and 2 if there is any:
            is_there_non_number = 1
            field_id = layer.fields().indexFromName(str(field))
            field_data_list = [field_id]
            # Creating a list of the fields elements and simultaneously checking whether these elements are only numbers or they can only be used as discrete values:
            feats = layer.getFeatures()
            field_elems = []
            break_for_null = 0
            for feat in feats:
                attrs = feat.attributes()
                field_elem = attrs[field_id]
                if not field_elem:
                    break_for_null = 1
                    break
                else:
                    field_elem = str(field_elem)
                    if is_there_non_number == 1:
                        field_elem_is_numb = self.is_numb(field_elem)
                        if field_elem_is_numb == False:
                            is_there_non_number = 2
                    field_elems.append(field_elem)
            if break_for_null == 1:
                self.error_for_NULL()
            else:
                if is_there_non_number == 2:
                    field_data_list.append(is_there_non_number)
                    self.val_ids(field_data_list, field_elems, field, layer)
                else:
                    self.disc_or_cont(field_data_list, field_elems, field, layer)


    # function for checking if a value is a number:
    def is_numb(self, value):
        try:
            float(value)
            return True
        except ValueError:
            return False


    # Error window for field selection:
    def error_for_field(self):
        windw = QWidget()
        windw.setWindowTitle("Error window")
        label = QLabel("A field must be selected, please make sure that your layer does have fields!")
        cancel_button = QPushButton("CANCEL")
        cancel_button.clicked.connect(lambda: windw.close())
        vbox = QVBoxLayout()
        vbox.addWidget(label)
        vbox.addWidget(cancel_button)
        windw.setLayout(vbox)
        windw.show()


    # Function for error window if there is a NULL field value:
    def error_for_NULL(self):
        windw = QWidget()
        windw.setWindowTitle("Error_window")
        label = QLabel("Please use a field that does not contain NULL values!")
        ok_button = QPushButton("OK")
        ok_button.clicked.connect(lambda: windw.close())
        hbox = QHBoxLayout()
        hbox.addStretch(2)
        hbox.addWidget(ok_button)
        hbox.addStretch(2)
        vbox = QVBoxLayout()
        vbox.addWidget(label)
        vbox.addLayout(hbox)
        windw.setLayout(vbox)
        windw.show()


    # Function for selecting between discrete and continuous values (only if the values are all numbers):
    def disc_or_cont(self, field_data_list, field_elems, field, layer):
        # Setting the value for the type selected (1 is continuous and 2 is discrete):
        self.sel_type = 1
        windw = QWidget()
        windw.setWindowTitle("value type selection")
        label = QLabel("Please select the correct value type for stratum determining field:")
        label1 = QLabel("Use values as continuous data:")
        label2 = QLabel("Use values as discrete data")
        button1 = QRadioButton()
        button1.setChecked(True)
        button1.toggled.connect(lambda: self.cont_but())
        button2 = QRadioButton()
        button2.toggled.connect(lambda: self.disc_but())
        ok_button = QPushButton("OK")
        ok_button.clicked.connect(
            lambda: self.use_cont_or_disc(field_data_list, field_elems, windw, field, layer))
        cancel_button = QPushButton("CANCEL")
        cancel_button.clicked.connect(lambda: windw.close())
        hbox = QHBoxLayout()
        hbox.addWidget(label1)
        hbox.addWidget(button1)
        hbox.addStretch(5)
        hbox.addWidget(label2)
        hbox.addWidget(button2)
        hbox2 = QHBoxLayout()
        hbox2.addStretch(15)
        hbox2.addWidget(ok_button)
        hbox2.addWidget(cancel_button)
        vbox = QVBoxLayout()
        vbox.addWidget(label)
        vbox.addLayout(hbox)
        vbox.addLayout(hbox2)
        windw.setLayout(vbox)
        windw.show()


    # Function for the action of continuous value radio button:
    def cont_but(self):
        self.sel_type = 1


    # Function for the action of discrete value radio button:
    def disc_but(self):
        self.sel_type = 2


    # Function for selecting the id generation of field elements or ranges based on the previous selection:
    def use_cont_or_disc(self, field_data_list, field_elems, windw, field, layer):
        windw.close()
        if self.sel_type == 1:
            field_data_list.append(self.sel_type)
            self.range_ids(field_data_list, field_elems, field, layer)
        if self.sel_type == 2:
            field_data_list.append(self.sel_type)
            self.val_ids(field_data_list, field_elems, field, layer)


    # Function for generating ids for ranges of the field values (when they are used as continuous types):
    def range_ids(self, field_data_list, field_elems, field, layer):
        field_elems = [float(i) for i in field_elems]
        whole_field_max = max(field_elems)
        self.current_min = min(field_elems)
        whole_field_min = copy.deepcopy(self.current_min)
        self.serial_for_ranges = 1
        self.this_usedids = []
        self.current_ranges = []
        self.used_serials = []
        windw = QWidget()
        windw.setWindowTitle("Range selection for continuous values")
        self.listofranges = QListWidget()
        self.listofranges.clear()
        plus_button = QPushButton("+")
        plus_button.clicked.connect(lambda: self.add_range(whole_field_max))
        minus_button = QPushButton("-")
        minus_button.clicked.connect(
            lambda: self.del_range(self.listofranges.selectedItems()[0], whole_field_min))
        ok_button = QPushButton("OK")
        ok_button.clicked.connect(
            lambda: self.finish_range(windw, field_data_list, field, layer, whole_field_max))
        cancel_button = QPushButton("CANCEL")

        cancel_button.clicked.connect(lambda: windw.close())
        hbox1 = QHBoxLayout()
        hbox1.addWidget(plus_button)
        hbox1.addWidget(minus_button)
        hbox2 = QHBoxLayout()
        hbox2.addWidget(ok_button)
        hbox2.addWidget(cancel_button)
        vbox = QVBoxLayout()
        vbox.addWidget(self.listofranges)
        vbox.addLayout(hbox1)
        vbox.addLayout(hbox2)
        windw.setLayout(vbox)
        windw.show()


    # Function for adding new range:
    def add_range(self, whole_field_max):
        if float(self.current_min) == float(whole_field_max):
            window_er = QWidget()
            window_er.setWindowTitle("Error window")
            label_er = QLabel("The last range is already assigned, please proceed to the finalization of these ranges!")
            ok_button_er = QPushButton("OK")
            ok_button_er.clicked.connect(lambda: window_er.close())
            vbox_er = QVBoxLayout()
            vbox_er.addWidget(label_er)
            hbox = QHBoxLayout()
            hbox.addStretch(5)
            hbox.addWidget(ok_button_er)
            hbox.addStretch(5)
            vbox_er.addLayout(hbox)
            window_er.setLayout(vbox_er)
            window_er.show()
        else:
            windw = QWidget()
            windw.setWindowTitle("Range settings")
            label = QLabel(
                "Please select the range and enter an ID:" + "\n" + "(Note that the maximum of the values in the selected field is : " + str(
                    whole_field_max) + "\nAnd also note that the assigned upper value of the range is not included[that is the begining of the next range]\nexcept for the last one)")
            label1 = QLabel("Range: " + str(self.current_min) + "- ")
            newmax = QLineEdit()
            label2 = QLabel("ID: ")
            this_id = QLineEdit()
            ok_button = QPushButton("OK")
            ok_button.clicked.connect(
                lambda: self.assign_range(whole_field_max, newmax.text(), this_id.text(), windw))
            cancel_button = QPushButton("CANCEL")
            cancel_button.clicked.connect(lambda: windw.close())
            hbox1 = QHBoxLayout()
            hbox1.addWidget(label1)
            hbox1.addWidget(newmax)
            hbox2 = QHBoxLayout()
            hbox2.addWidget(label2)
            hbox2.addWidget(this_id)
            hbox3 = QHBoxLayout()
            hbox3.addWidget(ok_button)
            hbox3.addWidget(cancel_button)
            vbox = QVBoxLayout()
            vbox.addWidget(label)
            vbox.addLayout(hbox1)
            vbox.addLayout(hbox2)
            vbox.addLayout(hbox3)
            windw.setLayout(vbox)
            windw.show()


    # Function for checking the previous settings and if correct assign the values:
    def assign_range(self, whole_field_max, newmax, this_id, windw):
        windw.close()
        newmax = float(newmax)
        error_text = []
        is_error = 0
        is_numb_max = self.is_numb(newmax)
        if not newmax:
            is_error = is_error + 1
            error_text.append("A range maximum must be given!")
        else:
            if is_numb_max == False:
                error_text.append("The given maximum must be a number!")
                is_error = is_error + 1
            if is_numb_max == True:
                if newmax > float(whole_field_max):
                    is_error = is_error + 1
                    error_text.append("The last range can not exceed the maximum of the field values!")
                if newmax < float(self.current_min):
                    is_error = is_error + 1
                    error_text.append("The maximum of the range must be over the given minimum!")
        if not this_id:
            is_error = is_error + 1
            error_text.append("A range ID must be given!")
        is_repeated_id = 0
        for elem in self.usedidnames:
            if this_id in elem[1]:
                is_repeated_id = is_repeated_id + 1
        usedids = []
        for ids in self.this_usedids:
            usedids.append(ids[1])
        if this_id in usedids:
            is_repeated_id = is_repeated_id + 1
        if is_repeated_id != 0:
            is_error = is_error + 1
            error_text.append("The ID must be unique, already used IDs are not accepted!")
        if is_error != 0:
            self.error_range(error_text, whole_field_max)
        else:
            usedids = [self.serial_for_ranges, this_id]
            self.this_usedids.append(usedids)
            this_range = [self.serial_for_ranges, this_id, self.current_min, newmax]
            self.current_ranges.append(this_range)
            this_range = ', '.join(map(str, this_range))
            self.listofranges.addItem(this_range)
            self.used_serials.append(self.serial_for_ranges)
            self.current_min = newmax
            self.serial_for_ranges = self.serial_for_ranges + 1


    # Function for error window for range assignment:
    def error_range(self, error_text, whole_field_max):
        windw = QWidget()
        windw.setWindowTitle("Error window")
        label = QLabel("The following error(s) occurred:")
        textbox = QTextBrowser()
        error_text = '\n'.join(error_text)
        textbox.append(error_text)
        ok_button = QPushButton("OK")
        ok_button.clicked.connect(lambda: self.reopen_range(windw, whole_field_max))
        cancel_button = QPushButton("CANCEL")
        cancel_button.clicked.connect(lambda: windw.close())
        hbox = QHBoxLayout()
        hbox.addWidget(ok_button)
        hbox.addWidget(cancel_button)
        vbox = QVBoxLayout()
        vbox.addWidget(label)
        vbox.addWidget(textbox)
        vbox.addLayout(hbox)
        windw.setLayout(vbox)
        windw.show()


    # Function for reopening range selection:
    def reopen_range(self, windw, whole_field_max):
        windw.close()
        self.add_range(whole_field_max)


    # Function for deleting assigned ranges, first :
    def del_range(self, item, whole_field_min):
        if not item:
            windw = QWidget()
            windw.setWindowTitle("Error window")
            label = QLabel("Please select a range to delete!")
            cancel_button = QPushButton("CANCEL")
            cancel_button.clicked.connect(lambda: windw.close())
            vbox = QVBoxLayout()
            vbox.addWidget(label)
            vbox.addWidget(cancel_button)
            windw.setLayout(vbox)
            windw.show()
        else:
            item = item.text()
            item_serial = str()
            for a in item:
                if a != ",":
                    item_serial = item_serial + str(a)
                else:
                    break
            item_serial = int(item_serial)
            serials_to_del = []
            listelems = [self.listofranges.item(i) for i in range(self.listofranges.count())]
            for x in self.used_serials:
                if item_serial <= x:
                    serials_to_del.append(x)
            for y in serials_to_del:
                for ids in self.this_usedids:
                    if ids[0] == y:
                        ind = self.this_usedids.index(ids)
                        del self.this_usedids[ind]
                for rngs in self.current_ranges:
                    if rngs[0] == y:
                        ind = self.current_ranges.index(rngs)
                        del self.current_ranges[ind]
                indofser = self.used_serials.index(y)
                del self.used_serials[indofser]
                for elem in listelems:
                    elem_text = elem.text()
                    elem_ser = str()
                    for b in elem_text:
                        if b != ",":
                            elem_ser = elem_ser + str(b)
                        else:
                            break
                    if int(elem_ser) == int(y):
                        self.listofranges.takeItem(self.listofranges.row(elem))
            current_maxes = [float(whole_field_min)]
            for elem in self.current_ranges:
                current_maxes.append(elem[3])
            newmax = max(current_maxes)
            self.current_min = newmax


    # Function for finishing the assigned ranges:
    def finish_range(self, windw, field_data_list, field, layer, whole_field_max):
        if float(self.current_min) != float(whole_field_max):
            erwind = QWidget()
            erwind.setWindowTitle("Error window")
            label = QLabel(
                "Please continue the range selection until the last is assigned (when you reach the maximum of values)!")
            ok_button = QPushButton("OK")
            ok_button.clicked.connect(lambda: erwind.close())
            vbox = QVBoxLayout()
            vbox.addWidget(label)
            vbox.addWidget(ok_button)
            erwind.setLayout(vbox)
            erwind.show()
        else:
            windw.close()
            appendlist = []
            for ids in self.this_usedids:
                thislist = [ids[1]]
                for elem in self.current_ranges:
                    if elem[1] == ids[1]:
                        thislist.append(elem[2])
                        thislist.append(elem[3])
                appendlist.append(thislist)
            field_data_list.append(appendlist)
            self.id_numb = self.id_numb + 1
            field_data_list.insert(0, self.id_numb)
            field_data_list.append(whole_field_max)
            self.stratdata.append(field_data_list)
            newusedids = [self.id_numb]
            idlist = []
            for ids in self.this_usedids:
                idlist.append(ids[1])
            newusedids.append(idlist)
            self.usedidnames.append(newusedids)
            list_to_stratlist = [self.id_numb, field, "Continuous data type", whole_field_max]
            list_to_stratlist = ', '.join(map(str, list_to_stratlist))
            self.stratlist.addItem(list_to_stratlist)
            field_id = layer.fields().indexFromName(field)
            self.usedfields.append([self.id_numb, field_id])


    # Function for ID assignment window for discrete field values:
    def val_ids(self, field_data_list, field_elems, field, layer):
        windw = QWidget()
        windw.setWindowTitle("ID selection")
        vbox = QVBoxLayout()
        scrollbox = QScrollArea()
        vbox.addWidget(scrollbox)
        scrollbox.setWidgetResizable(True)
        scrollarea = QWidget(scrollbox)
        scrollvbox = QVBoxLayout(scrollarea)
        scrollarea.setLayout(scrollvbox)
        label_dict = {}
        line_edit_dict = {}
        hbox_dict = {}
        elem_count = 1
        # Creating a list to store the used elements with their serial and element name, to use it for the id and value assignment:
        # It is a list of lists containing the serial number and the element name
        usedelems = []
        # A list containing just the name of the used elements to know if they are already got an ID selection line:
        usedelems2 = []
        for elem in field_elems:
            if elem in usedelems2:
                pass
            else:
                label_dict["label" + str(elem_count)] = QLabel("Please enter the ID of the field elem: " + str(elem))
                line_edit_dict["line" + str(elem_count)] = QLineEdit()
                hbox_dict["hbox" + str(elem_count)] = QHBoxLayout()
                thislabel = label_dict.get("label" + str(elem_count))
                thisline = line_edit_dict.get("line" + str(elem_count))
                thishbox = hbox_dict.get("hbox" + str(elem_count))
                thishbox.addWidget(thislabel)
                thishbox.addWidget(thisline)
                scrollvbox.addLayout(thishbox)
                usedelems.append([elem_count, elem])
                usedelems2.append(elem)
                elem_count = elem_count + 1
        scrollbox.setWidget(scrollarea)
        ok_button = QPushButton("OK")
        ok_button.clicked.connect(
            lambda: self.check_disc_ids(field_data_list, field_elems, field, layer, windw, line_edit_dict,
                                        usedelems, usedelems2))
        cancel_button = QPushButton("CANCEL")
        cancel_button.clicked.connect(lambda: windw.close())
        hbox = QHBoxLayout()
        hbox.addWidget(ok_button)
        hbox.addWidget(cancel_button)
        vbox.addLayout(hbox)
        windw.setLayout(vbox)
        windw.show()


    # Function for getting and checking assignment:
    def check_disc_ids(self, field_data_list, field_elems, field, layer, windw, line_edit_dict, usedelems, usedelems2):
        windw.close()
        is_error = 0
        is_missing_value = 0
        missinglist = []
        is_used_value = 0
        usedlist = []
        for elem in usedelems:
            counter = elem[0]
            thisline_er = line_edit_dict.get("line" + str(counter))
            thisid_er = thisline_er.text()
            if not thisid_er:
                is_missing_value = is_missing_value + 1
                is_error = is_error + 1
                missinglist.append(elem)
            else:
                if thisid_er in self.usedidnames or thisid_er in usedelems2:
                    is_error = is_error + 1
                    is_used_value = is_used_value + 1
                    usedlist.append(elem)
        if is_error != 0:
            windw = QWidget()
            windw.setWindowTitle("Error window")
            label = QLabel("The following error(s) occured:")
            vbox = QVBoxLayout()
            vbox.addWidget(label)
            if is_missing_value != 0:
                label2 = QLabel("The following field element(s) doesn't have an ID assigned:")
                textbox = QTextBrowser()
                missinglist = ', '.join(map(str, missinglist))
                textbox.append(missinglist)
                vbox.addWidget(label2)
                vbox.addWidget(textbox)
            if is_used_value != 0:
                label3 = QLabel("The following field element(s) got an ID which was already used:")
                textbox2 = QTextBrowser()
                usedlist = ', '.join(map(str, usedlist))
                textbox2.append(usedlist)
                vbox.addWidget(label3)
                vbox.addWidget(textbox2)
            ok_button = QPushButton("OK")
            ok_button.clicked.connect(
                lambda: self.ok_repoen_disc(field_data_list, field_elems, field, layer, windw))
            cancel_button = QPushButton("CANCEL")
            cancel_button.clicked.connect(lambda: windw.close())
            hbox = QHBoxLayout()
            hbox.addWidget(ok_button)
            hbox.addWidget(cancel_button)
            vbox.addLayout(hbox)
            windw.setLayout(vbox)
            windw.show()
        else:
            ids_list = []
            here_used_ids = []
            for elem in usedelems:
                thisline = line_edit_dict.get("line" + str(elem[0]))
                thisid = thisline.text()
                thislist = [thisid, elem[1]]
                ids_list.append(thislist)
                here_used_ids.append(thisid)
            field_data_list.append(ids_list)
            self.id_numb = self.id_numb + 1
            field_data_list.insert(0, self.id_numb)
            self.stratdata.append(field_data_list)
            newusedids = [self.id_numb]
            newusedids.append(here_used_ids)
            self.usedidnames.append(newusedids)
            list_to_stratlist = [self.id_numb, field, "Discrete data type"]
            list_to_stratlist = ', '.join(map(str, list_to_stratlist))
            self.stratlist.addItem(list_to_stratlist)
            field_id = layer.fields().indexFromName(field)
            self.usedfields.append([self.id_numb, field_id])


    # Function for reopen the id assignment window for discrete values if an error was found and ok is pressed:
    def ok_repoen_disc(self, field_data_list, field_elems, field, layer, windw):
        windw.close()
        self.val_ids(field_data_list, field_elems, field, layer)


    # Function for deleting stratification determiner:
    def delstratspec(self, stratdet):
        stratdet = stratdet[0].text()
        if not stratdet:
            windw = QWidget()
            windw.setWindowTitle("Error window")
            label = QLabel("Please select a stratification determiner!")
            ok_button = QPushButton("OK")
            ok_button.clicked.connect(lambda: windw.close())
            hbox = QHBoxLayout()
            hbox.addStretch(2)
            hbox.addWidget(ok_button)
            hbox.addStretch(2)
            vbox = QVBoxLayout()
            vbox.addWidget(label)
            vbox.addLayout(hbox)
            windw.setLayout(vbox)
            windw.show()
        else:
            indnumb = str()
            for ltr in stratdet:
                if ltr != ",":
                    indnumb = indnumb + str(ltr)
                else:
                    break
            strat_id = int(indnumb)
            for elem in self.usedfields:
                if int(elem[0]) == strat_id:
                    ind = self.usedfields.index(elem)
                    del self.usedfields[ind]
            for elem in self.usedidnames:
                if int(elem[0]) == strat_id:
                    ind = self.usedidnames.index(elem)
                    del self.usedidnames[ind]
            for elem in self.stratdata:
                if int(elem[0]) == strat_id:
                    ind = self.stratdata.index(elem)
                    del self.stratdata[ind]
            all_stratdet = [self.stratlist.item(i) for i in range(self.stratlist.count())]
            for elem in all_stratdet:
                elem_text = elem.text()
                elem_ser = str()
                for b in elem_text:
                    if b != ",":
                        elem_ser = elem_ser + str(b)
                    else:
                        break
                if int(elem_ser) == strat_id:
                    self.stratlist.takeItem(self.stratlist.row(elem))


    # Function for getting the information about the selected stratification determiner:
    def infostratspec(self, spec, layer):
        if not spec:
            windw = QWidget()
            windw.setWindowTitle("Error window")
            label = QLabel("Please select a stratification determiner!")
            ok_button = QPushButton("OK")
            ok_button.clicked.connect(lambda: windw.close())
            hbox = QHBoxLayout()
            hbox.addStretch(2)
            hbox.addWidget(ok_button)
            hbox.addStretch(2)
            vbox = QVBoxLayout()
            vbox.addWidget(label)
            vbox.addLayout(hbox)
            windw.setLayout(vbox)
            windw.show()
        else:
            spec = spec[0].text()
            indnumb = str()
            for ltr in spec:
                if ltr != ",":
                    indnumb = indnumb + str(ltr)
                else:
                    break
            for tlist in self.stratdata:
                if int(tlist[0]) == int(indnumb):
                    speclist = tlist
            fields = layer.fields()
            field_index = int(speclist[1])
            field_name = fields[field_index].name()
            id_value = str()
            if speclist[2] == 1:
                discrete_or_continuous = "Continuous variable"
                for elem in speclist[3]:
                    id_value = id_value + str(elem[0]) + ": " + str(elem[1]) + "-" + str(elem[2]) + "\n"
            else:
                discrete_or_continuous = "Discrete variable"
                for elem in speclist[3]:
                    id_value = id_value + str(elem[0]) + ": " + str(elem[1]) + "\n"
            windw = QWidget()
            windw.setWindowTitle("Information window")
            label = QLabel("Stratum determiner information:")
            textbox = QTextBrowser()
            appendtext = "ID: " + str(speclist[0]) + "\n" + "FIELD: " + str(field_name) + "\n" + "TYPE: " + str(discrete_or_continuous) + "\n" + "ID/VALUE PAIRS: " + "\n" + id_value
            if speclist[2] == 1:
                appendtext = appendtext + "MAXIMUM OF THE WHOLE RANGE: " + str(speclist[4])
            textbox.append(appendtext)
            ok_button = QPushButton("OK")
            ok_button.clicked.connect(lambda: windw.close())
            hbox = QHBoxLayout()
            hbox.addStretch(2)
            hbox.addWidget(ok_button)
            hbox.addStretch(2)
            vbox = QVBoxLayout()
            vbox.addWidget(label)
            vbox.addWidget(textbox)
            vbox.addLayout(hbox)
            windw.setLayout(vbox)
            windw.show()


    # Function for error_window for time out of stratum name generation:
    def error_timeout(self):
        windw = QWidget()
        windw.setWindowTitle("Error window")
        label = QLabel("Please make sure that you don't have too much fields with tha name stratum_name + a number!")
        ok_button = QPushButton("OK")
        ok_button.clicked.connect(lambda: windw.close())
        hbox = QHBoxLayout()
        hbox.addStretch(2)
        hbox.addWidget(ok_button)
        hbox.addStretch(2)
        vbox = QVBoxLayout()
        vbox.addWidget(label)
        vbox.addLayout(hbox)
        windw.setLayout(vbox)
        windw.show()


    # Function for sorting the stratum determiner list for stratum generation (by the field ID value):
    def sort_for_strat(self, elem):
        return elem[1]


    # Function for error window if no stratum determiner is given:
    def error_for_strat(self):
        windw = QWidget()
        windw.setWindowTitle("Error window")
        label = QLabel("Please add a stratum determiner before proceed to sampling!")
        ok_button = QPushButton("OK")
        ok_button.clicked.connect(lambda: windw.close())
        vbox = QVBoxLayout()
        vbox.addWidget(label)
        vbox.addWidget(ok_button)
        windw.setLayout(vbox)
        windw.show()


    # Function for selecting sample amount of each stratum:
    def sampperstratum(self, windw, layer):
        if not self.stratdata:
            self.error_for_strat()
        else:
            field_names = []
            for f in layer.fields():
                field_names.append(f.name())
            probenumb = 1
            id_error = 0
            timeout = time.time() + 60
            stratname_index = int()
            while True:
                stratname = "str_nm" + str(probenumb)
                if stratname not in field_names:
                    layer.dataProvider().addAttributes([QgsField(stratname, QVariant.String)])
                    layer.updateFields()
                    stratname_index = layer.fields().indexFromName(stratname)
                    break
                if time.time() > timeout:
                    id_error = 1
                    break
                else:
                    probenumb = probenumb + 1
            if id_error == 1:
                self.error_timeout()
            else:
                windw.close()
                stratdata = sorted(self.stratdata, key=self.sort_for_strat)
                fields = layer.fields()
                first_strat = 1
                layer.startEditing()
                for field in fields:
                    field_id = layer.fields().indexFromName(field.name())
                    for elem in stratdata:
                        if int(elem[1]) == int(field_id):
                            feats = layer.getFeatures()
                            for feat in feats:
                                attrs = feat.attributes()
                                value = attrs[field_id]
                                if elem[2] == 1:
                                    for rang in elem[3]:
                                        if float(rang[1]) <= float(value) < float(rang[2]) or float(value) == float(
                                                rang[2]) == float(elem[4]):
                                            if first_strat == 1:
                                                changeto = str(rang[0])
                                            else:
                                                changeto = str(attrs[stratname_index]) + str(rang[0])
                                            layer.changeAttributeValue(feat.id(), stratname_index, str(changeto))
                                            break
                                if elem[2] == 2:
                                    for val in elem[3]:
                                        if str(value) == str(val[1]):
                                            if first_strat == 1:
                                                changeto = str(val[0])
                                            else:
                                                changeto = str(attrs[stratname_index]) + str(val[0])
                                            layer.changeAttributeValue(feat.id(), stratname_index, str(changeto))
                                            break
                            first_strat = first_strat + 1
                            this_ind = stratdata.index(elem)
                            del stratdata[this_ind]
                            break
                layer.commitChanges()
                self.strat_is_over(layer, stratname_index)


    # Function for selecting between using and not using oversampling (1 with oversampling and 2 without):
    def strat_is_over(self, layer, stratname_index):
        windw = QWidget()
        windw.setWindowTitle("Oversampling selection")
        label = QLabel(
            "If oversampling is desired in each stratum, please select OVERSAMPLING button,\nif not, please select REGULAR button)")
        oversample_button = QPushButton("OVERSAMPLING")
        oversample_button.clicked.connect(lambda: self.is_gps_strat(windw, 1, layer, stratname_index))
        regular_button = QPushButton("REGULAR")
        regular_button.clicked.connect(lambda: self.is_gps_strat(windw, 2, layer, stratname_index))
        hbox = QHBoxLayout()
        hbox.addWidget(oversample_button)
        hbox.addWidget(regular_button)
        vbox = QVBoxLayout()
        vbox.addWidget(label)
        vbox.addLayout(hbox)
        windw.setLayout(vbox)
        windw.show()


    # Function for deciding if displaying GPS coordinates as well is desired or not:
    def is_gps_strat(self, windw_prev, selval, layer, stratname_index):
        windw_prev.close()
        windw = QWidget()
        windw.setWindowTitle("GPS COORDINATES")
        label = QLabel("Do you want the GPS coordinates to be displayed as well in the sample point layers attribute table?")
        yes_button = QPushButton("YES")
        yes_button.clicked.connect(lambda: self.is_gps_strat_do(windw, selval, layer, stratname_index, 2))
        no_button = QPushButton("NO")
        no_button.clicked.connect(lambda: self.is_gps_strat_do(windw, selval, layer, stratname_index, 1))
        hbox = QHBoxLayout()
        hbox.addWidget(yes_button)
        hbox.addWidget(no_button)
        vbox = QVBoxLayout()
        vbox.addWidget(label)
        vbox.addLayout(hbox)
        windw.setLayout(vbox)
        windw.show()


    # Function for calling the strat_samp function and change the isgps value based on the previous selection (if gps_sel is 1, GPS coordinates don't needed, if 2
    # they are needed):
    def is_gps_strat_do(self, windw_prev, selval, layer, stratname_index, gps_sel):
        windw_prev.close()
        if gps_sel == 2:
            self.isgps = 2
        self.strat_samp(selval, layer, stratname_index)


    # Function for opening the stratum sampling settings window:
    def strat_samp(self, selval, layer, stratname_index):
        strat_info = [int(selval)]
        strats = []
        feats = layer.getFeatures()
        for feat in feats:
            attrs = feat.attributes()
            this_strat = attrs[stratname_index]
            if this_strat in strats:
                pass
            else:
                strats.append(this_strat)
        # list for storing lists of stratums and their IDs (for retrieving information from qlineedit storing dictionaries [ID, stratum])
        strat_samp_data = []
        strat_dict = {}
        strat_line_edit_dict = {}
        strat_hbox_dict = {}
        strat_over_label = {}
        strat_over_line = {}
        strat_over_hbox = {}
        strat_over_vbox = {}
        windw = QWidget()
        windw.setWindowTitle("Stratum sampling settings")
        label = QLabel("Please enter the sample (and oversample) number for each stratum:")
        vbox = QVBoxLayout()
        vbox.addWidget(label)
        scrollbox = QScrollArea()
        vbox.addWidget(scrollbox)
        scrollbox.setWidgetResizable(True)
        scrollarea = QWidget(scrollbox)
        scrollvbox = QVBoxLayout(scrollarea)
        scrollarea.setLayout(scrollvbox)
        counter = 1
        for strat in strats:
            this_data = [int(counter), strat]
            strat_samp_data.append(this_data)
            strat_dict["stratum" + str(counter)] = QLabel(
                "Please enter the number of desired sample for the stratum: " + str(strat))
            strat_line_edit_dict["stratum" + str(counter)] = QLineEdit()
            strat_hbox_dict["hbox" + str(counter)] = QHBoxLayout()
            thislabel = strat_dict.get("stratum" + str(counter))
            thisline = strat_line_edit_dict.get("stratum" + str(counter))
            thishbox = strat_hbox_dict.get("hbox" + str(counter))
            thishbox.addWidget(thislabel)
            thishbox.addWidget(thisline)
            if selval == 1:
                strat_over_label["stratum" + str(counter)] = QLabel("Oversample number:")
                strat_over_line["stratum" + str(counter)] = QLineEdit()
                strat_over_hbox["stratum_hbox" + str(counter)] = QHBoxLayout()
                strat_over_vbox["stratum_vbox" + str(counter)] = QVBoxLayout()
                overlabel = strat_over_label.get("stratum" + str(counter))
                overline = strat_over_line.get("stratum" + str(counter))
                overhbox = strat_over_hbox.get("stratum_hbox" + str(counter))
                overvbox = strat_over_vbox.get("stratum_vbox" + str(counter))
                overhbox.addWidget(overlabel)
                overhbox.addWidget(overline)
                overvbox.addLayout(thishbox)
                overvbox.addLayout(overhbox)
                scrollvbox.addLayout(overvbox)
            else:
                scrollvbox.addLayout(thishbox)
            counter = counter + 1
        scrollbox.setWidget(scrollarea)
        ok_button = QPushButton("OK")
        ok_button.clicked.connect(
            lambda: self.finish_strat(strat_samp_data, strat_line_edit_dict, strat_over_line, selval, strat_info, layer,
                                      stratname_index, windw))
        cancel_button = QPushButton("CANCEL")
        cancel_button.clicked.connect(lambda: windw.close())
        hbox_button = QHBoxLayout()
        hbox_button.addWidget(ok_button)
        hbox_button.addWidget(cancel_button)
        vbox.addLayout(hbox_button)
        windw.setLayout(vbox)
        windw.show()


    # Function for checking the assigned sample and oversample numbers and then creating the final list storing them:
    def finish_strat(self, strat_samp_data, strat_line_edit_dict, strat_over_line, selval, strat_info, layer,
                     stratname_index, windw_prev):
        windw_prev.close()
        is_error = 0
        strat_list_to_append = []
        is_samp = 0
        is_samp_int = 0
        is_samp_b_zero = 0
        is_oversamp = 0
        is_oversamp_int = 0
        is_oversamp_b_zero = 0
        for elem in strat_samp_data:
            thislist = []
            this_numb = strat_line_edit_dict.get("stratum" + str(elem[0]))
            this_numb = this_numb.text()
            if not this_numb:
                is_samp = is_samp + 1
                is_error = is_error + 1
            else:
                isint = self.integer_check(this_numb)
                if isint == 1:
                    is_samp_int = is_samp_int + 1
                    is_error = is_error + 1
                else:
                    this_numb = int(this_numb)
                    if this_numb < 0:
                        is_samp_b_zero = is_samp_b_zero + 1
                        is_error = is_error + 1
                    else:
                        thislist.append(elem[1])
                        thislist.append(this_numb)
            if selval == 1:
                this_ov = strat_over_line.get("stratum" + str(elem[0]))
                this_ov = this_ov.text()
                if not this_ov:
                    is_oversamp = is_oversamp + 1
                    is_error = is_error + 1
                else:
                    ovr_isint = self.integer_check(this_ov)
                    if ovr_isint == 1:
                        is_oversamp_int = is_oversamp_int + 1
                        is_error = is_error + 1
                    else:
                        this_ov = int(this_ov)
                        if this_ov < 0:
                            is_oversamp_b_zero = is_oversamp_b_zero + 1
                            is_error = is_error + 1
                        else:
                            thislist.append(this_ov)
            strat_list_to_append.append(thislist)
        if is_error != 0:
            error_text = []
            windw_er = QWidget()
            windw_er.setWindowTitle("Error window")
            label = QLabel("The following errors occured:")
            if is_samp != 0:
                error_text.append("There are one or more sample numbers that are not given")
            if is_samp_int != 0:
                error_text.append("There are one or more sample numbers that are not given in integer forms")
            if is_samp_b_zero != 0:
                error_text.append("There are one or more sample numbers that are below zero")
            if selval == 1:
                if is_oversamp != 0:
                    error_text.append("There are one or more oversample numbers that are not given")
                if is_oversamp_int != 0:
                    error_text.append("There are one or more oversample numbers that are not given in integer form")
                if is_oversamp_b_zero != 0:
                    error_text.append("There are one or more oversample numbers that are below zero")
            error_text = '\n'.join(error_text)
            textbox = QTextBrowser()
            textbox.append(error_text)
            ok_button = QPushButton("OK")
            ok_button.clicked.connect(lambda: self.strat_samp(windw_er, selval, layer, stratname_index))
            hbox = QHBoxLayout()
            hbox.addStretch(6)
            hbox.addWidget(ok_button)
            hbox.addStretch(6)
            vbox = QVBoxLayout()
            vbox.addWidget(label)
            vbox.addWidget(textbox)
            vbox.addLayout(hbox)
            windw_er.setLayout(vbox)
            windw_er.show()
        else:
            strat_info.append(strat_list_to_append)
            self.do_samp_strat(strat_info, layer, stratname_index)


    # Function for doing the sampling for the different strata:
    def do_samp_strat(self, strat_info, layer, stratname_index):
        usedlays = []
        fields = layer.fields()
        stratname_field = fields[stratname_index]
        stratname = stratname_field.name()
        origcrs = layer.crs().authid()
        if strat_info[0] == 2:
            for elem in strat_info[1]:
                feats = layer.getFeatures()
                sampl_lay = QgsVectorLayer("Polygon?crs={0}".format(origcrs), "temp_lay", "memory")
                usedlays.append(sampl_lay)
                pr = sampl_lay.dataProvider()
                for f in fields:
                    pr.addAttributes([f])
                sampl_lay.updateFields()
                for feat in feats:
                    attrs = feat.attributes()
                    strat = attrs[stratname_index]
                    if elem[0] == strat:
                        pr.addFeatures([feat])
                sampl_lay.updateExtents()
                oversample = 0
                is_oversampling = 1
                is_windw = 0
                is_from_strat = 1
                # Grid placement function called with default values for functionalities of the sampling from non stratified data:
                self.gridplacement(sampl_lay, elem[1], 0, 1, oversample, is_oversampling, is_from_strat,
                                   elem[0])
            self.do_del_field(layer, stratname)
        if strat_info[0] == 1:
            for elem in strat_info[1]:
                feats = layer.getFeatures()
                sampl_lay = QgsVectorLayer("Polygon?crs={0}".format(origcrs), "temp_lay", "memory")
                usedlays.append(sampl_lay)
                pr = sampl_lay.dataProvider()
                for f in fields:
                    pr.addAttributes([f])
                sampl_lay.updateFields()
                for feat in feats:
                    attrs = feat.attributes()
                    strat = attrs[stratname_index]
                    if elem[0] == strat:
                        pr.addFeatures([feat])
                sampl_lay.updateExtents()
                oversample = elem[2]
                self.is_oversampling = 2
                is_windw = 0
                is_from_strat = 1
                # Grid placement function called with default values for functionalities of the sampling from non stratified data:
                self.gridplacement(sampl_lay, elem[1], 0, 1, oversample, 2, is_from_strat,
                                   elem[0])
            self.do_del_field(layer, stratname)





    # -------------------------------------GRTS AREAL--------------------------------------------------------------------





    # Generator function to flatten multiple nested lists:
    def flatten(self, l):
        for el in l:
            if isinstance(el, collections.abc.Iterable) and not isinstance(el, (str, bytes)):
                yield from self.flatten(el)
            else:
                yield el


    # Error window for grid settings:
    def errorforquadr(self, error_text):
        windw = QWidget()
        windw.setWindowTitle("ERROR WINDOW")
        label = QLabel("The following error(s) occured (Please press OK to go back to the settings window):")
        text = QTextBrowser()
        error_text = ', '.join(error_text)
        text.append(error_text)
        ok_button = QPushButton()
        ok_button.setText("OK")
        ok_button.clicked.connect(lambda: self.error_ok(windw))
        vbox = QVBoxLayout()
        vbox.addWidget(label)
        vbox.addWidget(text)
        vbox.addStretch(3)
        vbox.addWidget(ok_button)
        windw.setLayout(vbox)
        windw.show()


    # Function for the error window if the while loop is longer than it should:
    def error_while(self):
        windw = QWidget()
        windw.setWindowTitle("ERROR WINDOW")
        label = QLabel(
            "The polygon ID field generation lasted longer than it should! Please check the amount of fields with name grts_id and a number!")
        cancel_button = QPushButton("CANCEL")
        cancel_button.clicked.connect(lambda: windw.close())
        vbox = QVBoxLayout()
        vbox.addWidget(label)
        vbox.addWidget(cancel_button)
        windw.setLayout(vbox)
        windw.show()


    # Function that closes the error window and opens the sampling settings window again:
    def error_ok(self, windw):
        windw.close()
        self.dlg.show()
        result = self.dlg.exec_()
        if result:
            self.iscorrect(self.dlg.cbox.currentText(), self.dlg.numb.text(), self.dlg.custom_quadr.text(),
                           self.quadrchecker, self.dlg.oversample.text(), self.is_oversampling)


    # Checking if the number is an integer (returns 0 if it is an integer and 1 if it is not):
    def integer_check(self, n):
        notintvalue = 0
        try:
            float(n)
        except ValueError:
            notintvalue = notintvalue + 1
        if notintvalue == 0:
            if float(n) - round(float(n), 0) != 0:
                notintvalue = notintvalue + 1
        if notintvalue != 0:
            isintvalue = 1
            return isintvalue
        else:
            isintvalue = 0
            return isintvalue


    # Function for using default quadrant ammount:
    def deftoggled(self, custom_quadr):
        self.quadrchecker = 1
        custom_quadr.setEnabled(False)
        self.dlg.label_cust_1.setEnabled(False)
        self.dlg.label_cust_2.setEnabled(False)


    # Function for using custom quadrant ammount:
    def customtoggled(self, custom_quadr):
        self.quadrchecker = 2
        custom_quadr.setEnabled(True)
        self.dlg.label_cust_1.setEnabled(True)
        self.dlg.label_cust_2.setEnabled(True)


    # Function for the disable oversampling button:
    def notoversample(self, oversample):
        self.is_oversampling = 1
        oversample.setEnabled(False)
        self.dlg.label_ov_1.setEnabled(False)
        self.dlg.label_ov_2.setEnabled(False)


    # Function for oversample button:
    def oversample(self, oversample):
        self.is_oversampling = 2
        oversample.setEnabled(True)
        self.dlg.label_ov_1.setEnabled(True)
        self.dlg.label_ov_2.setEnabled(True)


    # Function of the window asking back if the settings are correct:
    def iscorrect(self, layer, samp_number, custom, quadrchecker, oversample, is_oversampling):
        windw = QWidget()
        windw.setWindowTitle("ACCEPTING SETTINGS")
        label = QLabel(
            "Are you sure that everything is correctly set?\nIf yes please press YES! If not please press NO!")
        no_button = QPushButton("NO")
        no_button.clicked.connect(lambda: self.error_ok(windw))
        yes_button = QPushButton("YES")
        yes_button.clicked.connect(lambda: self.is_gps(layer, samp_number, custom, quadrchecker, oversample, is_oversampling, windw))
        vbox = QVBoxLayout()
        vbox.addWidget(label)
        hbox = QHBoxLayout()
        hbox.addWidget(yes_button)
        hbox.addWidget(no_button)
        vbox.addLayout(hbox)
        windw.setLayout(vbox)
        windw.show()


    # Function for the window checking if GPS coordinates are needed (It calls the next function with the decision_numb = 1, if not and 2 if needed):
    def is_gps(self, layer, samp_number, custom, quadrchecker, oversample, is_oversampling, windwp):
        windwp.close()
        windw = QWidget()
        windw.setWindowTitle("GPS COORDINATES")
        label = QLabel("Do you want the GPS coordinates to be displayed as well in the sample point layers attribute table?")
        yes_button = QPushButton("YES")
        yes_button.clicked.connect(lambda: self.is_gps_do(layer, samp_number, custom, quadrchecker, oversample, is_oversampling, 2, windw))
        no_button = QPushButton("NO")
        no_button.clicked.connect(lambda: self.is_gps_do(layer, samp_number, custom, quadrchecker, oversample, is_oversampling, 1, windw))
        hbox = QHBoxLayout()
        hbox.addWidget(yes_button)
        hbox.addWidget(no_button)
        vbox = QVBoxLayout()
        vbox.addWidget(label)
        vbox.addLayout(hbox)
        windw.setLayout(vbox)
        windw.show()


    # Function for changing the isgps value and call the gridplacement function:
    def is_gps_do(self, layer, samp_number, custom, quadrchecker, oversample, is_oversampling, decision_numb, windw):
        windw.close()
        if decision_numb == 2:
            self.isgps = 2
        # Calling the grid placement function with some values that are set default because they are needed only if the function is called from stratification:
        self.gridplacement(layer, samp_number, custom, quadrchecker, oversample, is_oversampling, 0, 0)


    # Grid placement on the layer:
    def gridplacement(self, layer, samp_number, custom, quadrchecker, oversample, is_oversampling,
                      is_from_strat, strat_name):
        erro_text = []
        is_error = 0
        samp_number_problem = 0
        oversample_porblem = 0
        if not layer:
            erro_text.append("A layer should be selected")
            is_error = is_error + 1
        else:
            if is_from_strat == 0:
                layer = QgsProject.instance().mapLayersByName(layer)[0]
            if not layer.isValid():
                erro_text.append("The layer must be a valid QGIS vector layer")
                is_error = is_error + 1
            else:
                prob_with_geom = 0
                feats = layer.getFeatures()
                for feat in feats:
                    if prob_with_geom == 0:
                        geom = feat.geometry()
                        if not geom.isGeosValid():
                            prob_with_geom = 1
                    else:
                        break
                if prob_with_geom == 1:
                    erro_text.append("All the layer geometries must be valid")
                    is_error = is_error + 1
        if not samp_number:
            erro_text.append("A sample number must be given")
            is_error = is_error + 1
            samp_number_problem = samp_number_problem + 1
        else:
            isintvalue = self.integer_check(samp_number)
            if isintvalue != 0:
                erro_text.append("The sample number must be an integer")
                is_error = is_error + 1
                samp_number_problem = samp_number_problem + 1
            else:
                if int(samp_number) < 0:
                    erro_text.append("The number of desired samples must be over zero")
                    is_error = is_error + 1
                    samp_number_problem = samp_number_problem + 1
        if is_oversampling == 2:
            if not oversample:
                erro_text.append("If oversampling is selected there should be a number of additional samples")
                is_error = is_error + 1
                oversample_porblem = oversample_porblem + 1
            else:
                isintover = self.integer_check(oversample)
                if isintover != 0:
                    erro_text.append("The number of additional samples must be an integer")
                    is_error = is_error + 1
                    oversample_porblem = oversample_porblem + 1
                else:
                    if int(oversample) < 0:
                        erro_text.append("The number of additional samples must be over zero")
                        is_error = is_error + 1
                        oversample_porblem = oversample_porblem + 1
        if oversample_porblem == 0 and samp_number_problem == 0 and is_oversampling == 2:
            samp_number = int(samp_number) + int(oversample)
        if quadrchecker == 1:
            if samp_number_problem == 0 and oversample_porblem == 0:
                cellgen = int(math.ceil(math.log(int(samp_number), 4) + 2))
                cellnumb = 4 ** cellgen
        if quadrchecker == 2:
            if samp_number_problem == 0 and oversample_porblem == 0:
                if not custom:
                    erro_text.append("The custom power of four must be given if custom is selected")
                    is_error = is_error + 1
                else:
                    customisint = self.integer_check(custom)
                    if customisint != 0:
                        erro_text.append("The custom power of four must be an integer")
                        is_error = is_error + 1
                    else:
                        if int(custom) < 0:
                            erro_text.append("The custom power of four must be over zero")
                            is_error = is_error + 1
                        else:
                            cellgen = int(math.ceil(math.log(int(samp_number), 4) + 2))
                            if cellgen > int(custom):
                                erro_text.append(
                                    "If custom quadrant number is selected, the selected power of 4 should be higher than the default(base 4 logarithm of the selected sample numbers plus 2)")
                                is_error = is_error + 1
                            if int(custom) >= cellgen:
                                cellgen = int(custom)
                                cellnumb = 4 ** cellgen
        if is_error != 0:
            self.errorforquadr(erro_text)
        else:
            # Here starts the actual grid placement
            # First we obtain the X and Y extent of the layer and check which is bigger
            layextent = layer.extent()
            xextentmax = layextent.xMaximum()
            xextentmin = layextent.xMinimum()
            yextentmax = layextent.yMaximum()
            yextentmin = layextent.yMinimum()
            xextentmax = float(xextentmax)
            xextentmin = float(xextentmin)
            yextentmax = float(yextentmax)
            yextentmin = float(yextentmin)
            xrange = xextentmax - xextentmin
            yrange = yextentmax - yextentmin
            basenumb = int()
            if xrange > yrange:
                basenumb = 1
            if yrange > xrange:
                basenumb = 2
            # If the X is bigger the first extent of the grid in both directions will be as long as the range
            # of the X extent of the layer
            if basenumb == 1:
                rangedif = xrange - yrange
                rangeadd = rangedif / 2
                yextentmax = yextentmax + rangeadd
                yextentmin = yextentmin - rangeadd
                xextentmax = xextentmax
                xextentmin = xextentmin
            # If the Y is bigger the first extent of the grid in both directions will be as long as the range
            # of the Y extent of the layer
            if basenumb == 2:
                rangedif = yrange - xrange
                rangeadd = rangedif / 2
                xextentmax = xextentmax + rangeadd
                xextentmin = xextentmin - rangeadd
                yextentmax = yextentmax
                yextentmin = yextentmin
            # Get the size of the cells based on the extent of the whole grid and the
            # desired cell number in a line
            cellx = (xextentmax - xextentmin) / (2 ** cellgen)
            celly = (yextentmax - yextentmin) / (2 ** cellgen)
            # Calculating the length to add for the previously calculated extent for letting the
            # grid be one cell size bigger than the longest extent of the layer
            addx = (cellx * (2 ** cellgen)) / ((2 ** cellgen) - 1)
            addy = addx
            # For the random plaecemnt of the grid we need to cut the added length to two part and randomly
            # add it to each side of the grid
            randfractx = round(uniform(1, 100), 1)
            randfracty = round(uniform(1, 100), 1)
            addx1 = addx / randfractx
            addx2 = addx - addx1
            addy1 = addy / randfracty
            addy2 = addy - addy1
            randnumbx = randint(1, 2)
            randnumby = randint(1, 2)
            if randnumbx == 1:
                xextentmax = xextentmax + addx1
                xextentmin = xextentmin - addx2
            if randnumbx == 2:
                xextentmax = xextentmax + addx2
                xextentmin = xextentmin - addx1
            if randnumby == 1:
                yextentmax = yextentmax + addy1
                yextentmin = yextentmin - addy2
            if randnumby == 2:
                yextentmax = yextentmax + addy2
                yextentmin = yextentmin - addy1
            # Calculating the now correct cell extent
            cellx = (xextentmax - xextentmin) / (2 ** cellgen)
            celly = (yextentmax - yextentmin) / (2 ** cellgen)
            extent = str(xextentmin) + ',' + str(xextentmax) + ',' + str(yextentmin) + ',' + str(yextentmax)
            crs = layer.crs()
            parameters = {'EXTENT': extent, 'HSPACING': cellx, 'VSPACING': celly, 'TYPE': 2, 'CRS': crs,
                          'OUTPUT': 'TEMPORARY_OUTPUT', 'HOVERLAY': 0, 'VOVERLAY': 0}
            # Place the grid
            grid = processing.run('qgis:creategrid', parameters)
            gridlay = grid['OUTPUT']
            self.gridorder(cellgen, cellnumb, layer, gridlay, samp_number, oversample, is_oversampling, is_from_strat,
                           strat_name)


    # Creating the order of cell IDs to match the here created hierarchical order of cell serial numbers:
    def gridorder(self, cellgen, cellnumb, layer, gridlayer, samp_numb, oversample, is_oversampling, is_from_strat,
                  strat_name):
        # Creating a list with all the possible cell numbers
        numblist = []
        for i in range(1, (cellnumb + 1)):
            numblist.append(i)
        # The grid cells are ordered by the columns, but we need them to be ordered by the lines, so
        # first we order the cell numbers the way we need them (but here only just every second ones)
        initlist = []
        for i in range(1, (2 ** cellgen + 1), 2):
            for x in range(1, (2 ** cellgen + 1), 2):
                number = numblist[i + ((x - 1) * (2 ** cellgen)) - 1]
                initlist.append(number)
        # Here we already created a list with sublists of the lowest levels of quadrants
        # In the next section we create quadrants from the previous lists (of quadtrants)
        # until there are only four lists left
        newlist = []
        for i in initlist:
            listquadr = [numblist[i - 1], numblist[i + (2 ** cellgen) - 1], numblist[i], numblist[i + (2 ** cellgen)]]
            newlist.append(listquadr)
        numblist = newlist
        if cellgen > 2:
            for i in range(1, (cellgen - 1)):
                usedlist = []
                newlist = []
                for x in range(len(numblist)):
                    if numblist[x] not in usedlist:
                        listquadr = [numblist[x], numblist[x + 1], numblist[x + (2 ** (cellgen - i))],
                                     numblist[x + (2 ** (cellgen - i)) + 1]]
                        newlist.append(listquadr)
                        usedlist.append(numblist[x])
                        usedlist.append(numblist[x + 1])
                        usedlist.append(numblist[x + (2 ** (cellgen - i))])
                        usedlist.append(numblist[x + (2 ** (cellgen - i)) + 1])
                numblist = newlist
        numblist = list(self.flatten(numblist))
        # After we obtained the list of the quadrants and their elements, we need
        # to create the first random order of 1 to 4, then go through the elements from each
        # create four values by ading it a second digit by a random permutation of
        # 1 to 4. We does this until the last level is done (there are as many as the length of cellgen)
        orderlist = [1, 2, 3, 4]
        shuffle(orderlist)
        for i in range(1, cellgen):
            newlist = list()
            for x in orderlist:
                randperm = [1, 2, 3, 4]
                shuffle(randperm)
                for y in randperm:
                    elem = str(x) + str(y)
                    newlist.append(elem)
            orderlist = newlist
        orderlist = list(map(int, orderlist))
        orderlist_old = copy.deepcopy(orderlist)
        # If oversampling is selected, we reverse the previously
        # designed numbers
        if is_oversampling == 2:
            revord = []
            for elem in orderlist:
                elem = str(elem)
                elem = ''.join(reversed(elem))
                elem = int(elem)
                revord.append(elem)
            orderlist = revord
        # Lastly we get the id of the cells, check for the ordinal
        # number of it in the constructed quadrant based list and check for the
        # value in the same place of order in the quadrant order number list, then
        # change the value to it in the id field
        gridfeatures = gridlayer.getFeatures()
        attrindex = gridlayer.fields().indexFromName("id")
        gridlayer.startEditing()
        for feat in gridfeatures:
            attrs = feat.attributes()
            idnumb = attrs[attrindex]
            indofid = numblist.index(idnumb)
            ordnumb = orderlist[indofid]
            gridlayer.changeAttributeValue(feat.id(), attrindex, ordnumb)
        gridlayer.commitChanges()
        self.sampl(gridlayer, layer, samp_numb, oversample, is_from_strat, strat_name)


    # Defining a function for grid sorting in the next section:
    def get_name(self, ft):
        return ft['id']


    # Function for creating the sample line:
    def sampl(self, gridlayer, layer, samp_numb, oversample, is_from_strat, strat_name):
        # Creating an ID field (named grts_id) for polygons for future references (values will be assigned in the next section):
        field_names = []
        for f in layer.fields():
            field_names.append(f.name())
        probenumb = 1
        id_error = 0
        timeout = time.time() + 60
        idfieldname_index = int()
        idfieldname_orig = str()
        while True:
            idfieldname = "grts_id" + str(probenumb)
            if idfieldname not in field_names:
                layer.dataProvider().addAttributes([QgsField(idfieldname, QVariant.Int)])
                layer.updateFields()
                idfieldname_orig = copy.deepcopy(idfieldname)
                idfieldname_index = layer.fields().indexFromName(idfieldname)
                break
            if time.time() > timeout:
                id_error = 1
                break
            else:
                probenumb = probenumb + 1
        if id_error == 1:
            self.error_while()
        else:
            idfieldname = idfieldname_orig
            # Getting whole polygons' inclusion probabilities
            # (by creating a list containing the list of ids, areas and line segments of each polygons)
            # (plus filling the polygon id field):
            getcontext().prec = 10
            layarea = Decimal()
            features = layer.getFeatures()
            for fet in features:
                geom = fet.geometry()
                area = geom.area()
                area = Decimal(area)
                layarea = layarea + area
            layarea = Decimal(layarea)
            # Get the whole line length, which is the sample number multiplied by 100,000
            wholeline = int(samp_numb) * 100000
            wholeline = Decimal(wholeline)
            polygon_probs = []
            idnumb = 1
            # Assigning id numbers for polygons and getting the area occupied by one polygon on the whole line (The id and the length
            # is stored in the polygon_probs variable, first is the id, second is the area of the polygon and third is the length of its line segment)
            fts = layer.getFeatures()
            layer.startEditing()
            for feat in fts:
                fid = feat.id()
                layer.changeAttributeValue(fid, idfieldname_index, idnumb)
                polylist = [idnumb]
                geom = feat.geometry()
                area = geom.area()
                area = Decimal(area)
                polylist.append(area)
                area_ratio = area / layarea
                polyline = wholeline * area_ratio
                polyline = int(polyline)
                polylist.append(polyline)
                polygon_probs.append(polylist)
                idnumb = idnumb + 1
            layer.updateFields()
            layer.commitChanges()
            # Cutting the grid layer by the original layer to be sampled:
            params = {'INPUT': gridlayer, 'OVERLAY': layer, 'OUTPUT': 'TEMPORARY_OUTPUT'}
            cutted = processing.run('native:clip', params)
            cuttedlayer = cutted['OUTPUT']
            # Sorting grid features by the id:
            feats = cuttedlayer.getFeatures()
            idindex = cuttedlayer.fields().indexFromName("id")
            feats = sorted(feats, key=self.get_name)
            # Looping through the sorted features and get the data of its line segment
            # (a list of two lists; in the first list there is the quadrant id and will be the extent of the line segment it is the ordrange,
            # and in the second list there are lists containing the id of the polygon and the extent of it inside the quadrant, which is the final_order_polygons):
            sample_order_list = []
            line_length = 0
            begin = 0
            begin_for_whole_quadrant = 0
            thisdict = {}
            for feat in feats:
                # First assign the id of the grid to the first list ()ordrange and then get the polygons inside the grid (as a layer, stored in the dictionary thisdict)
                # it will be useful for selecting the polygon part in which a random point selection is needed:
                featattrs = feat.attributes()
                feat_order = featattrs[idindex]
                ordrange = [feat_order]
                fid = feat.id()
                feataslay = cuttedlayer.materialize(QgsFeatureRequest().setFilterFid(fid))
                pars = {'INPUT': layer, 'OVERLAY': feataslay, 'OUTPUT': 'TEMPORARY_OUTPUT'}
                onelay = processing.run('native:clip', pars)
                onelayer = onelay['OUTPUT']
                thisdict["grid_" + str(feat_order)] = onelayer
                # Creating the information list of the polygons (id, order, length in the line, it is the polylist):
                ft = onelayer.getFeatures()
                polylist = []
                thisord = 1
                for fit in ft:
                    featdata = []
                    featattrs = fit.attributes()
                    featid = featattrs[idfieldname_index]
                    featdata.append(featid)
                    featdata.append(thisord)
                    for elem in polygon_probs:
                        if int(elem[0]) == int(featid):
                            geom = fit.geometry()
                            area = geom.area()
                            area = Decimal(area)
                            arearatio = area / elem[1]
                            arearatio = Decimal(arearatio)
                            featlength = elem[2] * arearatio
                            featlength = int(featlength)
                            featdata.append(featlength)
                        else:
                            pass
                    polylist.append(featdata)
                    thisord = thisord + 1
                # Creating the random permutation of the polygons and its place on the line (It is the final_order_polygons)
                # and calculating the length of the whole line of the quadrant:
                randperm = [*range(1, (thisord + 1))]
                shuffle(randperm)
                final_order_polygons = []
                whole_line_length = int()
                for i in randperm:
                    for el in polylist:
                        if el[1] == i:
                            polydata = [el[0]]
                            polyplace = [*range(begin, (begin + el[2] + 1))]
                            polydata.append(polyplace)
                            final_order_polygons.append(polydata)
                            whole_line_length = whole_line_length + el[2]
                            begin = begin + el[2]
                        else:
                            pass
                # Assigning the whole line length to the list containing the polygon id (ordrange)
                # and assigning the quadrants line length to the variable holding the whole line (line_length)
                # also creating the begining for the next quadrant:
                line_length = line_length + whole_line_length
                whole_line_length_as_number = copy.deepcopy(whole_line_length)
                whole_line_length = [
                    *range(begin_for_whole_quadrant, (begin_for_whole_quadrant + whole_line_length + 1))]
                ordrange.append(whole_line_length)
                begin_for_whole_quadrant = begin_for_whole_quadrant + whole_line_length_as_number
                # Creating a list of the quadrant infromation list (ordrange)
                # and the polygon information list (final_order_polygons):
                final_list = [ordrange, final_order_polygons]
                # Assigning this final list to the final list of the quadrant line information:
                sample_order_list.append(final_list)
            # Calling the next function to sample the line created in here:
            self.getsample(sample_order_list, line_length, samp_numb, thisdict, layer, cuttedlayer, idfieldname, gridlayer,
                           oversample, is_from_strat, strat_name)


    # Function for get the element for samples sorting in the next section:
    def sampsort(self, sample):
        return sample[0]


    # Function for getting the sample:
    def getsample(self, sample_order_list, line_length, samp_numb, thisdict, layer, cuttedlayer, idfieldname, gridlayer,
                  oversample, is_from_strat, strat_name):
        # Getting the line points from a systematic sampling method by assigning a random point in the line and then
        # selecting points in specific intervals (we get the interval if we divide the whole line to segments as many as the sample number):
        recent_elem = randint(1, line_length)
        line_numbers = [recent_elem]
        line_step = line_length / int(samp_numb)
        for y in range(1, int(samp_numb)):
            line_numb = recent_elem + line_step
            if line_numb <= line_length:
                line_numb = math.trunc(line_numb)
                line_numbers.append(line_numb)
                recent_elem = line_numb
            else:
                line_numb = line_numb - line_length
                line_numb = math.trunc(line_numb)
                line_numbers.append(line_numb)
                recent_elem = line_numb
        # Getting the ids from the previously got line points (by the previously constructed lists):
        samples = []
        for elem in sample_order_list:
            for samp in line_numbers:
                if samp in elem[0][1]:
                    for el in elem[1]:
                        if samp in el[1]:
                            this_samp = [elem[0][0], el[0]]
                            samples.append(this_samp)
        # Creating a layer for the sample points (with 4 attributes, order_number, serial_number, x_coordinate, y_coordinate):
        origcrs = layer.crs().authid()
        if is_from_strat == 0:
            sample_point_layer = QgsVectorLayer("Point?crs={0}".format(origcrs), "sample_points", "memory")
        if is_from_strat == 1:
            namelay = "sample_points" + str(strat_name)
            sample_point_layer = QgsVectorLayer("Point?crs={0}".format(origcrs), namelay, "memory")
        pr = sample_point_layer.dataProvider()
        sample_point_layer.startEditing()
        if self.isgps == 2:
            pr.addAttributes([QgsField("Serial_number", QVariant.String), QgsField("X_Coordinate", QVariant.Double), QgsField("Y_Coordinate", QVariant.Double), QgsField("GPS_X_Coordinate", QVariant.Double), QgsField("GPS_Y_Coordinate", QVariant.Double)])
        else:
            pr.addAttributes([QgsField("Serial_number", QVariant.String), QgsField("X_Coordinate", QVariant.Double), QgsField("Y_Coordinate", QVariant.Double)])
        sample_point_layer.updateFields()
        QgsProject.instance().addMapLayer(sample_point_layer)
        # Creating a layer for the oversample points if oversampling is selected:
        if self.is_oversampling == 2:
            if is_from_strat == 0:
                oversample_point_layer = QgsVectorLayer("Point?crs={0}".format(origcrs), "oversample_points", "memory")
            if is_from_strat == 1:
                nameovlay = "oversample_points" + str(strat_name)
                oversample_point_layer = QgsVectorLayer("Point?crs={0}".format(origcrs), nameovlay, "memory")
            opr = oversample_point_layer.dataProvider()
            oversample_point_layer.startEditing()
            if self.isgps == 2:
                opr.addAttributes([QgsField("Serial_number", QVariant.String), QgsField("X_Coordinate", QVariant.Double), QgsField("Y_Coordinate", QVariant.Double), QgsField("GPS_X_Coordinate", QVariant.Double), QgsField("GPS_Y_Coordinate", QVariant.Double)])
            else:
                opr.addAttributes([QgsField("Serial_number", QVariant.String), QgsField("X_Coordinate", QVariant.Double), QgsField("Y_Coordinate", QVariant.Double)])
            oversample_point_layer.updateFields()
            QgsProject.instance().addMapLayer(oversample_point_layer)
        # Taking-apart the oversample component of the sample number from the real sample amount (if there is oversampling):
        if self.is_oversampling == 2:
            samp_numb = int(samp_numb) - int(oversample)
        # Generating random points in the selected polygon fragments,
        # first we order the grids by the ids and also the sample ids too:
        cutfeats = cuttedlayer.getFeatures()
        cutfeats = sorted(cutfeats, key=self.get_name)
        idindex = cuttedlayer.fields().indexFromName("id")
        if self.is_oversampling == 1:
            samples = sorted(samples, key=self.sampsort)
        serial = 1
        samp_counter = 0
        # Iterating through the cells, and checking if the id is in it, if it is, then we do the same with the polygon fragments in it,
        # after we found it, we put a random point in it:
        for cf in cutfeats:
            if samp_counter == samp_numb:
                break
            cfatts = cf.attributes()
            for sampl in samples:
                if cfatts[idindex] == sampl[0]:
                    samplay = thisdict.get("grid_" + str(cfatts[idindex]))
                    samppointlayfeats = samplay.getFeatures()
                    smplf_id_index = samplay.fields().indexFromName(idfieldname)
                    for smplf in samppointlayfeats:
                        if smplf[smplf_id_index] == sampl[1]:
                            fid = smplf.id()
                            laytorandpoint = samplay.materialize(QgsFeatureRequest().setFilterFid(fid))
                            parameters = {'INPUT': laytorandpoint, 'MIN_DISTANCE': 0, 'POINTS_NUMBER': 1,
                                          'OUTPUT': 'TEMPORARY_OUTPUT'}
                            rand = processing.run('qgis:randompointsinlayerbounds', parameters)
                            randlayer = rand['OUTPUT']
                            rand_features = randlayer.getFeatures()
                            for randfet in rand_features:
                                randgeom = randfet.geometry()
                            fet = QgsFeature()
                            fet.setGeometry(randgeom)
                            if self.isgps == 1:
                                fet.setAttributes([str(serial), str(randgeom.asPoint()[0]), str(randgeom.asPoint()[1])])
                            else:
                                # Calculating the random points GPS coordinates (WGS84 CRS):
                                x_orig = str(randgeom.asPoint()[0])
                                y_orig = str(randgeom.asPoint()[1])
                                gpscrs = QgsCoordinateReferenceSystem(4326)
                                tr = QgsCoordinateTransform(layer.crs(), gpscrs, QgsProject.instance())
                                randgeom.transform(tr)
                                x_gps = str(randgeom.asPoint()[0])
                                y_gps = str(randgeom.asPoint()[1])
                                fet.setAttributes([str(serial), x_orig, y_orig, x_gps, y_gps])
                            pr.addFeatures([fet])
                            serial = serial + 1
                            sampind = samples.index(sampl)
                            del samples[sampind]
                            samp_counter = samp_counter + 1
                            break
                        else:
                            pass
        # Additional sample creation if oversampling is enabled
        # after we done with the normal sample points, we does the same with the rest, but put them in the oversample_points layer:
        if self.is_oversampling == 2:
            oscutfeats = cuttedlayer.getFeatures()
            oscutfeats = sorted(cutfeats, key=self.get_name)
            serial = 1
            for oscf in oscutfeats:
                oscfatts = oscf.attributes()
                for samp in samples:
                    if oscfatts[idindex] == samp[0]:
                        samplayer = thisdict.get("grid_" + str(oscfatts[idindex]))
                        samppointlayerfeats = samplayer.getFeatures()
                        smplrf_id_index = samplayer.fields().indexFromName(idfieldname)
                        for smplrf in samppointlayerfeats:
                            if smplrf[smplrf_id_index] == samp[1]:
                                fd = smplrf.id()
                                layertorandpoint = samplayer.materialize(QgsFeatureRequest().setFilterFid(fd))
                                params = {'INPUT': layertorandpoint, 'MIN_DISTANCE': 0, 'POINTS_NUMBER': 1,
                                          'OUTPUT': 'TEMPORARY_OUTPUT'}
                                osrand = processing.run('qgis:randompointsinlayerbounds', params)
                                osrandlayer = osrand['OUTPUT']
                                osrand_features = osrandlayer.getFeatures()
                                for osrandfet in osrand_features:
                                    osrandgeom = osrandfet.geometry()
                                osfet = QgsFeature()
                                osfet.setGeometry(osrandgeom)
                                if self.isgps == 1:
                                    osfet.setAttributes([str(serial), str(osrandgeom.asPoint()[0]), str(osrandgeom.asPoint()[1])])
                                else:
                                    # Calculating the random points GPS coordinates (WGS84 CRS):
                                    os_x_orig = str(osrandgeom.asPoint()[0])
                                    os_y_orig = str(osrandgeom.asPoint()[1])
                                    gpscrs = QgsCoordinateReferenceSystem(4326)
                                    tr = QgsCoordinateTransform(layer.crs(), gpscrs, QgsProject.instance())
                                    osrandgeom.transform(tr)
                                    os_x_gps = str(osrandgeom.asPoint()[0])
                                    os_y_gps = str(osrandgeom.asPoint()[1])
                                    osfet.setAttributes([str(serial), os_x_orig, os_y_orig, os_x_gps, os_y_gps])
                                opr.addFeatures([osfet])
                                serial = serial + 1
                                sampind = samples.index(samp)
                                del samples[sampind]
                                break
                            else:
                                pass
        ind = layer.fields().indexFromName(str(idfieldname))
        dellist = [ind]
        prov = layer.dataProvider()
        prov.deleteAttributes(dellist)
        layer.updateFields()





    # --------------------------- DELETING FIELD STORING THE STRATUM NAMES --------------------------------------------------------------






    # Function for the window asking if deleting or keeping the field is preferred:
    def do_del_field(self, layer, stratname):
        windw = QWidget()
        windw.setWindowTitle("Stratum name field deletion")
        label = QLabel("Do you want to keep the field storing tha stratum name for each feature?")
        yes_button = QPushButton("YES")
        yes_button.clicked.connect(lambda: windw.close())
        no_button = QPushButton("NO")
        no_button.clicked.connect(lambda: self.delfield(layer, stratname, windw))
        hbox = QHBoxLayout()
        hbox.addWidget(yes_button)
        hbox.addWidget(no_button)
        vbox = QVBoxLayout()
        vbox.addWidget(label)
        vbox.addLayout(hbox)
        windw.setLayout(vbox)
        windw.show()


    # Function for deleting the field if no is selected:
    def delfield(self, layer, stratname, windw):
        windw.close()
        ind = layer.fields().indexFromName(str(stratname))
        dellist = [ind]
        prov = layer.dataProvider()
        prov.deleteAttributes(dellist)
        layer.updateFields()





    # --------------------------- CREATING THE DIALOG WINDOW --------------------------------------------------------------





    def run(self):
        """Run method that performs all the real work"""
        # Create the dialog with elements (after translation) and keep reference
        # Only create GUI ONCE in callback, so that it will only load when the plugin is started
        if self.first_start == True:
            self.first_start = False
            self.dlg = VetSampDialog()
        # Setting to default the value for checking if GPS coordinates are needed:
        self.isgps = 1
        # Get the loaded layers in the combobox:
        self.dlg.cbox.clear()
        loaded_layers = self.iface.mapCanvas().layers()
        vector_layers = []
        for layer in loaded_layers:
            if layer.type() == QgsMapLayer.VectorLayer:
                vector_layers.append(layer)
        loaded_layers = vector_layers
        loaded_layers_name = []
        for layer in loaded_layers:
            name = layer.name()
            loaded_layers_name.append(name)
        self.dlg.cbox.addItems(loaded_layers_name)
        # Set the radiobuttons and linedit for the possible selection of custom quadrant number:
        self.quadrchecker = 1
        self.dlg.custom_quadr.setEnabled(False)
        self.dlg.label_cust_1.setEnabled(False)
        self.dlg.label_cust_2.setEnabled(False)
        grid_group = QButtonGroup(self.dlg)
        self.dlg.button_def.setChecked(True)
        self.dlg.button_def.toggled.connect(lambda: self.deftoggled(self.dlg.custom_quadr))
        self.dlg.button_custom.toggled.connect(lambda: self.customtoggled(self.dlg.custom_quadr))
        grid_group.addButton(self.dlg.button_def)
        grid_group.addButton(self.dlg.button_custom)
        # Set the radiobuttons and lineedit for the possible selection of oversampling:
        self.is_oversampling = 1
        self.dlg.oversample.setEnabled(False)
        self.dlg.label_ov_1.setEnabled(False)
        self.dlg.label_ov_2.setEnabled(False)
        oversampling_group = QButtonGroup(self.dlg)
        self.dlg.button_not_oversampling.setChecked(True)
        self.dlg.button_not_oversampling.toggled.connect(lambda: self.notoversample(self.dlg.oversample))
        self.dlg.button_oversampling.toggled.connect(lambda: self.oversample(self.dlg.oversample))
        oversampling_group.addButton(self.dlg.button_not_oversampling)
        oversampling_group.addButton(self.dlg.button_oversampling)
        # Setting the button for opening stratified sampling:
        self.dlg.strat_button.clicked.connect(lambda: self.is_strat_sel())
        # Setting the name of the dialog:
        self.dlg.setWindowTitle("VetSamp sampling settings")
        # show the dialog
        self.dlg.show()
        # Run the dialog event loop
        result = self.dlg.exec_()
        # See if OK was pressed
        if result:
            self.iscorrect(self.dlg.cbox.currentText(), self.dlg.numb.text(), self.dlg.custom_quadr.text(),
                           self.quadrchecker, self.dlg.oversample.text(), self.is_oversampling)

