#!/usr/bin/env python
"""
This namespace contains rsgislib Python bindings 

Please be aware that the following variables have been 
defined to match enums within RSGISLib.

Data Types for images:

    * TYPE_UNDEFINED = 0
    * TYPE_8INT = 1
    * TYPE_16INT = 2
    * TYPE_32INT = 3
    * TYPE_64INT = 4
    * TYPE_8UINT = 5
    * TYPE_16UINT = 6
    * TYPE_32UINT = 7
    * TYPE_64UINT = 8
    * TYPE_32FLOAT = 9
    * TYPE_64FLOAT = 10

Methods for the Maximum Likelihood Classifier:

    * METHOD_SAMPLES = 0        # as calculated by ML
    * METHOD_AREA = 1           # priors set by the relative area
    * METHOD_EQUAL = 2          # priors all equal
    * METHOD_USERDEFINED = 3    # priors passed in to function
    * METHOD_WEIGHTED = 4       # priors by area but with a weight applied

Shape indexes used with RasterGIS:

    * SHAPE_SHAPENA = 0
    * SHAPE_SHAPEAREA = 1
    * SHAPE_ASYMMETRY = 2
    * SHAPE_BORDERINDEX = 3
    * SHAPE_BORDERLENGTH = 4
    * SHAPE_COMPACTNESS = 5
    * SHAPE_DENSITY = 6
    * SHAPE_ELLIPTICFIT = 7
    * SHAPE_LENGTH = 8
    * SHAPE_LENGTHWIDTH = 9
    * SHAPE_WIDTH = 10
    * SHAPE_MAINDIRECTION = 11
    * SHAPE_RADIUSLARGESTENCLOSEDELLIPSE = 12
    * SHAPE_RADIUSSMALLESTENCLOSEDELLIPSE = 13
    * SHAPE_RECTANGULARFIT = 14
    * SHAPE_ROUNDNESS = 15
    * SHAPE_SHAPEINDEX = 16

Methods of initialising KMEANS:

    * INITCLUSTER_RANDOM = 0
    * INITCLUSTER_DIAGONAL_FULL = 1
    * INITCLUSTER_DIAGONAL_STDDEV = 2
    * INITCLUSTER_DIAGONAL_FULL_ATTACH = 3
    * INITCLUSTER_DIAGONAL_STDDEV_ATTACH = 4
    * INITCLUSTER_KPP = 5


Methods of calculating distance:

    * DIST_UNDEFINED = 0
    * DIST_EUCLIDEAN = 1
    * DIST_MAHALANOBIS = 2
    * DIST_MANHATTEN = 3
    * DIST_MINKOWSKI = 4
    * DIST_CHEBYSHEV = 5
    * DIST_MUTUALINFO = 6

Methods of summarising data:

    * SUMTYPE_UNDEFINED = 0
    * SUMTYPE_MODE = 1
    * SUMTYPE_MEAN = 2
    * SUMTYPE_MEDIAN = 3
    * SUMTYPE_MIN = 4
    * SUMTYPE_MAX = 5
    * SUMTYPE_STDDEV = 6
    * SUMTYPE_COUNT = 7
    * SUMTYPE_RANGE = 8
    * SUMTYPE_SUM = 9

Constants specifying how bands should be treated when sharpening
(see rsgislib.imageutils)

    * SHARP_RES_IGNORE = 0
    * SHARP_RES_LOW = 1
    * SHARP_RES_HIGH = 2

Options for interpolating raster data:

    * INTERP_NEAREST_NEIGHBOUR = 0
    * INTERP_BILINEAR = 1
    * INTERP_CUBIC = 2
    * INTERP_CUBICSPLINE = 3
    * INTERP_LANCZOS = 4
    * INTERP_AVERAGE = 5
    * INTERP_MODE = 6
    * INTERP_MIN = 7
    * INTERP_MAX = 8
    * INTERP_MEDIAN = 9
    * INTERP_Q1 = 10
    * INTERP_Q3 = 11
    * INTERP_SUM = 12

Options for image stretching / normalisation:

    * IMG_STRETCH_NONE = 0
    * IMG_STRETCH_USER = 1
    * IMG_STRETCH_LINEAR = 2
    * IMG_STRETCH_STDEV = 3
    * IMG_STRETCH_CUMULATIVE = 4

Options for vector geometry types:

    * GEOM_PT = 1
    * GEOM_LINE = 2
    * GEOM_POLY = 3
    * GEOM_MPT = 4
    * GEOM_MLINE = 5
    * GEOM_MPOLY = 6

Available thresholding methods:

    * THRES_METH_NONE = 0
    * THRES_METH_OTSU = 1
    * THRES_METH_YEN = 2
    * THRES_METH_ISODATA = 3
    * THRES_METH_CROSS_ENT = 4
    * THRES_METH_LI = 5
    * THRES_METH_KURT_SKEW = 6


Options for classification datasets:

    * CLS_ALL_DATA = 0
    * CLS_TRAIN_DATA = 1
    * CLS_VALID_DATA = 2
    * CLS_TEST_DATA = 3

Options for classification hyperparameter optimisation:
    * OPT_MTHD_UNDEFINED = 0
    * OPT_MTHD_BAYESOPT = 1
    * OPT_MTHD_OPTUNA = 2
    * OPT_MTHD_SKOPT = 3
    * OPT_MTHD_RANDOM = 4
    * OPT_MTHD_GRID = 5

Options for calculating correlation:
    * STATS_CORR_UNDEFINED = 0
    * STATS_CORR_PEARSONS = 1
    * STATS_CORR_SPEARMAN = 2
    * STATS_CORR_KENDALL_TAU = 3
    * STATS_CORR_POINT_BISERIAL = 4


Options for logical combination of data:
    * LOGIC_AND = 1
    * LOGIC_OR = 2

Options for variable type
    * VAR_TYPE_UNDEFINED = 0
    * VAR_TYPE_CONTINUOUS = 1
    * VAR_TYPE_CATEGORICAL = 2

"""
from __future__ import print_function

import datetime
import pathlib
import sys
import time

from osgeo import gdal, gdalconst

gdal.UseExceptions()

TYPE_UNDEFINED = 0
TYPE_8INT = 1
TYPE_16INT = 2
TYPE_32INT = 3
TYPE_64INT = 4
TYPE_8UINT = 5
TYPE_16UINT = 6
TYPE_32UINT = 7
TYPE_64UINT = 8
TYPE_32FLOAT = 9
TYPE_64FLOAT = 10

DIST_UNDEFINED = 0
DIST_EUCLIDEAN = 1
DIST_MAHALANOBIS = 2
DIST_MANHATTEN = 3
DIST_MINKOWSKI = 4
DIST_CHEBYSHEV = 5
DIST_MUTUALINFO = 6

SUMTYPE_UNDEFINED = 0
SUMTYPE_MODE = 1
SUMTYPE_MEAN = 2
SUMTYPE_MEDIAN = 3
SUMTYPE_MIN = 4
SUMTYPE_MAX = 5
SUMTYPE_STDDEV = 6
SUMTYPE_COUNT = 7
SUMTYPE_RANGE = 8
SUMTYPE_SUM = 9

METHOD_SAMPLES = 0  # as calculated by ML
METHOD_AREA = 1  # priors set by the relative area
METHOD_EQUAL = 2  # priors all equal
METHOD_USERDEFINED = 3  # priors passed in to function
METHOD_WEIGHTED = 4  # priors by area but with a weight applied

SHAPE_SHAPENA = 0
SHAPE_SHAPEAREA = 1
SHAPE_ASYMMETRY = 2
SHAPE_BORDERINDEX = 3
SHAPE_BORDERLENGTH = 4
SHAPE_COMPACTNESS = 5
SHAPE_DENSITY = 6
SHAPE_ELLIPTICFIT = 7
SHAPE_LENGTH = 8
SHAPE_LENGTHWIDTH = 9
SHAPE_WIDTH = 10
SHAPE_MAINDIRECTION = 11
SHAPE_RADIUSLARGESTENCLOSEDELLIPSE = 12
SHAPE_RADIUSSMALLESTENCLOSEDELLIPSE = 13
SHAPE_RECTANGULARFIT = 14
SHAPE_ROUNDNESS = 15
SHAPE_SHAPEINDEX = 16

INITCLUSTER_RANDOM = 0
INITCLUSTER_DIAGONAL_FULL = 1
INITCLUSTER_DIAGONAL_STDDEV = 2
INITCLUSTER_DIAGONAL_FULL_ATTACH = 3
INITCLUSTER_DIAGONAL_STDDEV_ATTACH = 4
INITCLUSTER_KPP = 5

SHARP_RES_IGNORE = 0
SHARP_RES_LOW = 1
SHARP_RES_HIGH = 2

INTERP_NEAREST_NEIGHBOUR = 0
INTERP_BILINEAR = 1
INTERP_CUBIC = 2
INTERP_CUBICSPLINE = 3
INTERP_LANCZOS = 4
INTERP_AVERAGE = 5
INTERP_MODE = 6
INTERP_MIN = 7
INTERP_MAX = 8
INTERP_MEDIAN = 9
INTERP_Q1 = 10
INTERP_Q3 = 11
INTERP_SUM = 12

GEOM_PT = 1
GEOM_LINE = 2
GEOM_POLY = 3
GEOM_MPT = 4
GEOM_MLINE = 5
GEOM_MPOLY = 6

IMG_STRETCH_NONE = 0
IMG_STRETCH_USER = 1
IMG_STRETCH_LINEAR = 2
IMG_STRETCH_STDEV = 3
IMG_STRETCH_CUMULATIVE = 4

THRES_METH_NONE = 0
THRES_METH_OTSU = 1
THRES_METH_YEN = 2
THRES_METH_ISODATA = 3
THRES_METH_CROSS_ENT = 4
THRES_METH_LI = 5
THRES_METH_KURT_SKEW = 6

CLS_ALL_DATA = 0
CLS_TRAIN_DATA = 1
CLS_VALID_DATA = 2
CLS_TEST_DATA = 3

OPT_MTHD_UNDEFINED = 0
OPT_MTHD_BAYESOPT = 1
OPT_MTHD_OPTUNA = 2
OPT_MTHD_SKOPT = 3
OPT_MTHD_RANDOM = 4
OPT_MTHD_GRID = 5

STATS_CORR_UNDEFINED = 0
STATS_CORR_PEARSONS = 1  # Pearson
STATS_CORR_SPEARMAN = 2  # Spearman
STATS_CORR_KENDALL_TAU = 3  # Kendall's tau
STATS_CORR_POINT_BISERIAL = 4  # pointbiserialr

LOGIC_AND = 1
LOGIC_OR = 2

VAR_TYPE_UNDEFINED = 0
VAR_TYPE_CONTINUOUS = 1
VAR_TYPE_CATEGORICAL = 2


def get_install_base_path() -> pathlib.PurePath:
    """
    A function which returns the base path for the RSGISLib installation.
    This can be used to find files stored in share etc. installed alongside
    the RSGISLib source code.

    :return: a pathlib.PurePath object with the base path of the RSGISLib
             installation.

    """
    import platform
    import site

    site_path_str = site.getsitepackages()[0]
    site_path = pathlib.PurePath(site_path_str)

    base_path_comps = []
    for path_part in site_path.parts:
        if path_part != "lib":
            base_path_comps.append(path_part)
        else:
            break

    if platform.system() == "Windows":
        base_path = pathlib.PureWindowsPath(*base_path_comps)
    else:
        base_path = pathlib.PurePosixPath(*base_path_comps)

    return base_path


RSGISLIB_INSTALL_PREFIX = str(get_install_base_path())


def get_rsgislib_version() -> str:
    """
    Calls rsgis-config to get the version number.

    :return: string with the rsgislib version number

    """

    # Try calling rsgis-config to get minor version number
    try:
        import shutil

        if shutil.which("rsgis-config") is not None:
            import subprocess

            out = subprocess.run(
                ["rsgis-config", "--version"],
                capture_output=True,
                text=True,
                check=True,
            )
            version_str = out.stdout
            version_str = version_str.split("\n")[0]
        else:
            version_str = "NA.NA"
    except Exception:
        version_str = "NA.NA"
    return version_str


__version__ = get_rsgislib_version()

py_sys_version_str = "{}.{}".format(sys.version_info.major, sys.version_info.minor)
py_sys_version_flt = float(py_sys_version_str)


class RSGISPyException(Exception):
    """
    A class representing the RSGISLib exception.
    """

    def __init__(self, value: str):
        """
        Init for the RSGISPyException class
        """
        self.value = value

    def __str__(self):
        """
        Return a string representation of the exception
        """
        return repr(self.value)


class RSGISGDALErrorHandler:
    """
    A class representing a generic GDAL Error Handler which
    can be used to pick up GDAL warnings rather than just
    failure errors.
    """

    def __init__(self):
        """
        Init for RSGISGDALErrorHandler. Class attributes are
        err_level, err_no and err_msg

        """
        self.err_level = gdal.CE_None
        self.err_no = 0
        self.err_msg = ""

    def handler(self, err_level, err_no, err_msg):
        """
        The handler function which is called with the error information.

        :param err_level: The level of the error
        :param err_no: The error number
        :param err_msg: The message (string) associated with the error.

        """
        self.err_level = err_level
        self.err_no = err_no
        self.err_msg = err_msg


def get_rsgislib_datatype(gdal_type: str) -> int:
    """
    Convert from GDAL data type string to RSGISLib data type int.

    :return: int

    """
    gdal_type = gdal_type.lower()
    if gdal_type == "int8":
        out_dt = TYPE_8INT
    elif gdal_type == "int16":
        out_dt = TYPE_16INT
    elif gdal_type == "int32":
        out_dt = TYPE_32INT
    elif gdal_type == "int64":
        out_dt = TYPE_64INT
    elif gdal_type == "byte" or gdal_type == "uint8":
        out_dt = TYPE_8UINT
    elif gdal_type == "uint16":
        out_dt = TYPE_16UINT
    elif gdal_type == "uint32":
        out_dt = TYPE_32UINT
    elif gdal_type == "uint64":
        out_dt = TYPE_64UINT
    elif gdal_type == "float32":
        out_dt = TYPE_32FLOAT
    elif gdal_type == "float64":
        out_dt = TYPE_64FLOAT
    else:
        raise RSGISPyException(
            f"The data type '{gdal_type}' is unknown / not supported."
        )
    return out_dt


def get_gdal_datatype(rsgislib_datatype: int) -> int:
    """
    Convert from RSGIS data type to GDAL data type int.

    :return: int

    """
    if rsgislib_datatype == TYPE_16INT:
        out_dt = gdal.GDT_Int16
    elif rsgislib_datatype == TYPE_32INT:
        out_dt = gdal.GDT_Int32
    elif rsgislib_datatype == TYPE_8UINT:
        out_dt = gdal.GDT_Byte
    elif rsgislib_datatype == TYPE_16UINT:
        out_dt = gdal.GDT_UInt16
    elif rsgislib_datatype == TYPE_32UINT:
        out_dt = gdal.GDT_UInt32
    elif rsgislib_datatype == TYPE_32FLOAT:
        out_dt = gdal.GDT_Float32
    elif rsgislib_datatype == TYPE_64FLOAT:
        out_dt = gdal.GDT_Float64
    elif rsgislib_datatype == TYPE_64INT:
        if hasattr(gdalconst, "GDT_Int64"):
            out_dt = gdal.GDT_Int64
        else:
            raise RSGISPyException("GDT_Int64 not available in your GDAL installation.")
    elif rsgislib_datatype == TYPE_64UINT:
        if hasattr(gdalconst, "GDT_UInt64"):
            out_dt = gdal.GDT_UInt64
        else:
            raise RSGISPyException(
                "GDT_UInt64 not available in your GDAL installation."
            )
    elif rsgislib_datatype == TYPE_8INT:
        if hasattr(gdalconst, "GDT_Int8"):
            out_dt = gdal.GDT_Int8
        else:
            raise RSGISPyException("GDT_Int8 not available in your GDAL installation.")
    else:
        raise RSGISPyException(
            f"The data type '{rsgislib_datatype}' is unknown / not supported."
        )
    return out_dt


def get_rsgislib_datatype_str(rsgislib_datatype: int) -> str:
    """
    Get a string representation of the datatype.
    Easier for a user to interpret.

    :return: str

    """
    if rsgislib_datatype == TYPE_16INT:
        out_dt = "16 bit INTEGER"
    elif rsgislib_datatype == TYPE_32INT:
        out_dt = "32 bit INTEGER"
    elif rsgislib_datatype == TYPE_8UINT:
        out_dt = "8 bit UNSIGNED INTEGER (BYTE)"
    elif rsgislib_datatype == TYPE_16UINT:
        out_dt = "16 bit UNSIGNED INTEGER"
    elif rsgislib_datatype == TYPE_32UINT:
        out_dt = "32 bit UNSIGNED INTEGER"
    elif rsgislib_datatype == TYPE_32FLOAT:
        out_dt = "32 bit FLOAT"
    elif rsgislib_datatype == TYPE_64FLOAT:
        out_dt = "64 bit FLOAT"
    elif rsgislib_datatype == TYPE_64INT:
        out_dt = "64 bit INTEGER"
    elif rsgislib_datatype == TYPE_64UINT:
        out_dt = "32 bit UNSIGNED INTEGER"
    elif rsgislib_datatype == TYPE_8INT:
        out_dt = "8 bit INTEGER"
    else:
        raise RSGISPyException(
            f"The data type '{rsgislib_datatype}' is unknown / not supported."
        )
    return out_dt


def get_numpy_datatype(rsgislib_datatype: int) -> int:
    """
    Convert from RSGISLib data type to numpy datatype

    :param rsgis_datatype:
    :return: numpy datatype

    """
    import numpy

    if rsgislib_datatype == TYPE_8INT:
        numpy_dt = numpy.int8
    elif rsgislib_datatype == TYPE_16INT:
        numpy_dt = numpy.int16
    elif rsgislib_datatype == TYPE_32INT:
        numpy_dt = numpy.int32
    elif rsgislib_datatype == TYPE_64INT:
        numpy_dt = numpy.int64
    elif rsgislib_datatype == TYPE_8UINT:
        numpy_dt = numpy.uint8
    elif rsgislib_datatype == TYPE_16UINT:
        numpy_dt = numpy.uint16
    elif rsgislib_datatype == TYPE_32UINT:
        numpy_dt = numpy.uint32
    elif rsgislib_datatype == TYPE_64UINT:
        numpy_dt = numpy.uint64
    elif rsgislib_datatype == TYPE_32FLOAT:
        numpy_dt = numpy.float32
    elif rsgislib_datatype == TYPE_64FLOAT:
        numpy_dt = numpy.float64
    else:
        raise RSGISPyException("Data type was not recognised.")
    return numpy_dt


def get_numpy_char_codes_datatype(rsgislib_datatype: int) -> str:
    """
    Convert from RSGISLib data type to numpy datatype

    :param rsgis_datatype:
    :return: numpy character code datatype

    """
    import numpy

    if rsgislib_datatype == TYPE_8INT:
        numpy_dt = numpy.dtype(numpy.int8).char
    elif rsgislib_datatype == TYPE_16INT:
        numpy_dt = numpy.dtype(numpy.int16).char
    elif rsgislib_datatype == TYPE_32INT:
        numpy_dt = numpy.dtype(numpy.int32).char
    elif rsgislib_datatype == TYPE_64INT:
        numpy_dt = numpy.dtype(numpy.int64).char
    elif rsgislib_datatype == TYPE_8UINT:
        numpy_dt = numpy.dtype(numpy.uint8).char
    elif rsgislib_datatype == TYPE_16UINT:
        numpy_dt = numpy.dtype(numpy.uint16).char
    elif rsgislib_datatype == TYPE_32UINT:
        numpy_dt = numpy.dtype(numpy.uint32).char
    elif rsgislib_datatype == TYPE_64UINT:
        numpy_dt = numpy.dtype(numpy.uint64).char
    elif rsgislib_datatype == TYPE_32FLOAT:
        numpy_dt = numpy.dtype(numpy.float32).char
    elif rsgislib_datatype == TYPE_64FLOAT:
        numpy_dt = numpy.dtype(numpy.float64).char
    else:
        raise RSGISPyException("Data type was not recognised.")
    return numpy_dt


def get_gdal_interp_type(rsgis_interp_method: int):
    gdal_interp_method = gdal.GRA_NearestNeighbour
    if rsgis_interp_method == INTERP_BILINEAR:
        gdal_interp_method = gdal.GRA_Bilinear
    elif rsgis_interp_method == INTERP_LANCZOS:
        gdal_interp_method = gdal.GRA_Lanczos
    elif rsgis_interp_method == INTERP_CUBICSPLINE:
        gdal_interp_method = gdal.GRA_CubicSpline
    elif rsgis_interp_method == INTERP_NEAREST_NEIGHBOUR:
        gdal_interp_method = gdal.GRA_NearestNeighbour
    elif rsgis_interp_method == INTERP_CUBIC:
        gdal_interp_method = gdal.GRA_Cubic
    elif rsgis_interp_method == INTERP_AVERAGE:
        gdal_interp_method = gdal.GRA_Average
    elif rsgis_interp_method == INTERP_MODE:
        gdal_interp_method = gdal.GRA_Mode
    elif rsgis_interp_method == INTERP_MIN:
        gdal_interp_method = gdal.GRA_Min
    elif rsgis_interp_method == INTERP_MAX:
        gdal_interp_method = gdal.GRA_Max
    elif rsgis_interp_method == INTERP_MEDIAN:
        gdal_interp_method = gdal.GRA_Med
    elif rsgis_interp_method == INTERP_Q1:
        gdal_interp_method = gdal.GRA_Q1
    elif rsgis_interp_method == INTERP_Q3:
        gdal_interp_method = gdal.GRA_Q3
    elif rsgis_interp_method == INTERP_SUM:
        gdal_interp_method = gdal.GRA_Sum
    else:
        raise RSGISPyException("Interpolation method was not recognised or known.")
    return gdal_interp_method


def is_notebook():
    """
    A function to find if running within a python notebook. If
    running within a jupyter notebook then can use a different
    progress bar.

    :return: boolean: True: within notebook

    """
    try:
        # Do not import get_ipython as if it is a notebook it will
        # already have been imported.
        shell = get_ipython().__class__.__name__
        if shell == "ZMQInteractiveShell":
            return True  # Jupyter notebook or qtconsole
        elif shell == "TerminalInteractiveShell":
            return False  # Terminal running IPython
        else:
            return False  # Other type (?)
    except NameError:
        return False  # Probably standard Python interpreter


class RSGISTime:
    """
    Class to calculate run time for a function, format and print out.

    Need to call start before running function and end immediately after.

    .. code:: python

        t = RSGISTime()
        t.start()
        rsgislib.segmentation.clump(kMeansFileZonesNoSgls, initClumpsFile,
                                    gdalformat, False, 0)
        t.end()

    Note, this is only designed to provide some general feedback, for
    benchmarking the timeit module is better suited.

    """

    def __init__(self):
        self.start_time = time.time()
        self.end_time = time.time()

    def start(self, print_start_time: bool = False):
        """
        Start timer, optionally printing start time

        :param print_start_time: A boolean specifying whether the start time
                                 should be printed to console.

        """
        self.start_time = time.time()
        if print_start_time:
            print(time.strftime("Start Time: %H:%M:%S, %a %b %m %Y."))

    def end(self, report_diff: bool = True, precede_str: str = "", post_str: str = ""):
        """
        End timer and optionally print difference.
        If precedeStr or postStr have a value then they will be used instead
        of the generic wording around the time.

        "{precede_str} {time} {post_str}"

        :param report_diff: A boolean specifying whether time difference should
                            be printed to console.
        :param precede_str: A string which is printed ahead of time difference
        :param post_str: A string which is printed after the time difference

        """
        self.end_time = time.time()
        if report_diff:
            self.calc_diff(precede_str, post_str)

    def calc_diff(self, precede_str: str = "", post_str: str = ""):
        """
        Calculate time difference, format and print.

        :param precede_str: A string which is printed ahead of time difference
        :param post_str: A string which is printed after the time difference

        """
        time_diff = self.end_time - self.start_time

        use_custom_mss = False
        if (len(precede_str) > 0) or (len(post_str) > 0):
            use_custom_mss = True

        if time_diff <= 1:
            if use_custom_mss:
                print(f"{precede_str} in less than a second {post_str}")
            else:
                print("Algorithm Completed in less than a second.")
        else:
            time_obj = datetime.datetime.utcfromtimestamp(time_diff)
            time_diff_str = time_obj.strftime("%H:%M:%S")
            if use_custom_mss:
                print(f"{precede_str} {time_diff_str} {post_str}")
            else:
                print(f"Algorithm Completed in {time_diff_str}.")


class TQDMProgressBar:
    """
    Uses TQDM TermProgress to print a progress bar to the terminal
    """

    def __init__(self):
        self.lprogress = 0
        self.pbar = None

    def setTotalSteps(self, steps: int):
        import tqdm

        if is_notebook():
            import tqdm.notebook

            self.pbar = tqdm.notebook.tqdm(total=steps)
        else:
            self.pbar = tqdm.tqdm(total=steps)
        self.lprogress = 0

    def setProgress(self, progress: int):
        if self.pbar is None:
            self.setTotalSteps(steps=100)
        step = progress - self.lprogress
        self.pbar.update(step)
        self.lprogress = progress

    def reset(self):
        import tqdm

        if self.pbar is not None:
            self.pbar.close()

        if is_notebook():
            import tqdm.notebook

            self.pbar = tqdm.notebook.tqdm(total=100)
        else:
            self.pbar = tqdm.tqdm(total=100)
        self.lprogress = 0

    def setLabelText(self, text: str):
        sys.stdout.write("\n%s\n" % text)

    def wasCancelled(self):
        return False

    def displayException(self, trace):
        sys.stdout.write(trace)

    def displayWarning(self, text: str):
        sys.stdout.write("Warning: %s\n" % text)

    def displayError(self, text: str):
        sys.stdout.write("Error: %s\n" % text)

    def displayInfo(self, text: str):
        sys.stdout.write("Info: %s\n" % text)
