'''
/***************************************************************************
Name		: blender_worker.py
Description : BASIICS Blending for Fews_Tools Plugin
This code adapted from: https://snorfalorpagus.net/blog/2013/12/07/
                             multithreading-in-qgis-python-plugins/
copyright   : (C) 2020-2023 by FEWS
email       : minxuansun@contractor.usgs.gov
Created     : 01/23/2020
Modified    : 02/08/2020 cholen - Moved some functions to basiics_batch_utils
              06/15/2020 cholen - Handle missing data in
                                    __calc_intrpltd_rato_and_anom_vals__
              06/18/2020 cholen - Replaced sample_dates with a bat_dic element
              06/23/2020 cholen - Get cumulative lists outside of loop
              07/13/2020 cholen - Add cell size adj to raster outputs
              07/14/2020 cholen - Adjust error
              08/29/2020 cholen - Only display the last 3 periods, add ratio
                                  outputs, apply anom addition correction
                                  to data file
              10/08/2020 cholen - Remove outputs when not flagged, fixes ratio
                                  array and anom array nodata, fixed fuzz dist
              10/23/2020 cholen - Remove masking and process ds extents
              12/03/2020 cholen - Handle OSError
              09/29/2021 cholen - Update to use delimiter in stats headings
              01/18/2022 cholen - New gdal utils, refactor.
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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 os

from PyQt5 import QtCore
from qgis.core import QgsMessageLog, Qgis

from fews_tools.utilities import basiics_batch_utilities as b_util
from fews_tools.utilities import geoclim_gdal_utilities as g_util
from fews_tools.utilities import geoclim_utilities as util
from fews_tools.utilities import geoclim_qgs_utilities as qgs_util


class BlenderWorker(QtCore.QObject):
    '''
    Class for BASIICS blending
    '''
    def __init__(self, batch_dic, wrksp_setup):
        '''
        init
        params(dic) - batch_dic
        params(dic) - wrksp_setup
        '''
        QtCore.QObject.__init__(self)
        self.bat_dic = batch_dic
        self.wrksp_setup = wrksp_setup
        self.stn_dic = None
        self.killed = False
        self.stn_2_stn_list = []
        self.stn_2_pixel_list = []
        self.region_stns_filename = ''
        self.region_data_filename = ''
        self.split_csv_file_list = None
        self.ds_params = None
        self.step = 5.0
        self.curr_progress = 0.0

    def __calc_intrpltd_rato_and_anom_vals__(self):
        '''
        Step 7-12 Calculate the ratio * grid intrpltd and xval
        values and anomaly values, add into station dic
        '''
        for key in self.stn_dic:
            if self.stn_dic[key]['Intrpltd_ratio_val'] ==\
                    self.bat_dic['stn_missing_val'] or\
                    self.stn_dic[key]['Xvalidated_ratio_val'] ==\
                    self.bat_dic['stn_missing_val']:
                interp_val = self.bat_dic['stn_missing_val']
                intrp_one_rem_val =\
                    self.bat_dic['stn_missing_val']
                anom_val = b_util.ANOMMISSINGVAL
            else:
                interp_val = (
                    self.stn_dic[key]['Intrpltd_ratio_val'] *
                    self.stn_dic[key]['Grid_val'])
                intrp_one_rem_val = (
                    self.stn_dic[key]['Xvalidated_ratio_val'] *
                    self.stn_dic[key]['Grid_val'])
                anom_val = (
                    self.stn_dic[key]['Intrpltd_stn_val'] -
                    interp_val)

            self.stn_dic[key]['Int_ratio_x_grid_val'] =\
                interp_val
            self.stn_dic[key]['Xval_ratio_x_grid_val'] =\
                intrp_one_rem_val
            self.stn_dic[key]['Anomaly_val'] = anom_val

    def __calc_ratio_plus_anom__(self):
        '''
        Step 7-14 Calculate the ratio + anom values, add into station dic
        '''
        # this is always true at this point
        if self.bat_dic['co_interp_type'] == 'ratio_and_anom':
            for key in self.stn_dic:
                if self.stn_dic[key]['Intrpltd_stn_val'] !=\
                        self.bat_dic['ds_dic']['DATAMISSINGVALUE']:
                    intrpltd_ratio_plus_anom_val =\
                        (self.stn_dic[key]['Int_ratio_x_grid_val'] +
                         self.stn_dic[key]['Intrpltd_anomaly'])
                    x_validated_ratio_plus_anom_val =\
                        (self.stn_dic[key]['Xval_ratio_x_grid_val'] +
                         self.stn_dic[key]['Xvalidated_anomaly'])

                    if intrpltd_ratio_plus_anom_val < 0:
                        intrpltd_ratio_plus_anom_val = 0.0
                    if x_validated_ratio_plus_anom_val < 0:
                        x_validated_ratio_plus_anom_val = 0.0
                    self.stn_dic[key]['Intrpltd_ratio_plus_anom'] =\
                        intrpltd_ratio_plus_anom_val
                    self.stn_dic[key]['Xvalidated_ratio_plus_anom_val'] =\
                        x_validated_ratio_plus_anom_val
                else:
                    self.stn_dic[key]['Intrpltd_ratio_plus_anom'] =\
                        self.bat_dic['ds_dic']['DATAMISSINGVALUE']
                    self.stn_dic[key]['Xvalidated_ratio_plus_anom_val'] =\
                        self.bat_dic['ds_dic']['DATAMISSINGVALUE']

    def __calc_stn_minus_grid__(self):
        '''
        Step 7-22 Calculate intpld stn - intrpld grid - write to station dic
        '''
        for key in self.stn_dic:
            if self.stn_dic[key]['Intrpltd_stn_val'] ==\
                    self.bat_dic['stn_missing_val'] or\
                    self.stn_dic[key]['Interp_array_val'] ==\
                    self.bat_dic['ds_dic']['DATAMISSINGVALUE']:
                self.stn_dic[key]['Intrpltd_stn_minus_intrpltd_grid'] =\
                    b_util.ANOMMISSINGVAL
            else:
                self.stn_dic[key]['Intrpltd_stn_minus_intrpltd_grid'] = (
                    self.stn_dic[key]['Intrpltd_stn_val'] -
                    self.stn_dic[key]['Interp_array_val'])

    def run(self):
        '''
        run method
        '''
        ret_tuple = None

        try:
            QgsMessageLog.logMessage('Beginning blend of stations and rasters',
                                     level=Qgis.Info)
            # figure progress step for bar
            self.step = 65.0 / len(self.bat_dic['good_files_list'])
            self.curr_progress = 0

            # only one cointertype available
            if self.bat_dic['co_interp_type'] != 'ratio_and_anom':
                self.bat_dic['co_interp_type'] = 'ratio_and_anom'

            self.ds_params = qgs_util.extract_raster_file_params(
                self.bat_dic['good_files_list'][0])

            # Step 1 - extract regions and data.
            self.region_stns_filename, self.region_data_filename, err =\
                b_util.get_stations(self.bat_dic, self.ds_params)
            if err is True:
                raise RuntimeError
            if self.killed is True:
                raise KeyboardInterrupt
            self.update_progress()

            # Step 2 - find closest stations to each station for lookup
            # uses the stations file created in Step 1
            self.stn_2_stn_list, err =\
                b_util.get_station_2_station_list(
                    self.bat_dic, self.ds_params,
                    self.region_stns_filename)
            if err is True:
                raise RuntimeError
            if self.killed is True:
                raise KeyboardInterrupt
            self.update_progress()

            # Step 3 - find the closest stations to pixels for look up.
            # uses the region file created in Step 1
            self.stn_2_pixel_list, err = b_util.get_station_2_pixel_list(
                self.bat_dic, self.ds_params, self.region_stns_filename)
            if err is True:
                raise RuntimeError
            if self.killed is True:
                raise KeyboardInterrupt
            self.update_progress()

            # Step 4 - split up the region data file into one for each date
            self.split_csv_file_list, err =\
                b_util.split_station_file(
                    self.bat_dic['curr_output_path'],
                    self.bat_dic,
                    self.region_data_filename)
            if err is True:
                raise RuntimeError
            if self.killed is True:
                raise KeyboardInterrupt
            self.update_progress()

            # Step 5 get fuzzy distance
            b_util.get_fuzzy_dist(self.bat_dic, self.ds_params)

            if self.bat_dic['output_stats_flag']:
                # setup cum stats file
                # open with 'w' so any pre-existing file gets overwritten
                with open(self.bat_dic['stats_out_file'], 'w') as stats:
                    stats.write("Name" + self.bat_dic["delimiter"] +
                                "FileName" + self.bat_dic["delimiter"] +
                                "Long" + self.bat_dic["delimiter"] +
                                "Lat" + self.bat_dic["delimiter"] +
                                "StnVal" + self.bat_dic["delimiter"] +
                                "GridVal" + self.bat_dic["delimiter"] +
                                "BASIICSval" + self.bat_dic["delimiter"] +
                                "XValidatedBASIICS" + self.bat_dic["delimiter"] +
                                "XValidatedNoGrid\n")
            orig_lr_val = self.bat_dic['lr_value']
            anom_lr_val = 0
            orig_stn_missing_val = self.bat_dic['stn_missing_val']
            reg_exp = b_util.get_datestring_reg_expression(self.bat_dic)
            # Step 7 start sample date loop
            for sample in self.bat_dic['dates_list']:
                # Step 7-1
                name_base = self.bat_dic['output_prefix'] + sample
                # Step 7-2 verify that sample date csv file exists
                sample_date_csv_filename, err = b_util.get_csv(
                    self.split_csv_file_list, sample)
                if err is True:
                    raise RuntimeError

                # Step 7-3 verify that grid file exists
                rain_grid_filename, err = b_util.get_grid(
                    self.bat_dic, sample, reg_exp)
                if err is True:
                    raise RuntimeError
                # Step 7-4 Start filling in station dictionary, this
                # fills in lat, long, orig value and grid value
                self.stn_dic = b_util.build_station_dic(
                    self.bat_dic,
                    sample_date_csv_filename,
                    rain_grid_filename)
                # handle cases where no valid data exists for date
                if not self.stn_dic:
                    continue

                # per VB GeoCLIM comment - for the no-background
                # interpolation and cross validation, use the idw_ordinary
                # as standard, regardless of whether the user has selected
                # idw_s or idw_o for the cointerpolation type in the GUI

                # Step 7-5 Interpolate station values using 'ordinary' type
                b_util.cointerpolate_stations_idw(
                    self.bat_dic, self.stn_dic,
                    'Ordinary', self.stn_2_stn_list,
                    'Stn_val', 'Intrpltd_stn_val', 'Xvalidated_stn_val')
                QgsMessageLog.logMessage('Interpolated station values',
                                         level=Qgis.Info)

                # Step 7-7 remove any of the stations that show missing val
                bad_vals = [b_util.MINSHORT,
                            self.bat_dic['ds_dic']['DATAMISSINGVALUE']]
                b_util.remove_nodata_keys(
                    self.stn_dic, 'Stn_val', bad_vals)
                dst_shp_filename = None
                if self.bat_dic['output_stats_flag']:
                    # Step 7-8 Create station shapefile for date
                    dst_shp_filename =\
                        os.path.join(self.bat_dic['curr_output_path'],
                                     name_base + '_stn.shp')
                    qgs_util.create_station_shapefile(
                        self.stn_dic, dst_shp_filename)
                    QgsMessageLog.logMessage(
                        dst_shp_filename + ' Shapefile complete',
                        level=Qgis.Info)

                    # Step 7-9 Output initial stats if flagged
                    dst_stats1_file =\
                        os.path.join(self.bat_dic['curr_output_path'],
                                     name_base + '.stat.txt')

                    try:
                        if os.path.exists(dst_stats1_file):
                            os.remove(dst_stats1_file)
                    except OSError:  # message use and re-raise the error
                        QgsMessageLog.logMessage(
                            ('File open in another process:  ' +
                             dst_stats1_file),
                            level=Qgis.Critical)
                        raise OSError

                    stats_dic =\
                        util.weighted_least_squares_simple_linear_dic(
                            self.stn_dic, 'Stn_val', 'Grid_val', None,
                            int(self.bat_dic['stn_missing_val']))

                    title_l = ('Statistical analysis comparing '
                               'station values (X) with background grid '
                               'values (Y) for ' + dst_stats1_file)
                    b_util.print_least_squares_stats(
                        stats_dic, dst_stats1_file, title_l, False)

                    b_util.print_station_info(
                        self.bat_dic, self.stn_dic, dst_stats1_file)

                # Step 7-10 Get station ratios, add to dic
                b_util.calc_station_ratios(self.bat_dic, self.stn_dic)
                # Step 7-11 Interpolate the station ratios
                b_util.cointerpolate_stations_idw(
                    self.bat_dic, self.stn_dic,
                    self.bat_dic['interp_type'],
                    self.stn_2_stn_list,
                    'Ratio', 'Intrpltd_ratio_val', 'Xvalidated_ratio_val')

                # Step 7-12 Calculate the ratio * grid intrpltd and xval
                # values and anomaly values, add into station dic
                self.__calc_intrpltd_rato_and_anom_vals__()

                #  per VB GeoCLIM comment - interpolate anomalies with
                #  simple idw, relaxing the anomalies to 0(lrVal) as you
                #  move away from known station values
                self.bat_dic['lr_value'] = 0

                # Step 7-13 Interpolate anomaly vals
                b_util.cointerpolate_stations_idw(
                    self.bat_dic, self.stn_dic,
                    'Simple', self.stn_2_stn_list, 'Anomaly_val',
                    'Intrpltd_anomaly', 'Xvalidated_anomaly')
                # reset lr_val to orig value
                self.bat_dic['lr_value'] = orig_lr_val

                # Step 7-14 Calculate ratio + anom
                self.__calc_ratio_plus_anom__()

                # Step 7-15 - interpolate the station ratios to pixel array
                # pixels outside area will be self.bat_dic['lr_value']
                intrpltd_ratios_array =\
                    b_util.interpolate_array_idw(
                        self.bat_dic,
                        self.ds_params,
                        self.stn_dic, self.bat_dic['interp_type'],
                        self.stn_2_pixel_list, 'Ratio',
                        self.bat_dic['lr_value'])
                if self.bat_dic['output_stats_flag']:
                    ratio_base_filename =\
                        (name_base + '_ratio' +
                         self.bat_dic['ds_dic']['DATASUFFIX'])

                    dst_ratio_filename =\
                        os.path.join(self.bat_dic['curr_output_path'],
                                     ratio_base_filename)
                    err = g_util.write_file(dst_ratio_filename,
                                            intrpltd_ratios_array,
                                            self.ds_params[1],
                                            self.ds_params[2],
                                            self.bat_dic['geotransform'],
                                            g_util.TYPE_DIC["Float32"]["GDAL"])
                    if err is True:
                        raise RuntimeError
                _, _, _, _, data_type = g_util.get_geotiff_info(rain_grid_filename)
                np_data_type = g_util.TYPE_DIC[data_type]["NP"]
                grid_array = g_util.extract_raster_array(
                    rain_grid_filename, np_data_type)
                # Step 7-16 - Create the intper ratios array * grid array
                intrpltd_ratios_x_grid_array =\
                    (intrpltd_ratios_array * grid_array)  # no masking
                intrpltd_ratios_array = None
                del intrpltd_ratios_array

                # Step 7-17 extract the interpolated array val at stn pts
                b_util.extract_point_stats(
                    self.bat_dic, self.ds_params,
                    self.stn_dic, 'Interp_array_val',
                    intrpltd_ratios_x_grid_array)

                # Step 7-18 Calculate the anomalies
                # intpld stn - intrpld grid
                self.__calc_stn_minus_grid__()
                b_util.remove_nodata_keys(
                    self.stn_dic, 'Intrpltd_stn_val',
                    [self.bat_dic['stn_missing_val']])
                b_util.remove_nodata_keys(
                    self.stn_dic, 'Interp_array_val',
                    [self.bat_dic['ds_dic']['DATAMISSINGVALUE']])

                # Step 7-19 Interpolate the
                # anomalies intpld stn - intrpld grid
                # interpolate the anomalies with a simple idw,
                # relaxing the anomalies to 0 as you move away from known
                # stn values don't use missing_value since the anomalies
                # could be within this range. Instead use very low missing
                # values such as -29999
                self.bat_dic['lr_value'] = anom_lr_val
                self.bat_dic['stn_missing_val'] = b_util.ANOMMISSINGVAL
                anomaly_array = b_util.interpolate_array_idw(
                    self.bat_dic,
                    self.ds_params,
                    self.stn_dic, 'Simple',
                    self.stn_2_pixel_list,
                    'Intrpltd_stn_minus_intrpltd_grid',
                    b_util.ANOM_ARRAY_FILL_VAL)

                # apply anomaly additive correction to dst data array
                final_blend_array =\
                    (intrpltd_ratios_x_grid_array + anomaly_array)
                # set any negative values to 0 if not 'missing'
                for row in range(self.ds_params[2]):
                    for col in range(self.ds_params[1]):
                        if (grid_array[row, col] ==
                             self.bat_dic['ds_dic']['DATAMISSINGVALUE']):
                            final_blend_array[row, col] =\
                                 self.bat_dic['ds_dic']['DATAMISSINGVALUE']
                        elif ((final_blend_array[row, col] !=
                             self.bat_dic['ds_dic']['DATAMISSINGVALUE'])
                                and (final_blend_array[row, col] < 0)):
                            final_blend_array[row, col] = 0

                grid_array = None
                del grid_array

                # reset the changed parameters
                self.bat_dic['lr_value'] = orig_lr_val
                self.bat_dic['stn_missing_val'] = orig_stn_missing_val

                # Step 7-20 Create the output data raster
                temp_path = self.wrksp_setup.get_temp_data_path()
                b_util.create_output_raster_file(
                    self.bat_dic, self.ds_params, final_blend_array,
                    temp_path, name_base)

                if self.bat_dic['output_stats_flag']:
                    # Step 7-21 Create the output anomaly raster
                    b_util.create_output_raster_file(
                        self.bat_dic, self.ds_params, anomaly_array,
                        temp_path, name_base, True)

                # create all the stats outputs if flagged
                if self.bat_dic['output_stats_flag']:
                    # Step 7-22 extract the final_blend_array val at stn pts
                    b_util.extract_point_stats(
                        self.bat_dic, self.ds_params,
                        self.stn_dic, 'final_basiics_val',
                        final_blend_array)
                    # Step 7-23 Plot crossval graph
                    dst_jpg_file =\
                        os.path.join(self.bat_dic['curr_output_path'],
                                     name_base + '.crossval_graph.jpg')
                    title = ('Cross Validation:ppt' + sample)
                    b_util.plot_graph_dic(
                        self.stn_dic,
                        'final_basiics_val',
                        'Xvalidated_ratio_plus_anom_val',
                        'BASIICS Pixel Value',
                        'Cross-validated BASIICS Value',
                        title, dst_jpg_file)

                    # Step 7-24 Plot stngrid graph
                    dst_jpg_file =\
                        os.path.join(self.bat_dic['curr_output_path'],
                                     name_base + '.stngrid_graph.jpg')
                    title = ('Comparison between BASIICS Val at Station and Grid:ppt' +
                             sample)
                    b_util.plot_graph_dic(self.stn_dic,
                                          'final_basiics_val',
                                          'Grid_val',
                                          'BASIICS Pixel Value',
                                          'Original Grid Value',
                                          title, dst_jpg_file)

                    # Step 7-25 - output stats

                    stats_dic =\
                        util.weighted_least_squares_simple_linear_dic(
                            self.stn_dic, 'final_basiics_val',
                            'Xvalidated_ratio_plus_anom_val', None,
                            self.bat_dic['stn_missing_val'])

                    title_l = ('Cross-validated statistical analysis '
                               'comparing BASIICS Pixel Value '
                               'at the station location (X) and '
                               'Cross-validated BASIICS value (Y) for ' +
                               dst_stats1_file)
                    b_util.print_least_squares_stats(
                        stats_dic, dst_stats1_file, title_l, True)

                    b_util.print_cross_validation(
                        self.stn_dic, 'final_basiics_val',
                        'Xvalidated_ratio_plus_anom_val', dst_stats1_file)

                    # Step 7-26 - add to cumulative stats lists
                    b_util.print_station_info_cumulative(
                        self.bat_dic, self.stn_dic,
                        self.bat_dic['stats_out_file'],
                        sample)

                intrpltd_ratios_x_grid_array = None
                del intrpltd_ratios_x_grid_array
                final_blend_array = None
                del final_blend_array
                #  end loop cleanup
                QgsMessageLog.logMessage(sample + ' loop complete',
                                         level=Qgis.Info)
                if self.killed is True:
                    raise KeyboardInterrupt
                self.update_progress()
            self.progress.emit(90)
            # printout cumulative graphics and stats
            if self.bat_dic['output_stats_flag']:
                cum_basiics_vals = []
                cum_grid_vals = []
                cum_xval_vals = []
                cum_xval_vals_no_grid = []
                # read the stats csv file
                data_list = b_util.read_csv_file(
                    self.bat_dic['stats_out_file'],
                    self.bat_dic['delimiter'], 1)
                for entry in data_list:
                    cum_basiics_vals.append(float(entry[6]))
                    cum_grid_vals.append(float(entry[5]))
                    cum_xval_vals.append(float(entry[7]))
                    cum_xval_vals_no_grid.append(float(entry[8]))

                if not cum_grid_vals:
                    QgsMessageLog.logMessage('No valid stations found',
                                             level=Qgis.Info)
                    raise IOError
                # Step 8 - print out cumulative cross validation jpg
                dst_jpg_file =\
                    os.path.join(self.bat_dic['curr_output_path'],
                                 'stats.crossval_graph.jpg')
                b_util.plot_graph_list(cum_basiics_vals, cum_xval_vals,
                                       'BASIICS Pixel Value',
                                       'Cross-validated BASIICS Value',
                                       'Cross Validation:stats',
                                       dst_jpg_file)

                # Step 9 - print out cumulative stn_grid jpg
                dst_jpg_file =\
                    os.path.join(self.bat_dic['curr_output_path'],
                                 'stats.stngrid_graph.jpg')
                b_util.plot_graph_list(
                    cum_basiics_vals, cum_grid_vals,
                    'BASIICS Pixel Value', 'Original Grid Value',
                    'Comparison between Station and Grid:stats',
                    dst_jpg_file)

                # Step 10 - print final stats to cumulative stats file
                stats_dic =\
                    util.weighted_least_squares_simple_linear_list(
                        cum_basiics_vals, cum_grid_vals, None,
                        self.bat_dic['stn_missing_val'])

                title_l = (
                    'Comparison statistics for BASIICS Pixel Value ' +
                    '(X) against Original Grid Value (Y)')
                b_util.print_least_squares_stats(
                    stats_dic, self.bat_dic['stats_out_file'],
                    title_l, True)

                stats_dic =\
                    util.weighted_least_squares_simple_linear_list(
                        cum_basiics_vals, cum_xval_vals,
                        None, self.bat_dic['stn_missing_val'])

                title_l = ('Crossvalidated Comparison statistics for ' +
                           'BASIICS Pixel Value (X) against ' +
                           'Cross-Validated BASIICS Value(Y)')
                b_util.print_least_squares_stats(
                    stats_dic, self.bat_dic['stats_out_file'],
                    title_l, True)

                stats_dic =\
                    util.weighted_least_squares_simple_linear_list(
                        cum_basiics_vals, cum_xval_vals_no_grid,
                        None, self.bat_dic['stn_missing_val'])

                title_l = (
                    'Comparison statistics for BASIICS Pixel Value ' +
                    '(X) against [No Background] ' +
                    'Cross-Validated IDW-Interpolated Value(Y)')
                b_util.print_least_squares_stats(
                    stats_dic, self.bat_dic['stats_out_file'],
                    title_l, True)
                if self.killed is True:
                    raise KeyboardInterrupt
            # cleanup
            try:
                os.remove(self.region_stns_filename)
                os.remove(self.region_data_filename)
            except OSError:
                pass
            if self.killed is False:
                self.progress.emit(100)
                ret_tuple = (0, "Blending stations and rasters complete")
        # exit with appropriate message on killed (KeyboardInterrupt)
        except KeyboardInterrupt:
            self.progress.emit(0)
            ret_tuple = (0, u"Blending aborted by user")
        # forward any execeptions upstream
        except BaseException as exc:
            self.error.emit(exc, u"Unspecified error in Blending")
        self.finished.emit(ret_tuple)

    def kill(self):
        '''
        Kill method
        '''
        self.killed = True

    def update_progress(self):
        '''
        Helper for progress bar updates
        '''
        self.curr_progress += self.step
        if self.curr_progress > 90:
            self.curr_progress = 90
        self.progress.emit(int(self.curr_progress))

    finished = QtCore.pyqtSignal(object)

    error = QtCore.pyqtSignal(Exception, str)

    progress = QtCore.pyqtSignal(int)
