"""This module concentrates the help functions to run the functions behind the scenes

"""


# Function list:
#
#     - LanguageManagment
#         - get_language(self)
#         - set_language(self, language)
#         - __str__(self)
#         - __repr__(self)
#
#     - AlphaManagement(LanguageManagment)
#         - get_alfa(self)
#         - set_alfa(self, alfa)
#         - __str__(self)
#         - __repr__(self)
#
#
#     - NDigitsManagement(LanguageManagment)
#         - get_n_digits(self)
#         - set_n_digits(self, n_digits)
#         - __str__(self)
#         - __repr__(self)

#     - _change_decimal_separator_x_axis(fig, axes, decimal_separator)
#     - _change_locale(language, decimal_separator=".", local="pt_BR")
#     - _change_locale_back_to_default(default_locale)
#     - _check_blank_space(value, param_name, language)
#     - _check_conflicting_filename(file_name, extension, language)
#     - _check_decimal_separator(decimal_separator, language)
#     - _check_figure_extension(value, param_name, language)
#     - _check_file_exists(file_name)
#     - _check_file_name_is_str(file_name, language)
#     - _check_forbidden_character(value, param_name, language)
#     - _check_plot_design(plot_design, param_name, plot_design_default, plot_design_example, language)
#     - _check_which_density_gaussian_kernal_plot(which, language)
#     - _export_to_csv(df, file_name="my_data", sep=',', language)
#     - _export_to_xlsx(df_list, language, file_name=None, sheet_names=[None,None])
#     - _flat_list_of_lists(my_list, param_name, language)
#     - _raises_when_fit_was_not_applied(func_name, language, name)
#     - _replace_last_occurrence(value, old, new, occurrence)
#     - _sep_checker(sep, language)
#     - _truncate(value, language, decs=None)



#########################################
################ Imports ################
#########################################

###### Standard ######
import locale
import logging
from pathlib import Path
import traceback

###### Third part ######
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

###### Home made ######
from pycafee.database_management import management
from pycafee.utils import general
from pycafee.utils import checkers


###########################################
################ Functions ################
###########################################



# with test, with database, with docstring
class LanguageManagement:
    """Instantiates a class for ``language`` management. This class is primarily for internal use.


    """

    def __init__(self, language=None, **kwargs):
        super().__init__(**kwargs)
        """Constructs the name of the language

        Parameters
        ----------
        language : string
            The abreviation of a language (dafualt None, which means 'en')

        Notes
        -----
        The method does not allow the input of values that are not of type string and that have lenght higher than 5 (due to the database restrictions)

        """
        all_languages = management._get_all_available_languages()
        current_default_language = management._get_current_default_language()

        # Obtendo o idioma padrão #
        if language is None:
            self.language = management._get_current_default_language()
        # Caso queria alterar o valor diretamente ao instanciar a classe
        else:
            # verificando se o valor passado é uma string
            checkers._check_is_str(language, "language", current_default_language)
            # agora verificando se o valor passado é um idioma válido
            if language not in all_languages.keys():
                all_formated_languages = [f"  --->  {lang}" for lang in all_languages]
                fk_id_function = management._query_func_id("LanguageManagment")
                messages = management._get_messages(fk_id_function, current_default_language)
                try:
                    raise ValueError(messages[1][0][0])
                except ValueError:
                    msg = [
                        f"{messages[2][0][0]} '{language}' {messages[2][2][0]}",
                        f"{messages[2][3][0]}",
                        all_formated_languages
                    ]
                    msg = general._flatten_list_of_list_string(msg)
                    msg = list(msg)
                    general._display_n_line_attention(msg)
                    raise
            else:
                self.language = language

    def get_language(self):
        """Returns the current language
        """
        return self.language

    def set_language(self, language):
        """Changes the current language

        Parameters
        ----------
        language : ``str``
            The language code

        Notes
        -----
        The ``language`` must be a ``str`` with no more then ``5`` elements.

        """
        all_languages = management._get_all_available_languages()
        # verificando se o valor passado é uma string
        checkers._check_is_str(language, "language", self.language)
        # agora verificando se o valor passado é um idioma válido
        if language not in all_languages.keys():
            all_formated_languages = [f"  --->  {lang}" for lang in all_languages]
            fk_id_function = management._query_func_id("LanguageManagment")
            messages = management._get_messages(fk_id_function, self.language)
            try:
                raise ValueError(messages[1][0][0])
            except ValueError:
                msg = [
                    f"{messages[2][0][0]} '{language}' {messages[2][2][0]}",
                    f"{messages[2][3][0]}",
                    all_formated_languages
                ]
                msg = general._flatten_list_of_list_string(msg)
                msg = list(msg)
                general._display_n_line_attention(msg)
                raise
        else:
            self.language = language

    def __str__(self):
        fk_id_function = management._query_func_id("LanguageManagement")
        messages = management._get_messages(fk_id_function, self.language)
        return f"{messages[5][0][0]} '{self.language}'"

    def __repr__(self):
        return self.language

# with test, with database, with docstring
class AlphaManagement(LanguageManagement):
    """Instanciates a class for ``alpha`` managment. This class inherits from :class:`.LanguageManagement` and it is primarily for internal use.

    """

    def __init__(self, alfa=None, **kwargs):
        super().__init__(**kwargs)
        """Constructs the significance level value

        Parameters
        ----------
        alfa : ``float``
            The significance level (default is ``None``, which means ``0.05``)

        Notes
        -----
        This method only allows input of type ``float`` and between ``0.0`` and ``1.0``.

        """

        if alfa is None:
            self.alfa = 0.05
        else:
            checkers._check_is_float(alfa, "alfa", self.language)
            checkers._check_data_in_range(alfa, "alfa", 0.0, 1.0, self.language)
            self.alfa = alfa

    def get_alfa(self):
        """Returns the current ``alpha`` value
        """
        return self.alfa

    def set_alfa(self, alfa):
        """Changes the ``alpha`` value

        Parameters
        ----------
        alfa : ``float``
            The new significance level

        Notes
        -----
        This method only allows input of type ``float`` and between ``0.0`` and ``1.0``.

        """
        checkers._check_is_float(alfa, "alfa", self.language)
        checkers._check_data_in_range(alfa, "alfa", 0.0, 1.0, self.language)
        self.alfa = alfa

    def __repr__(self):
        return self.alfa

    def __str__(self):
        fk_id_function = management._query_func_id("AlphaManagement")
        messages = management._get_messages(fk_id_function, self.language)
        return f"{messages[1][0][0]} '{self.alfa}'"

# with test, with database, with docstring
class NDigitsManagement(LanguageManagement):
    """Instanciates a class for ``n_digits`` managment. This class inherits from :class:`.LanguageManagement` and it is primarily for internal use.

    """
    def __init__(self, n_digits=None, **kwargs):
        super().__init__(**kwargs)
        """Constructs the n_digits value

        Parameters
        ----------
        n_digits : ``int``
            The maximum number of decimal places that the calculated parameters can have, ``default = 3``.

        Notes
        -----
        This method only accepts ``int`` values higher than ``0``.

        """
        if n_digits is None:
            self.n_digits = 3
        else:
            checkers._check_is_integer(n_digits, "n_digits", self.language)
            checkers._check_is_positive(n_digits, "n_digits", self.language)
            self.n_digits = n_digits

    def get_n_digits(self):
        """Returns the ``n_digits`` parameter
        """
        return self.n_digits

    def set_n_digits(self, n_digits):
        """Sets the ``n_digits`` parameter

        Parameters
        ----------
        n_digits : ``int``
            The maximum number of decimal places to be shown.

        """
        checkers._check_is_integer(n_digits, "n_digits", self.language)
        checkers._check_is_positive(n_digits, "n_digits", self.language)
        self.n_digits = n_digits

    def __repr__(self):
        return self.n_digits

    def __str__(self):
        fk_id_function = management._query_func_id("NDigitsManagement")
        messages = management._get_messages(fk_id_function, self.language)
        return f"{messages[1][0][0]} '{self.n_digits}'"



class PlotsManagement:
    """This class just instantiates the default values for ploting and add methods to get/control these default values. It is primarily for internal use.

    """
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        # enviar estes valores para a database e permiter a alteração(?)
        self.width = 12
        self.height = 6
        self.export = False
        self.extension = "png"
        self.dpi = 100
        self.tight = True
        self.transparent = False
        self.legend = True
        self.decimal_separator = "."
        self.local = "pt_BR"
        self.legend_label = "data"
        self.x_label = "data"
        self.y_label = "data"


    def _get_default_width(self,width):
        if width == 'default':
            width = self.width
        else:
            checkers._check_is_float_or_int(width, "width", self.language)
        return width

    def _get_default_height(self,height):
        if height == 'default':
            height = self.height
        else:
            checkers._check_is_float_or_int(height, "height", self.language)
        return height

    def _get_default_export(self,export):
        if export is None:
            export = self.export
        else:
            checkers._check_is_bool(export, "export", self.language)
        return export

    def _get_default_extension(self, extension):

        if extension is None:
            extension = self.extension
        else:
            checkers._check_is_str(extension, "extension", self.language)
            _check_forbidden_character(extension, "extension", self.language)
            _check_figure_extension(extension, "extension", self.language)
        return extension

    def _get_default_dpi(self,dpi):
        if dpi is None:
            dpi = self.dpi
        else:
            checkers._check_is_float_or_int(dpi, "dpi", self.language)
            checkers._check_is_positive(dpi, "dpi", self.language)
        return dpi

    def _get_default_tight(self,tight):
        if tight is None:
            tight = self.tight
        else:
            checkers._check_is_bool(tight, "tight", self.language)
        return tight

    def _get_default_transparent(self,transparent):
        if transparent is None:
            transparent = self.transparent
        else:
            checkers._check_is_bool(transparent, "transparent", self.language)
        return transparent

    def _get_default_legend(self,legend):
        if legend is None:
            legend = self.legend
        else:
            checkers._check_is_bool(legend, "legend", self.language)
        return legend

    def _get_default_x_label(self, x_label):
        if x_label is None:
            x_label = self.x_label
        else:
            checkers._check_is_str(x_label, "x_label", self.language)
        return x_label

    def _get_default_y_label(self, y_label):
        if y_label is None:
            y_label = self.y_label
        else:
            checkers._check_is_str(y_label, "y_label", self.language)
        return y_label

    def _get_default_decimal_separator(self, decimal_separator):
        if decimal_separator is None:
            decimal_separator = self.decimal_separator
        else:
            pass # o controle é feito em outra função
        return decimal_separator

    # def _get_default_local(self, local):
    #     if local is None:
    #         local = self.local
    #     else:
    #         pass # o controle é feito em outra função
    #     return local

    def _get_default_legend_label(self, legend_label):
        if legend_label is None:
            legend_label = self.legend_label
        else:
            checkers._check_is_str(legend_label, "legend_label", self.language)
        return legend_label


# with some test, no text, no database, with docstring
def _change_decimal_separator_x_axis(fig, axes, decimal_separator):
    """Esta função altera o separador de casa decimal através da mudança do seu label
    """
    if decimal_separator != ".":
        new_x_ticks_labels = []
        fig.canvas.draw()
        for text in axes.get_xticklabels():
            result = _replace_last_occurrence(text.get_text(), '.', decimal_separator, 1)
            new_x_ticks_labels.append(result)
        new_x_ticks = []
        for text in axes.get_xticklabels():
            result = _replace_last_occurrence(text.get_text(), '.', decimal_separator, 1)
            new_x_ticks.append(result)
        axes.set_xticklabels(new_x_ticks_labels)
        # for text in axes.get_xticklabels():
        #     temp = text.get_text()
        return axes
    else:
        return axes

# with some test, no text, no database, with docstring
def _change_decimal_separator_y_axis(fig, axes, decimal_separator):
    """Esta função altera o separador de casa decimal através da mudança do seu label
    """
    if decimal_separator != ".":
        new_y_ticks = []
        fig.canvas.draw()
        for text in axes.get_yticklabels():
            result = _replace_last_occurrence(text.get_text(), '.', decimal_separator, 1)
            new_y_ticks.append(result)
        axes.set_yticklabels(new_y_ticks)
        for text in axes.get_yticklabels():
            temp = text.get_text()
        return axes
    else:
        return axes

## This is current not used due to an issue on google colab ##
def _change_locale(language, decimal_separator=".", local="pt_BR"):
    """This function momentarily changes the decimal separator used in charts.

    Parameters
    ----------
    decimal_separator : string (default ".")
        The decimal separator symbol. It can be the dot (default '.') or the comma (',').
    local : string (default "pt_BR")
        The alias for the desired locale. Only used if decimal_separator is a comma, setting matplolib's default value. Its only function is to change the decimal separator symbol and should be changed only if the "pt_BR" option is not available.
    language : string
        The language code

    Notes
    -----
    The parameter 'language' isn't checked if it is a string.

    Returns
    -------
    default_locale : The user's system default value (string or None)
        - None if the default values are used
        - String with the user's system default value, which will be passed to the _change_locale_back_to_default() function to restore the user's default value

    Notes
    -----
    This function must always be used in conjunction with the _change_locale_back_to_default(default_locale) function.

    """
    # conferindo o local
    checkers._check_is_str(local, "local", language)
    # conferindo o decimal_separator
    checkers._check_is_str(decimal_separator, "decimal_separator", language)
    current_decimal_separator_available = [".", ","]
    # querring
    func_name = "_change_locale"
    fk_id_function = management._query_func_id(func_name)
    messages = management._get_messages(fk_id_function, language, func_name)
    if decimal_separator not in current_decimal_separator_available:
        try:
            raise ValueError(messages[1][0][0])
        except ValueError:
            general._display_one_line_attention(
                    f"{messages[2][0][0]} 'decimal_separator' {messages[2][2][0]} '{decimal_separator}'",
                                )
            raise
    if decimal_separator == ",":
        default_locale = locale.getdefaultlocale()[0]
        try:
            locale.setlocale(locale.LC_NUMERIC, local)
            plt.rcParams['axes.formatter.use_locale'] = True
        except locale.Error:
            general._display_one_line_attention(
                f"{messages[3][0][0]} {local} {messages[3][2][0]}"
            )
            # if 'google.colab' in str(get_ipython()):
            #   print('Running on CoLab')
            # else:
            #   print('Not running on CoLab')
            raise
    else:
        default_locale = None
    return default_locale

## This is current not used due to an issue on google colab ##
# with a test, no text, no database, with docstring
def _change_locale_back_to_default(default_locale):
    """This function restores the local parameter to its default value.

    Parameters
    ----------
    default_locale : string
        The default value of locale, which is obtained through the _change_locale() function

    Notes
    -----
    This function must always be used in conjunction with the _change_locale(decimal_separator=".", local="pt_BR") function.

    """
    if default_locale is not None:
        locale.setlocale(locale.LC_NUMERIC, default_locale)
        plt.rcParams['axes.formatter.use_locale'] = False

# with tests, with text, with database, with docstring
def _check_blank_space(value, param_name, language):
    """This function checks if a string has white space

    The function compares the length of 'value' with the length of first element in value.split(). If these lengths are equal, 'value' has no white space.
    The function does not check if value is a string, and this should be done previously to prevent errors.

    Parameters
    ----------
    value : string
        The string to be evaluated
    param_name : string
        The param_name
    language : string
        The language code

    Notes
    -----
    The parameter 'param_name' isn't checked if it is a string.
    The parameter 'language' isn't checked if it is a string.


    Returns
    -------
        True is value does not have white space
        Raises ValueError if value has white space

    """
    checkers._check_is_str(value, param_name, language)
    value_size = len(value)
    value_split = value.split()[0]
    value_split_size = len(value_split)
    if value_size == value_split_size:
        return True
    else:
        func_name = "_check_blank_space"
        fk_id_function = management._query_func_id(func_name)
        messages = management._get_messages(fk_id_function, language, func_name)
        try:
            raise ValueError(messages[1][0][0])
        except ValueError:
            general._display_one_line_attention(
                                f"{messages[2][0][0]} '{value}' {messages[2][2][0]}",
                                )
            raise

# with test, with database, with docstring
def _check_conflicting_filename(file_name, extension, language):
    """This function checks if a file exists in the current directory. If it exists, it returns a different name for the file, so as not to overwrite existing files.

    Paramters
    ---------
    file_name : ``str``
        The name of the file to be exported, without its extension
    extension : ``str``
        The file extension
    language : ``str``
        The language code

    Notes
    -----
    The parameter ``file_name`` isn't checked if it is a string.
    The parameter ``extension`` isn't checked if it is a string.
    The parameter ``language`` isn't checked if it is a string.

    If the file_name + extension does not exists in the current directory, the output os file_name + "." + extension
    If the file_name + extension exists in the current directory, a string ("_n") is inserted at the end od the file_name (where n is a number). Is this is true, the user is wanrned about it.

    Returns
    -------
    file_name : ``str``
        The file name that does not exists in the current directory
    exitis : ``bool``
        Whether the file already exists (``True``) or not (``False``)

    """
    ### Baptism of Fire ###
    file = file_name + "." + extension
    # if the file already exists, create a new name for the file
    file_exists = _check_file_exists(file)
    if file_exists:
        exitis = True
        ### quering ###
        func_name = "_check_conflicting_filename"
        fk_id_function = management._query_func_id(func_name)
        messages = management._get_messages(fk_id_function, language, func_name)
        i = 0
        msg_1 = f"{messages[1][0][0]} '{file}' {messages[1][2][0]}"
        # try to create a new name by adding a number to the end of the name.
        while file_exists:
            i += 1
            file = file_name + "_" + str(i) + "." + extension
            file_exists = _check_file_exists(file)
        file_name = file
        # warn that the file already exists
        # warn the user the name of the file to be exported
        general._display_n_line_warn([
                        f"    {messages[2][0][0]}",
                        msg_1,
                        f"{messages[3][0][0]} '{file_name}'"
                        ])
    else:
        file_name = file
        exitis = False
    return exitis, file_name

# with tests, with text, with database, with docstring
def _check_decimal_separator(decimal_separator, language):
    """This function checks if decimal_separator is valid

    Parameters
    ----------
    decimal_separator : ``str``
        The value to check if it valied.
    language : ``str``
        The language code

    Notes
    -----
    The parameter ``param_name`` isn't checked if it is a ``str``.
    The parameter ``language`` isn't checked if it is a ``str``.


    The allowed decimal separators are:

        ".": "dot",
        ",": "comma",


    Returns
    -------
    True if decimal_separator is valid.
    Raises ValueError is decimal_separator is not a valid.



    """
    list_of_allowed = [".", ","]


    if decimal_separator not in list_of_allowed:
        ### quering ###
        func_name = "_check_decimal_separator"
        fk_id_function = management._query_func_id(func_name)
        messages = management._get_messages(fk_id_function, language, func_name)
        try:
            error = messages[1][0][0]
            raise ValueError(error)
        except ValueError:
            all_formated_characters = [f"    --->    '{charcater}'" for charcater in list_of_allowed]
            msg = [
                f"{messages[2][0][0]} '{decimal_separator}' {messages[2][2][0]}",
                f"{messages[3][0][0]}",
                all_formated_characters
                ]
            flat_list = general._flatten_list_of_list_string(msg)
            flat_list = list(flat_list)
            general._display_n_line_attention(
                flat_list
            )
            raise
    return True


# with test, with text, with database, with docstring
def _check_figure_extension(value, param_name, language):
    """This function checks if 'value' is one of the extensions available for exporting figures using matplotlib.

    The allowed extensions for matlab version 3.5 are:
        'eps'  : 'Encapsulated Postscript'
        'jpg'  : 'Joint Photographic Experts Group'
        'jpeg' : 'Joint Photographic Experts Group'
        'pdf'  : 'Portable Document Format'
        'pgf'  : 'PGF code for LaTeX'
        'png'  : 'Portable Network Graphics'
        'ps'   : 'Postscript'
        'raw'  : 'Raw RGBA bitmap'
        'rgba' : 'Raw RGBA bitmap'
        'svg'  : 'Scalable Vector Graphics'
        'svgz' : 'Scalable Vector Graphics'
        'tif'  : 'Tagged Image File Format'
        'tiff' : 'Tagged Image File Format'

    However, the options are checked using plt.gcf().canvas.get_supported_filetypes().

    Parameters
    ----------
    value : string
        The value to check if it is a valid extension for figure creation.
    param_name : string
        The original name of the parameter passed through the parameter 'value'.
    language : string
        The language code

    Notes
    -----
    The parameter 'param_name' isn't checked if it is a string.
    The parameter 'language' isn't checked if it is a string.

    To check the supported extensions on your system, use:

    >>> from matplotlib import pyplot as plt
    >>> suported_types = plt.gcf().canvas.get_supported_filetypes()
    >>> for key, value in suported_types.items():
            print(key, ":", value)
    >>> plt.close()
    Returns
    -------
    True if 'value' is an allowed extension.
    Raises ValueError is 'value' is not an allowed extension.


    """
    suported_types = plt.gcf().canvas.get_supported_filetypes()
    plt.close() # essencial, pois o acima criar uma figura em branco, e caso o código seja ativado vai criar essa fig em branco
    if value not in suported_types.keys():
        ### quering ###
        func_name = "_check_figure_extension"
        fk_id_function = management._query_func_id(func_name)
        messages = management._get_messages(fk_id_function, language, func_name)
        try:
            raise ValueError(messages[1][0][0])
        except ValueError:
            extensions_allowed = []
            for key, value in suported_types.items():
                extensions_allowed.append(f"    --->    '{key}':  {value}")

            msg = [
                f"{messages[2][0][0]} '{value}' {messages[2][2][0]} '{param_name}' {messages[2][4][0]}!",
                messages[3][0][0],
                extensions_allowed
                ]
            flat_list = general._flatten_list_of_list_string(msg)
            flat_list = list(flat_list)
            general._display_n_line_attention(
                flat_list
            )
            raise
    return True

# with tests, with no text with no database, with docstring
def _check_file_exists(file_name):
    """This function checks if a file already exists on the current folder

    Parameters
    ----------
    file_name : string
        The file name (with extension).

    Return
    ------
    True if file already exists
    or
    False if file does not exists

    """
    file = Path(file_name)
    if file.exists():
        return True
    else:
        return False

# with tests, with no text with no database, with docstring
def _check_file_name_is_str(file_name, language):
    """This function checks if a variable can be the name of a file, checking if it is of type string and if its size is greater than zero.

    Parameters
    ----------
    file_name : string
    language : string
        The language code

    Notes
    -----
    The parameter 'language' isn't checked if it is a string.

    """

    if isinstance(file_name, str) == False:
        ### quering ###
        func_name = "_check_file_name_is_str"
        fk_id_function = management._query_func_id(func_name)
        messages = management._get_messages(fk_id_function, language, func_name)
        try:
            raise ValueError(messages[1][0][0])
        except ValueError:
            general._display_one_line_attention(
                                text = f"{messages[2][0][0]} '{str(type(file_name).__name__)}'"
                                )
            raise
    if len(file_name) == 0:
        func_name = "_check_file_name_is_str"
        fk_id_function = management._query_func_id(func_name)
        messages = management._get_messages(fk_id_function, language, func_name)
        try:
            raise ValueError(messages[3][0][0])
        except ValueError:
            # "query"
            general._display_one_line_attention(
                                text = f"{messages[4][0][0]}"
                                )
            raise

# with tests, with text, with database, with docstring
def _check_forbidden_character(value, param_name, language):
    """This function checks if there are characters that can be problematic for a file name.

    Parameters
    ----------
    value : string
        The value to check if it has some espcific characters.
    param_name : string
        The original name of the parameter passed through the parameter 'value'.
    language : string
        The language code

    Notes
    -----
    The parameter 'param_name' isn't checked if it is a string.
    The parameter 'language' isn't checked if it is a string.

    This function checks if a string contains some characters that can be problematic for saving files.
    The forbidden characters are:

        "/": 'forward slash',
        "<": "less than",
        ">": "greater than",
        ":": "colon",
        "\"": "double quote",
        "\\": "back slash",
        "|": "vertical bar",
        "?": "question mark",
        "*": "asterisk",
        ".": "dot",
        ",": "comma",
        "[": "left square bracket",
        "]": "right square bracket",
        ";": "semicolon",

    Returns
    -------
    True if value does not have any forbidden.
    Raises ValueError is value is not a valid string.

    References
    ----------
    .. [1] https://stackoverflow.com/a/31976060/17872198
    .. [2] https://stackoverflow.com/q/1976007/17872198
    """
    list_of_forbbiden = {
        "/": 'forward slash',
        "<": "less than",
        ">": "greater than",
        ":": "colon",
        "\"": "double quote",
        "\\": "back slash",
        "|": "vertical bar",
        "?": "question mark",
        "*": "asterisk",
        ".": "dot",
        ",": "comma",
        "[": "left square bracket",
        "]": "right square bracket",
        ";": "semicolon",
    }
    for key in list_of_forbbiden.keys():
        if key in value:
            ### quering ###
            func_name = "_check_forbidden_character"
            fk_id_function = management._query_func_id(func_name)
            messages = management._get_messages(fk_id_function, language, func_name)
            try:
                raise ValueError(messages[1][0][0])
            except ValueError:
                list_of_charcater = list(list_of_forbbiden.keys())
                all_formated_characters = [f"    --->    '  {charcater}  '" for charcater in list_of_charcater]
                msg = [
                    f"{messages[2][0][0]} '{key}' ({list_of_forbbiden[key]}) {messages[2][2][0]}",
                    f"{messages[3][0][0]}",
                    all_formated_characters
                    ]
                flat_list = general._flatten_list_of_list_string(msg)
                flat_list = list(flat_list)
                general._display_n_line_attention(
                    flat_list
                )
                raise
    return True

# with tests, with text, with database, with docstring
def _check_plot_design(plot_design, param_name, plot_design_default, plot_design_example, language):
    """This function checks if the dictionary passed through the plot_desing parameter is correct.

    Parameters
    ----------
    plot_design : dict
        The dictionary to be tested, which is passed by the user
    param_name : string
        The name of the parameter being tested
    plot_design_default : dict
        A dictionary containing the default values to be compared with the plot_design
    plot_design_example: dict
        A dictionary with the same keys as plot_design, but the values are strings that are used as an example.
    language : string
        The language code

    Notes
    -----
    The parameter 'param_name' isn't checked if it is a string.
    The parameter 'language' isn't checked if it is a string.

    Returns
    -------
    True if 'plot_design' seems to be corret
    ValueError if 'plot_design' has something wrong

    """

    # Verificando se plot_design
    checkers._check_is_dict(plot_design, param_name, language)

    # lista com as chaves passadas
    data_keys_list = list(plot_design.keys())

    # lista com as chaves necessárias
    keys_list = list(plot_design_default.keys())

    # verificando o dicionário tem as chaves esperadas
    for chave in keys_list:
        if chave not in data_keys_list:
            ### quering ###
            func_name = "_check_plot_design"
            fk_id_function = management._query_func_id(func_name)
            messages = management._get_messages(fk_id_function, language, func_name)
            try:
                raise ValueError(messages[1][0][0])
            except ValueError:
                all_formated_keys = [f"    --->  '{key}'" for key in keys_list]
                msg = [
                    f"{messages[2][0][0]} '{chave}' {messages[2][2][0]}",
                    f"{messages[3][0][0]}",
                    all_formated_keys
                ]
                msg = general._flatten_list_of_list_string(msg)
                msg = list(msg)
                general._display_n_line_attention(msg)
                raise
    # verificando se o número de chaves passadas é correto
    if len(plot_design.keys()) != len(plot_design_default.keys()):
        func_name = "_check_plot_design"
        fk_id_function = management._query_func_id(func_name)
        messages = management._get_messages(fk_id_function, language, func_name)
        try:
            raise ValueError(messages[4][0][0])
        except ValueError:
            all_formated_keys = [f"    --->  '{key}'" for key in keys_list]
            msg = [
                f"{messages[5][0][0]} '{len(plot_design_default.keys())}', {messages[5][2][0]} '{len(plot_design.keys())}'.",
                f"{messages[6][0][0]}",
                all_formated_keys
            ]
            msg = general._flatten_list_of_list_string(msg)
            msg = list(msg)
            general._display_n_line_attention(msg)
            raise

    # verificando os values
    # conferindo se todos os values são listas
    for chave, value in plot_design.items():
        func_name = "_check_plot_design"
        fk_id_function = management._query_func_id(func_name)
        messages = management._get_messages(fk_id_function, language, func_name)
        if isinstance(value, list) == False:
            try:
                raise ValueError(messages[7][0][0])
            except ValueError:
                general._display_one_line_attention(
                                    f"{messages[8][0][0]} '{chave}' {messages[8][2][0]} '{type(value).__name__}'"
                                    )
                raise
    # verificando o tamanho da lista para cada dict
    for chave, value in plot_design.items():
        if len(plot_design[chave]) != len(plot_design_default[chave]):
            func_name = "_check_plot_design"
            fk_id_function = management._query_func_id(func_name)
            messages = management._get_messages(fk_id_function, language, func_name)
            try:
                raise ValueError(messages[9][0][0])
            except ValueError:
                msg = [
                    f"{messages[10][0][0]} '{chave}' {messages[10][2][0]} {len(plot_design_default[chave])}, {messages[10][4][0]} {len(plot_design[chave])}.",
                    f"{messages[11][0][0]}",
                    f"    --->   {plot_design_example[chave]}"
                    ]
                general._display_n_line_attention(msg)
                raise
    # verificando o tipo de cada elemento dentro das listas
    for (chave, value), (chave_def, value_def) in zip(plot_design.items(), plot_design_default.items()):
        for i in range(len(value)):
            func_name = "_check_plot_design"
            fk_id_function = management._query_func_id(func_name)
            messages = management._get_messages(fk_id_function, language, func_name)
            if type(value[i]) != type(value_def[i]):
                try:
                    raise ValueError(messages[12][0][0])
                except ValueError:
                    msg = f"{messages[13][0][0]} '{i}' {messages[13][2][0]} '{chave}' {messages[13][4][0]} '{type(value_def[i]).__name__}', {messages[13][6][0]} '{type(value[i]).__name__}'"
                    general._display_one_line_attention(
                                        msg
                                        )
                    raise
    return True

# with tests, with text, with database,, with docstring
def _check_which_density_gaussian_kernal_plot(which, language):
    """This function checks if the value passed to the 'which' parameter used to draw the Gaussian density plot is valid.

    Parameters
    ----------
    which : string
        A string containing the keys used to draw the kde graph.
    language : string
        The language code

    Notes
    -----
    The parameter 'language' isn't checked if it is a string.

    Return
    ------
    True if which was considered correct
    Raise ValueError if it was not considered correct

    """
    which_original = which
    list_accepted_keys = ["mean", "median", "mode", "all"]
    # removando espaços em branco
    which = which.replace(" ", "")
    # verificando se tem apenas caracteres aceitos
    list_accepted_letters = ["a", "e", "d", "i", "l", "m", "n", "o", ","]
    list_which = list(which)
    for word in list_which:
        if word not in list_accepted_letters:
            ### quering ###
            func_name = "_check_which_density_gaussian_kernal_plot"
            fk_id_function = management._query_func_id(func_name)
            messages = management._get_messages(fk_id_function, language, func_name)
            try:
                error = messages[1][0][0]
                raise ValueError(error)
            except ValueError:
                msg = [
                        f"{messages[2][0][0]} '{which}' {messages[2][2][0]}.",
                        f"{messages[3][0][0]}",
                        [f"    --->  {chave}" for chave in list_accepted_keys],
                ]
                msg = general._flatten_list_of_list_string(msg)
                msg = list(msg)
                general._display_n_line_attention(msg)
                raise


    which = which.split(",")
    for value in which:
        func_name = "_check_which_density_gaussian_kernal_plot"
        fk_id_function = management._query_func_id(func_name)
        messages = management._get_messages(fk_id_function, language, func_name)
        if value not in list_accepted_keys:
            try:
                error = messages[1][0][0]
                raise ValueError(error)
            except ValueError:
                msg = [
                        f"{messages[2][0][0]} '{value}' {messages[2][2][0]}.",
                        f"{messages[3][0][0]}",
                        [f"    --->  {chave}" for chave in list_accepted_keys],
                ]
                msg = general._flatten_list_of_list_string(msg)
                msg = list(msg)
                general._display_n_line_attention(msg)
                raise
    if "all" in which:
        func_name = "_check_which_density_gaussian_kernal_plot"
        fk_id_function = management._query_func_id(func_name)
        messages = management._get_messages(fk_id_function, language, func_name)
        if len(which) != 1:
            try:
                error = messages[4][0][0]
                raise ValueError(error)
            except ValueError:

                general._display_two_line_attention(
                    f"{messages[5][0][0]}",
                    f"    ---->    '{which_original}'"
                )
                raise

    return which

# with SOME test, with text, with database
def _export_to_csv(df, language, file_name="my_data", sep=','):
    """Export the data to csv file
    This function is just a wraper around DataFrame.to_csv to export csv files.

    Parameters
    ----------
    df : pandas.DataFrame
        The DataFrame with the data to be exported
    language : string
        The language code
    file_name : string
        The name of the file to be exported, without its extension (default = 'my_data')
    sep : string of length 1
        Field delimiter for the output file (default = ';'). The same parameter of the DataFrame.to_csv pandas method [1]_


    Notes
    -----
    The parameter 'language' isn't checked if it is a string.

    References
    ----------
    .. [1] https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.to_csv.html

    """

    # cheking if the filename is correct
    _check_file_name_is_str(file_name, language)
    # checking if a problematic character is present in the filename
    _check_forbidden_character(file_name, "file_name", language)
    # adding the '.csv' extension
    file = file_name + '.csv'
    # cheking if the sep is valid
    _sep_checker(sep, language)
    # cheking if the df is a DataFrame
    checkers._check_is_data_frame(df, file_name, language)
    # cheking if the file already exists. If it does, ask the user if wants to replace the file
    file_exists = _check_file_exists(file)

    ### querring ###
    func_name = "_export_to_csv"
    fk_id_function = management._query_func_id(func_name)
    messages = management._get_messages(fk_id_function, language, func_name)

    # if the file already exists, create a new name for the file
    if file_exists:
        # warn that the file already exists
        print(f"{messages[1][0][0]} '{file}' {messages[1][2][0]}")
        i = 0
        # try to create a new nine by adding a number to the end of the name.
        while file_exists:
            i += 1
            file = file_name + "_" + str(i) + ".csv"
            file_exists = _check_file_exists(file)
        file_name = file
        # warn the user the name of the file to be exported
        print(f"     ---> {messages[2][0][0]} '{file_name}'")
    else:
        file_name = file_name + '.csv'

    try:
        df.to_csv(file_name, encoding='utf-8-sig', index=False, sep=sep)
        general._display_one_line_success(
            text = f"{messages[3][0][0]} '{file_name}' {messages[3][2][0]}"
        )
    except PermissionError:
        # logging.error(traceback.format_exc())
        general._display_two_line_attention(
            text1 = f"{messages[4][0][0]} '{file_name}' {messages[4][2][0]}.",
            text2 = f"{messages[5][0][0]} '{file_name}' {messages[5][2][0]}"
            )
        raise
    except FileNotFoundError: # acredito que o problema com subfolder é resolvido com a proibição do /
        # logging.error(traceback.format_exc())
        general._display_two_line_attention(
            text1 = f"{messages[4][0][0]} '{file_name}' {messages[4][2][0]}.",
            text2 = f"{messages[6][0][0]}"
            )
        raise
    return True

# with SOME test, with text, with database, with docstring
def _export_to_xlsx(df_list, language, file_name=None, sheet_names=[None,None]):
    """Export the data to .xlsx file
    This function is just a wraper around DataFrame.to_excel [1]_ to export excel files.

    Parameters
    ----------
    df_list : list with pandas.DataFrame
        A list where each element is a DataFrame that will be inserted in a different sheet. The order of DataFrames in this list must match the list of names passed through the sheet_names parameter.
    language : string
        The language code
    file_name : string
        The name of the file to be exported, without its extension (default = 'my_data')
    sheetet_names : list (default [None, None])
        A list where each element is a string that will be used for each sheet. The order of strings in this list must match the list of DataFrames passed through the df_list parameter.


    Notes
    -----
    The parameter 'language' isn't checked if it is a string.

    Returns
    -------
    True if the file is exported
    ValueError if it was not possible to export the file

    References
    ----------
    .. [1] https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.to_excel.html

    """
    checkers._check_is_list(df_list, "df_list", language)
    for df in df_list:
        checkers._check_is_data_frame(df, "df", language)

    if file_name is None:
        file_name = "sample"
    else:
        checkers._check_is_str(file_name, "file_name", language)
        _check_forbidden_character(file_name, "file_name", language)

    checkers._check_is_list(sheet_names, "sheet_names", language)
    for name in sheet_names:
        checkers._check_is_str(name, "sheet_names", language)

    ### querring ###
    func_name = "_export_to_excel"
    fk_id_function = management._query_func_id(func_name)
    messages = management._get_messages(fk_id_function, language, func_name)

    # combando nome corretamente
    file_name = file_name + ".xlsx"

    # verificando se os dataframes são validos
    for df in df_list:
        checkers._check_is_data_frame(df, "df_list", language)

    ### verificando se o arquivo .xlsx já existe
    if _check_file_exists(file_name):
        ## caso exista, obter uma lista com o nome das abas do arquivo
        # obter os sheet names
        arquivo = pd.ExcelFile(file_name, engine="openpyxl")
        # iterar esta lista comparando se os sheet_names já existem nela
        sheet_alredy_exists = [] # lista vazia para acumular nomes repetidos
        for sheet in arquivo.sheet_names: # olhando nome por nome dentro de sheet_names
            if sheet in sheet_names:
                sheet_alredy_exists.append(sheet) # apendadndo
            else:
                pass
        # caso pelo menos 1 aba já exista, avisar que o nome escolhido será alterado
        if len(sheet_alredy_exists) > 0:
            lista_sheet_names = [f"    --->    '{sheet}'" for sheet in arquivo.sheet_names]
            sheet_alredy_exists = [f"    --->    '{sheet}'" for sheet in sheet_alredy_exists]
            msg = [
                f"{messages[1][0][0]} '{file_name}' {messages[1][2][0]}",
                lista_sheet_names,
                f"{messages[2][0][0]}",
                sheet_alredy_exists,
                "\n",
                f"{messages[3][0][0]}",
                f"{messages[4][0][0]}",
            ]
            msg = general._flatten_list_of_list_string(msg)
            msg = list(msg)
            general._display_n_line_attention(msg)
            arquivo.close()
        else:
            pass # é pass pois caso o sheet name já exista, é apenas para avisar que o nome será alterado. É apenas um aviso
        # caso não tenha nenhum nome conflitante, inserir novas abas no arquivo fornecido
        try:
            with pd.ExcelWriter(file_name, mode="a", if_sheet_exists="new") as writer:
                for i in range(len(df_list)):
                    df_list[i].to_excel(writer, sheet_name=sheet_names[i], index=False, engine="openpyxl")
            general._display_one_line_success(f"{messages[5][0][0]} '{file_name}' {messages[5][2][0]}")
        except PermissionError:
            general._display_two_line_attention(
                text1 = f"{messages[6][0][0]} '{file_name}' {messages[6][2][0]}",
                text2 = f"{messages[7][0][0]} '{file_name}' {messages[7][2][0]}"
                )
            raise
        except FileNotFoundError: # acredito que o problema com subfolder é resolvido com a proibição do /
            # logging.error(traceback.format_exc())
            general._display_two_line_attention(
                text1 = f"{messages[6][0][0]} '{file_name}' {messages[6][2][0]}",
                text2 = f"{messages[8][0][0]}"
                        )
            raise
    else:
        ## caso não exista, criar o arquivo e exportar
        try:
            with pd.ExcelWriter(file_name) as writer:
                for i in range(len(df_list)):
                    df_list[i].to_excel(writer, sheet_name=sheet_names[i], index=False, engine="openpyxl")
            general._display_one_line_success(f"{messages[5][0][0]} '{file_name}' {messages[5][2][0]}")
        except PermissionError:
            general._display_two_line_attention(
                text1 = f"{messages[6][0][0]} '{file_name}' {messages[6][2][0]}",
                text2 = f"{messages[7][0][0]} '{file_name}' {messages[7][2][0]}"
                )
            raise
        except FileNotFoundError: # acredito que o problema com subfolder é resolvido com a proibição do /
            # logging.error(traceback.format_exc())
            general._display_two_line_attention(
                text1 = f"{messages[6][0][0]} '{file_name}' {messages[6][2][0]}",
                text2 = f"{messages[8][0][0]}"
                        )
            raise
    return True

# with some test, with text, with database
def _flat_list_of_lists(my_list, param_name, language):
    """This function flats a list of lists

    Parameters
    ----------
    my_list : list of lists
        The list with lists to be flattened. All inner elements must be a list

    param_name : string
        The original name of the parameter passed through the parameter 'my_list'.

    Notes
    -----
    The parameter 'param_name' isn't checked if it is a string.
    The parameter 'my_list' isn't checked if it is a list.

    Returns
    -------
    A flattened list
    ValueError if 'my_list' does not contain lists in all its elements.

    """
    checkers._check_is_list(my_list, param_name, language)
    ### quering ###
    func_name = "_flat_list_of_lists"
    fk_id_function = management._query_func_id(func_name)
    messages = management._get_messages(fk_id_function, language, func_name)
    if all(isinstance(element, list) for element in my_list) == False:
        try:
            raise ValueError(messages[1][0][0])
        except ValueError:
            # "query"
            general._display_one_line_attention(
                                text = f"{messages[2][0][0]} {param_name} {messages[2][2][0]}"
                                )
            raise
    return [item for sublist in my_list for item in sublist]

# wtih tests, with text, the database is stored on func_name, with docstring
def _raises_when_fit_was_not_applied(func_name, language, name):
    """This function raises error when called

    Parameters
    ----------
    func_name : ``str``
        The name of the function sabed on the database
    language : ``str``
        The language code
    name : ``str``
        The sample name

    Notes
    -----
    The parameter ``language`` isn't checked if it is a string.
    The parameter ``name`` isn't checked if it is a string.

    Returns
    -------
    ``ValueError``

    Notes
    -----
    This function must always be used in conjunction with a fit function, where error and messages are at key 1 and 2.

    """
    fk_id_function = management._query_func_id(func_name)
    messages = management._get_messages(fk_id_function, language)
    try:
        error = messages[1][0][0]
        raise ValueError(error)
    except ValueError:
        general._display_one_line_attention(
            f"{messages[2][0][0]} {name}",
                            )
        raise

# with some test, no text, no database, with docstring
def _replace_last_occurrence(value, old, new, occurrence):
    """This function replaces the last ``occurrence`` of ``old`` in ``value`` with the value passed in ``new``

    Parameters
    ----------
    value : ``str``
        The text to be changed
    old : ``str``
        The character(s) to be replaced
    new : ``str``
        The new characters
    occurrence : ``int``
        The number of occurrences that will be replaced

    Returns
    -------

    value : ``str``
        The changed text


    References
    ----------
    .. [1] Adapted from mg. rreplace - How to replace the last occurrence of an expression in a string?. Available at: `stackoverflow.com <https://stackoverflow.com/a/2556252/17872198>`_. Access on: 10 May. 2022.

    """
    li = value.rsplit(old, occurrence)
    return new.join(li)



# with tests, with text, with database, with docstring
def _sep_checker(sep, language):
    """This function checks if a value could be used as 'sep' parameter for pandas to_csv function

    Parameters
    ----------
    sep : string
        The sep value
    language : string
        The language code

    Notes
    -----
    The parameter 'language' isn't checked if it is a string.

    Returns
    -------
    True if sep is valid.
    Raises ValueError is sep is not suitable to be used in pd.to_csv.

    """
    checkers._check_is_str(sep, "sep", language)
    ### quering ###
    func_name = "_sep_checker"
    fk_id_function = management._query_func_id(func_name)
    messages = management._get_messages(fk_id_function, language, func_name)

    if len(sep) != 1:
        try:
            raise ValueError(messages[1][0][0]) #message
        except ValueError:
            general._display_two_line_attention(
                                text1 = f"{messages[2][0][0]} 'sep' {messages[2][2][0]} '{len(sep)}' <--- ({sep})",
                                text2 = f'    {messages[3][0][0]}: ---> ";" '
                                )
            raise
    return True

# with tests, without database, with docstring
def _truncate(value, language, decs=None):
    """This function truncates a 'value' with 'decs' decimal places.

    This function is a wrapper around numpy.trunc() [1]_ adapted with a clever solution for the truncation of decimal places [2]_.

    Parameters
    ----------
    value : int or float
        The value to be truncated
    decs : int or None (default = None)
        The number of decimal places the value should be truncated
    language : string
        The language code

    Notes
    -----
    The parameter 'language' isn't checked if it is a string.

    Returns
    -------
    value
        if value or decs is None, just return the input value
        elif value is integer, just return the input value
        else return the value truncated

    References
    ----------
    .. [1] https://numpy.org/doc/stable/reference/generated/numpy.trunc.html
    .. [2] The inspiration came from: https://stackoverflow.com/a/46020635/17872198

    Examples
    --------

    >>> from easy_stat.utils.helpers import _truncate
    >>> print(_truncate(0.111, 'en', decs=0))
    0.0

    >>> print(_truncate(0.111, 'en', decs=1))
    0.1

    >>> print(_truncate(0.111, 'en', decs=2))
    0.11

    >>> print(_truncate(0.111, 'en', decs=10))
    0.111

    >>> print(_truncate(0.111, 'en', decs=None))
    0.111

    >>> print(_truncate(10, 'en', decs=2))
    10

    >>> print(_truncate(10, 'en', decs=None))
    10

    """
    if decs is None or value is None:
        return value
    else:
        checkers._check_is_integer(decs, "decs", language)
    checkers._check_is_float_or_int(value, "value", language)

    if isinstance(value, (int, np.uint, np.integer)):
        return value
    else:
        return np.trunc(value*10**decs)/(10**decs)



























# Death, death, death, death, death, death, death, death, death, Death, death, death https://youtu.be/jRc9dbgiBPI?t=334
