from __future__ import annotations

from typing import List, Optional

import numpy as np
import pandas as pd
import yaml

import ambrosia.tools.bin_intervals as bin_pkg
import ambrosia.tools.theoretical_tools as theory_pkg
from ambrosia import types
from ambrosia.tools.ab_abstract_component import ABMetaClass, ABToolAbstract, SimpleDesigner

from .handlers import EmpiricHandler, TheoryHandler, calc_prob_control_class

SIZE: str = "size"
EFFECT: str = "effect"
POWER: str = "power"
BINARY_DESIGN_METHODS: List[str] = ["theory", "binary"]

class Designer(yaml.YAMLObject, ABToolAbstract, metaclass=ABMetaClass):
    """
    Unit for experiments and pilots parameters design.

    Enables to design missing experiment parameters using historical data.
    The main related to each other designable parameters for a single metric are:

        - Effect (Minimal Detectible Effect):
            old_mean_metric_value * effect_value = new_mean_metric_value
        - Sample size:
            Number of research objects in sample
            (for example number of users and their retention).
        - Errors (I type error, II type error):
            I error (alpha):
                Probability to detect presence of effect
                for equally distributed samples.
            II error (beta):
                Probability not to find effect
                for differently distributed samples.

    Parameters
    ----------
    dataframe : PassedDataType, optional
        DataFrame with metrics historical values.
    sizes : SampleSizeType, optional
        Values of research objects number in groups samples during
        the experiment.
    effects : EffectType, optional
        Effects values that are expected during the experiment.
    first_type_errors : StatErrorType, default: ``0.05``
        I type error bounds
        P (detect difference for equal) < alpha.
    second_type_errors : StatErrorType, default: ``0.2``
        II type error bounds
        P (suppose equality for different groups) < beta.
    metrics : MetricNamesType, optional
        Column names of metrics in dataframe to be designed.
    method : str, optional
        Method used for experiment design.
        Can be ``"theory"``, ``"empiric"`` or ``"binary"``.

    Attributes
    ----------
    dataframe : PassedDataType
        DataFrame with metrics historocal values.
    sizes : SampleSizeType
        Number of research objects in group samples.
    effects : EffectType
        Effects values in the experiment.
    first_type_errors : StatErrorType, default: ``0.05``
        I type errors.
    second_type_errors : StatErrorType, default: ``0.2``
        II type errors.
    metrics : MetricNamesType
        Column names of metrics in dataframe to be designed.
    method : str
        Method used for experiment design.

    Examples
    --------
    We have retention labels for users of mobile app for previous month.
    Suppose old_retention = ``0.3``, that is 30% of users returned to the app
    in a month after installation.

    Let us fix the following parameters:
        I type error (alpha) = ``0.05``
        (5% of equal samples we can suppose to be different).

        II type error (beta) = ``0.2``
        (20% of different sampels we can suppose to be equal).

    We add onboarding to our app and want to estimate an effect, by A/B testing
    and wish to increase retention value to 31% percents, so our effect
    parameter gets value of ``1.0(3)``. Now we want to find how much users we
    need in both groups to detect such effect.

    We can use ``Designer`` class in the following way:

    >>> designer = Designer(dataframe=df, metric='retention', effect=1.033)
    >>> designer.run("size")

    Note, that default values for errors are:
        ``first_type_error`` = ``0.05``

        ``second_type_error`` = ``0.2``

    Then we get dataframe that contains value of  sufficient number of users
    for our experiment.

    Notes
    -----

    Constructors:

    >>> designer = Designer()
    >>> # You can pass an Iterable or single object for some parameters
    >>> designer = Designer(
    >>>     dataframe=df,
    >>>     sizes=[100, 200],
    >>>     metrics='LTV',
    >>>     effects=1.05
    >>> )
    >>> designer = Desginer(sizes=1000, metrics=['retention', 'LTV'])
    >>> # You can use path to .csv table for pandas
    >>> designer = Designer('./data/table.csv')

    Setters:

    >>> designer.set_first_errors([0.05, 0.01])
    >>> desginer.set_dataframe(df)

    Run:

    >>> # One can pass arguments and they will have higher priority
    >>> designer.run('size', effects=1.1)
    >>> designer.run('effect', sizes=[500, 1000], metrics='retention')
    >>> # You can set method (watch below)
    >>> designer.run('effect', sizes=[500, 1000], metrics='retention', method='binary')

    Load from yaml config:

    >>> config = '''
            !splitter # <--- this is yaml tag (!important)
                effects:
                    - 0.9
                    - 1.05
                sizes:
                    - 1000
        '''
    >>> designer = yaml.load(config)
    >>> # Or use the implmented function
    >>> designer = load_from_config(config)

    Use standalone function instead of a class:

    >>> design('size', dataframe=df, effects=1.05, metrics='retention')
    """

    # YAML tag for loading from configs
    yaml_tag = "!designer"

    def set_first_errors(self, first_type_errors: types.StatErrorType) -> None:
        if isinstance(first_type_errors, float):
            self.__alpha = [first_type_errors]
        else:
            self.__alpha = first_type_errors

    def set_second_errors(self, second_type_errors: types.StatErrorType) -> None:
        if isinstance(second_type_errors, float):
            self.__beta = [second_type_errors]
        else:
            self.__beta = second_type_errors

    def set_sizes(self, sizes: types.SampleSizeType) -> None:
        if isinstance(sizes, int):
            self.__size = [sizes]
        else:
            self.__size = sizes

    def set_effects(self, effects: types.EffectType) -> None:
        if isinstance(effects, (float, int)):
            self.__effect = [effects]
        else:
            self.__effect = effects

    def set_dataframe(self, dataframe: types.PassedDataType) -> None:
        if isinstance(dataframe, str):
            if dataframe.endswith(".csv"):
                self.__df = pd.read_csv(dataframe)
            else:
                raise ValueError("File name must ends with .csv")
        else:
            self.__df = dataframe

    def set_method(self, method: str) -> None:
        self.__method = method

    def set_metrics(self, metrics: str) -> None:
        if isinstance(metrics, types.MetricNameType):
            self.__metrics = [metrics]
        else:
            self.__metrics = metrics

    def __init__(
        self,
        dataframe: Optional[types.PassedDataType] = None,
        sizes: Optional[types.SampleSizeType] = None,
        effects: Optional[types.EffectType] = None,
        first_type_errors: types.StatErrorType = 0.05,
        second_type_errors: types.StatErrorType = 0.2,
        metrics: Optional[types.MetricNamesType] = None,
        method: str = "theory",
    ):
        """
        Designer class constructor to initialize the object.
        """
        self.set_first_errors(first_type_errors)
        self.set_second_errors(second_type_errors)
        self.set_sizes(sizes)
        self.set_effects(effects)
        self.set_metrics(metrics)
        self.set_dataframe(dataframe)
        self.set_method(method)

    def __getstate__(self):
        """
        Get the state of the object to serialize.
        """
        return dict(
            effects=self.__effect,
            sizes=self.__size,
            first_type_errors=self.__alpha,
            second_type_errors=self.__beta,
            metrics=self.__metrics,
            method=self.__method,
        )

    @classmethod
    def from_yaml(cls, loader: yaml.Loader, node: yaml.Node):
        kwargs = loader.construct_mapping(node)
        return cls(**kwargs)

    @staticmethod
    def __dataframe_handler(handler: SimpleDesigner, parameter: str, **kwargs) -> pd.DataFrame:
        """
        Handles different dataframe types.
        Now pandas and spark are available.
        """
        if parameter == SIZE:
            return handler.size_design(**kwargs)
        elif parameter == EFFECT:
            return handler.effect_design(**kwargs)
        elif parameter == POWER:
            return handler.power_design(**kwargs)
        else:
            raise ValueError(f"Only {SIZE}, {EFFECT} and {POWER} parameters of the experiment could be designed.")

    @staticmethod
    def __theory_design(label: str, args: types._UsageArgumentsType, **kwargs) -> types.DesignerResult:
        """
        Designing an experiment, using a theoretical approach.
        """
        result: types.DesignerResult = {}
        for metric_name in args["metric"]:
            kwargs["dataframe"] = args["df"]
            kwargs["column"] = metric_name
            kwargs["first_errors"] = np.array(args["alpha"])
            if label == SIZE:
                kwargs["effects"] = args[EFFECT]
                kwargs["second_errors"] = np.array(args["beta"])
            elif label == EFFECT:
                kwargs["sample_sizes"] = args[SIZE]
                kwargs["second_errors"] = np.array(args["beta"])
            elif label == POWER:
                kwargs["sample_sizes"] = args[SIZE]
                kwargs["effects"] = args[EFFECT]
            result[metric_name] = Designer.__dataframe_handler(TheoryHandler(), label, **kwargs)
        if len(args["metric"]) == 1:
            return result[args["metric"][0]]
        else:
            return result

    @staticmethod
    def __empiric_design(label: str, args: types._UsageArgumentsType, **kwargs) -> types.DesignerResult:
        """
        Designing an experiment, using an empirical approach.
        """
        kwargs["dataframe"] = args["df"]
        kwargs["alphas"] = np.array(args["alpha"])
        kwargs["metrics"] = args["metric"]
        if label == SIZE:
            kwargs["effects"] = args[EFFECT]
            kwargs["betas"] = np.array(args["beta"])
        elif label == EFFECT:
            kwargs["group_sizes"] = args[SIZE]
            kwargs["betas"] = np.array(args["beta"])
        elif label == POWER:
            groups_ratio: float = kwargs.pop("groups_ratio") if "groups_ratio" in kwargs else 1.0
            kwargs["sample_sizes_a"] = args[SIZE]
            kwargs["sample_sizes_b"] = [int(groups_ratio * size) for size in args[SIZE]]
            kwargs["effects"] = args[EFFECT]
        return Designer.__dataframe_handler(EmpiricHandler(), label, **kwargs)

    @staticmethod
    def __binary_design(label: str, args: types._UsageArgumentsType, **kwargs) -> types.DesignerResult:
        """
        Designing an experiment, using the approach for binary metrics.
        """
        result: types.DesignerResult = {}
        kwargs["first_errors"] = np.array(args["alpha"])
        for metric_name in args["metric"]:
            kwargs["p_a"] = calc_prob_control_class(args["df"], metric_name)
            if label == SIZE:
                kwargs["delta_relative_values"] = args[EFFECT]
                kwargs["second_errors"] = args["beta"]
                result[metric_name] = bin_pkg.get_table_sample_size_on_effect(**kwargs)
            elif label == EFFECT:
                kwargs["second_errors"] = args["beta"]
                kwargs["sample_sizes"] = args[SIZE]
                result[metric_name] = bin_pkg.get_table_effect_on_sample_size(**kwargs)
            elif label == POWER:
                kwargs["delta_relative_values"] = args[EFFECT]
                kwargs["sample_sizes"] = args[SIZE]
                result[metric_name] = bin_pkg.get_table_power_on_size_and_delta(**kwargs)
        if len(args["metric"]) == 1:
            return result[args["metric"][0]]
        else:
            return result

    @staticmethod
    def __pre_design(label: str, args: types._UsageArgumentsType, **kwargs) -> types.DesignerResult:
        """
        Helper function for run() method logic.
        """
        admissible_methods: List[str] = ["theory", "empiric", "binary"]
        if args["method"] == "theory":
            return Designer.__theory_design(label, args, **kwargs)
        elif args["method"] == "empiric":
            return Designer.__empiric_design(label, args, **kwargs)
        elif args["method"] == "binary":
            return Designer.__binary_design(label, args, **kwargs)
        else:
            raise ValueError(f'Choose method from {", ".join(admissible_methods)}, got {args["method"]}')

    def run(
        self,
        to_design: str,
        method: Optional[str] = None,
        sizes: Optional[types.SampleSizeType] = None,
        effects: Optional[types.EffectType] = None,
        first_type_errors: Optional[types.StatErrorType] = None,
        second_type_errors: Optional[types.StatErrorType] = None,
        dataframe: Optional[types.PassedDataType] = None,
        metrics: Optional[types.MetricNamesType] = None,
        **kwargs,
    ) -> types.DesignerResult:
        """
        Perform an experiment design for chosen parameter and metrics
        using historical data.

        Parameters
        ----------
        to_design : str
           Parameter that will be designed using historical data.
           Can take the values of ``"size"``, ``"effect"`` or ``"power"``.
        method : str, optional
            Method used for experiment design.
            Can be ``"theory"``, ``"empiric"`` or ``"binary"``.
        sizes : SampleSizeType, optional
            Values of research objects number in groups samples during
            the experiment.
            If is not provided, must exist as proper class attribute.
        effects : EffectType, optional
            Effects for experiment
            If is not provided, must exist as proper class attribute.
        first_type_errors : StatErrorType, optional
            I type error bounds
            P (detect difference for equal) < alpha.
        second_type_errors : StatErrorType, optional
            II type error bounds
            P (suppose equality for different groups) < beta.
        dataframe : PassedDataType, optional
            DataFrame with metrics historical values.
            If is not provided, must exist as proper class attribute.
        metrics : MetricNamesType, optional
            Column names of metrics in dataframe to be designed.
            If not provided, must exist as proper class attribute.
        **kwargs : Dict
            Other keyword arguments.

        Other Parameters
        ----------------
        as_numeric : bool, default: ``False``
            The result of calculations can be obtained as a percentage string
            either as a number, this parameter could used to toggle.
        groups_ratio : float, default: ``1.0``
            Ratio between two groups.
        alternative : str, default: ``"two-sided"``
            Alternative hypothesis, can be ``"two-sided"``, ``"greater"``
            or ``"less"``.
            ``"greater"`` - if effect is positive.
            ``"less"`` - if effect is negative.
        stabilizing_method : str, default: ``"asin"``
            Effect trasformation. Can be ``"asin"`` and ``"norm"``.
            For non-binary metrics: only ``"norm"`` is accceptable.
            For binary metrics: ``"norm"`` and ``"asin"``, but ``"asin"``
            is more robust and accurate.
            Acceptable only for ``"theory"`` method and actual for binary metrics!

        Returns
        -------
        result : DesignerResult
            Table or dictionary with the results of parameter design for each
            metric.
        """
        if isinstance(effects, (float, int)):
            effects = [effects]
        if isinstance(sizes, int):
            sizes = [sizes]
        if isinstance(first_type_errors, float):
            first_type_errors = [first_type_errors]
        if isinstance(second_type_errors, float):
            second_type_errors = [second_type_errors]
        if isinstance(metrics, types.MetricNameType):
            metrics = [metrics]

        arguments_choice: types._PrepareArgumentsType = {
            "df": (self.__df, dataframe),
            "alpha": (self.__alpha, first_type_errors),
            "metric": (self.__metrics, metrics),
            "method": (self.__method, method),
        }

        designable_parameters: List[str] = [SIZE, EFFECT, POWER]
        if to_design == SIZE:
            arguments_choice[EFFECT] = (self.__effect, effects)
            arguments_choice["beta"] = (self.__beta, second_type_errors)
            chosen_args: types._UsageArgumentsType = Designer._prepare_arguments(arguments_choice)
            return Designer.__pre_design(SIZE, chosen_args, **kwargs)
        elif to_design == EFFECT:
            arguments_choice[SIZE] = (self.__size, sizes)
            arguments_choice["beta"] = (self.__beta, second_type_errors)
            chosen_args: types._UsageArgumentsType = Designer._prepare_arguments(arguments_choice)
            return Designer.__pre_design(EFFECT, chosen_args, **kwargs)
        elif to_design == POWER:
            arguments_choice[SIZE] = (self.__size, sizes)
            arguments_choice[EFFECT] = (self.__effect, effects)
            chosen_args: types._UsageArgumentsType = Designer._prepare_arguments(arguments_choice)
            return Designer.__pre_design(POWER, chosen_args, **kwargs)
        else:
            raise ValueError(f'Incorrect parameter name to design, choose from {", ".join(designable_parameters)}')

def load_from_config(yaml_config: str, loader: type = yaml.Loader) -> Designer:
    """
    Restore a ``Designer`` class instance from a yaml config.

    For yaml_config you can pass file name with config,
    it must ends with .yaml, for example: "config.yaml".

    For loader you can choose SafeLoader.
    """
    if isinstance(yaml_config, str) and yaml_config.endswith(".yaml"):
        with open(yaml_config, "r", encoding="utf8") as file:
            return yaml.load(file, Loader=loader)
    return yaml.load(yaml_config, Loader=loader)

def design(
    to_design,
    dataframe: types.PassedDataType,
    metrics: types.MetricNamesType,
    sizes: types.SampleSizeType = None,
    effects: types.EffectType = None,
    first_type_errors: types.StatErrorType = (0.05,),
    second_type_errors: types.StatErrorType = (0.2,),
    method: str = "theory",
    **kwargs,
) -> types.DesignerResult:
    """
    Function wrapper around the ``Designer`` class.

    Make experiment design based on historical data using passed arguments.

    Creates an instance of the ``Designer`` class internally and execute
    run method with corresponding arguments.

    Parameters
    ----------
    to_design : str
        Parameter that will be designed using historical data.
        Can take the values of ``"size"``, ``"effect"`` or ``"power"``.
    dataframe : PassedDataType
        DataFrame with metrics historical values.
    metrics : MetricNamesType
        Column names of metrics in dataframe to be designed.
    sizes : SampleSizeType, optional
        Values of research objects number in groups samples during
        the experiment.
        If is not provided, ``effects`` value must be defined.
    effects : EffectType, optional
        Effects for experiment
        If is not provided, ``sizes`` value must be defined.
    first_type_errors : StatErrorType, default: ``(0.05,)``
        I type error bounds
        P (detect difference for equal) < alpha.
    second_type_errors : StatErrorType, default: ``(0.2,)``
        II type error bounds
        P (suppose equality for different groups) < beta.
    method : str, default: ``"theory"``
        Method used for experiment design.
        Can be ``"theory"``, ``"empiric"`` or ``"binary"``.
    **kwargs : Dict
        Other keyword arguments.

    Other Parameters
    ----------------
    as_numeric : bool, default: ``False``
        The result of calculations can be obtained as a percentage string
        either as a number, this parameter could used to toggle.
    groups_ratio : float, default: ``1.0``
        Ratio between two groups.
    alternative : str, default: ``"two-sided"``
        Alternative hypothesis, can be ``"two-sided"``, ``"greater"``
        or ``"less"``.
        ``"greater"`` - if effect is positive.
        ``"less"`` - if effect is negative.
    stabilizing_method : str, default: ``"asin"``
        Effect trasformation. Can be ``"asin"`` and ``"norm"``.
        For non-binary metrics: only ``"norm"`` is accceptable.
        For binary metrics: ``"norm"`` and ``"asin"``, but ``"asin"``
        is more robust and accurate.
        Acceptable only for ``"theory"`` method and actual for binary metrics!

    Returns
    -------
    result : DesignerResult
        Table or dictionary with the results of parameter design for each
        metric.
    """
    return Designer(
        dataframe=dataframe,
        metrics=metrics,
        first_type_errors=first_type_errors,
        second_type_errors=second_type_errors,
        sizes=sizes,
        effects=effects,
        method=method,
    ).run(to_design, **kwargs)

def design_binary_size(
    prob_a: float,
    effects: types.EffectType,
    first_type_errors: types.StatErrorType = (0.05,),
    second_type_errors: types.StatErrorType = (0.2,),
    method: str = "theory",
    groups_ratio: float = 1.0,
    alternative: str = "two-sided",
    stabilizing_method: str = "asin",
    **kwargs,
) -> pd.DataFrame:
    """
    Design size for binary metrics.

    Parameters
    ----------
    prob_a : float
        Probability of success for the control group.
    effects : EffectType
        List or single value of relative effects.
        For example: ``1.05``, ``[1.05, 1.2]``.
    first_type_errors : StatErrorType, default: ``(0.05,)``
       I type error bounds
       P (detect difference for equal) < alpha.
    second_type_errors : StatErrorType, default: ``(0.2,)``
       II type error bounds
       P (suppose equality for different groups) < beta.
    method : str, default: ``"theory"``
        Supports 2 methods: ``"theory"`` and ``"binary"``
        ``"theory"`` ~ by formula using statsmodels solve_power mechanism
        ``"binary"`` ~ using different types of intervals
    groups_ratio : float, default: ``1.0``
        Ratio between two groups.
    alternative : str, default: ``"two-sided"``
        Alternative hypothesis, can be ``"two-sided"``, ``"greater"``
        or ``"less"``.
        ``"greater"`` - if effect is positive.
        ``"less"`` - if effect is negative.
    stabilizing_method : str, default: ``"asin"``
        Effect trasformation. Can be ``"asin"`` and ``"norm"``.
        For non-binary metrics: only ``"norm"`` is accceptable.
        For binary metrics: ``"norm"`` and ``"asin"``, but ``"asin"``
        is more robust and accurate.
    **kwargs : Dict
        Other keyword arguments.

    Returns
    -------
    result_table : pd.DataFrame
        Table with results of design.
    """
    if isinstance(effects, (float, int)):
        effects = [effects]
    if isinstance(first_type_errors, float):
        first_type_errors = [first_type_errors]
    if isinstance(second_type_errors, float):
        second_type_errors = [second_type_errors]
    if method == "theory":
        return theory_pkg.get_table_sample_size(
            mean=prob_a,
            std=None,
            effects=effects,
            first_errors=first_type_errors,
            second_errors=second_type_errors,
            target_type="binary",
            groups_ratio=groups_ratio,
            alternative=alternative,
            stabilizing_method=stabilizing_method,
        )
    elif method == "binary":
        return bin_pkg.get_table_sample_size_on_effect(
            p_a=prob_a,
            first_errors=first_type_errors,
            second_errors=second_type_errors,
            delta_relative_values=effects,
            **kwargs,
        )
    else:
        raise ValueError(f"Choose valid method from {BINARY_DESIGN_METHODS}, got {method}")

def design_binary_effect(
    prob_a: float,
    sizes: types.SampleSizeType,
    first_type_errors: types.StatErrorType = (0.05,),
    second_type_errors: types.StatErrorType = (0.2,),
    method: str = "theory",
    groups_ratio: float = 1.0,
    alternative: str = "two-sided",
    stabilizing_method: str = "asin",
    as_numeric: bool = False,
    **kwargs,
) -> pd.DataFrame:
    """
    Design effect for binary metrics.

    Parameters
    ----------
    prob_a : float
         Probability of success for the control group.
    sizes : SampleSizeType
        List or single value of group sizes.
        For example: ``100``, ``[100, 200]``.
    first_type_errors : StatErrorType, default: ``(0.05,)``
       I type error bounds
       P (detect difference for equal) < alpha.
    second_type_errors : StatErrorType, default: ``(0.2,)``
       II type error bounds
       P (suppose equality for different groups) < beta.
    method: str, default: ``"theory"``
        Supports 2 methods: ``"theory"`` and ``"binary"``
        ``"theory"`` ~ by formula using statsmodels solve_power mechanism
        ``"binary"`` ~ using different types of intervals
    groups_ratio : float, default: ``1.0``
        Ratio between two groups.
    alternative : str, default: ``"two-sided"``
        Alternative hypothesis, can be ``"two-sided"``, ``"greater"``
        or ``"less"``.
        ``"greater"`` - if effect is positive.
        ``"less"`` - if effect is negative.
    stabilizing_method : str, default: ``"asin"``
        Effect trasformation. Can be ``"asin"`` and ``"norm"``.
        For non-binary metrics: only ``"norm"`` is accceptable.
        For binary metrics: ``"norm"`` and ``"asin"``, but ``"asin"``
        is more robust and accurate.
    as_numeric : bool, default: ``False``
        The result of calculations can be obtained as a percentage string
        either as a number, this parameter could used to toggle.
    **kwargs : Dict
        Other keyword arguments.

    Returns
    -------
    result_table : pd.DataFrame
        Table with results of design.
    """
    if isinstance(sizes, int):
        sizes = [sizes]
    if isinstance(first_type_errors, float):
        first_type_errors = [first_type_errors]
    if isinstance(second_type_errors, float):
        second_type_errors = [second_type_errors]
    if method == "theory":
        return theory_pkg.get_minimal_effects_table(
            mean=prob_a,
            std=None,
            sample_sizes=sizes,
            first_errors=first_type_errors,
            second_errors=second_type_errors,
            as_numeric=as_numeric,
            target_type="binary",
            groups_ratio=groups_ratio,
            alternative=alternative,
            stabilizing_method=stabilizing_method,
        )
    elif method == "binary":
        return bin_pkg.get_table_effect_on_sample_size(
            p_a=prob_a,
            sample_sizes=sizes,
            first_errors=first_type_errors,
            second_errors=second_type_errors,
            as_numeric=as_numeric,
            **kwargs,
        )
    else:
        raise ValueError(f"Choose valid method from {BINARY_DESIGN_METHODS}, got {method}")

def design_binary_power(
    prob_a: float,
    sizes: types.SampleSizeType,
    effects: types.EffectType,
    first_type_errors: types.StatErrorType = (0.05,),
    method: str = "theory",
    groups_ratio: float = 1.0,
    alternative: str = "two-sided",
    stabilizing_method: str = "asin",
    as_numeric: bool = False,
    **kwargs,
) -> pd.DataFrame:
    """
    Design power for binary metrics.

    Parameters
    ----------
    prob_a : float
       Probability of success for the control group.
    sizes : SampleSizeType
        List of single value of group sizes.
        For example: ``100``, ``[100, 200]``.
    effects : EffectType
        List or single value of relative effects.
        For example: ``1.05``, ``[1.05, 1.2]``.
    first_type_errors : StatErrorType, default: ``(0.05,)``
       I type error bounds
       P (detect difference for equal) < alpha.
    method: str, default: ``"theory"``
        Supports 2 methods: ``"theory"`` and ``"binary"``
        ``"theory"`` ~ by formula using statsmodels solve_power mechanism
        ``"binary"`` ~ using different types of intervals
    groups_ratio : float, default: ``1.0``
        Ratio between two groups.
    alternative : str, default: ``"two-sided"``
        Alternative hypothesis, can be ``"two-sided"``, ``"greater"``
        or ``"less"``.
        ``"greater"`` - if effect is positive.
        ``"less"`` - if effect is negative.
    stabilizing_method : str, default: ``"asin"``
        Effect trasformation. Can be ``"asin"`` and ``"norm"``.
        For non-binary metrics: only ``"norm"`` is accceptable.
        For binary metrics: ``"norm"`` and ``"asin"``, but ``"asin"``
        is more robust and accurate.
    as_numeric : bool, default: ``False``
        The result of calculations can be obtained as a percentage string
        either as a number, this parameter could used to toggle.
    **kwargs : Dict
        Other keyword arguments.

    Returns
    -------
    result_table : pd.DataFrame
        Table with results of design.
    """
    if isinstance(effects, (int, float)):
        effects = [effects]
    if isinstance(sizes, int):
        sizes = [sizes]
    if isinstance(first_type_errors, float):
        first_type_errors = [first_type_errors]
    if method == "theory":
        return theory_pkg.get_power_table(
            mean=prob_a,
            std=None,
            sample_sizes=sizes,
            effects=effects,
            first_errors=first_type_errors,
            as_numeric=as_numeric,
            target_type="binary",
            groups_ratio=groups_ratio,
            alternative=alternative,
            stabilizing_method=stabilizing_method,
        )
    elif method == "binary":
        return bin_pkg.get_table_power_on_size_and_delta(
            p_a=prob_a,
            sample_sizes=sizes,
            first_errors=first_type_errors,
            delta_relative_values=effects,
            as_numeric=as_numeric,
            **kwargs,
        )
    else:
        raise ValueError(f"Choose valid method from {BINARY_DESIGN_METHODS}, got {method}")

def design_binary(
    to_design: str,
    prob_a: float,
    sizes: Optional[types.SampleSizeType] = None,
    effects: Optional[types.EffectType] = None,
    first_type_errors: types.StatErrorType = (0.05,),
    second_type_errors: types.StatErrorType = (0.2,),
    method: str = "theory",
    groups_ratio: float = 1.0,
    alternative: str = "two-sided",
    stabilizing_method: str = "asin",
    **kwargs,
) -> pd.DataFrame:
    """
    Design of experiment parameters for binary metrics based
    on a known conversion value.

    Parameters
    ----------
    to_design : str
        Parameter to design.
    prob_a : float
        Probability of success for the control group.
    sizes : SampleSizeType, optional
        List or single value of group sizes.
        For example: ``100``, ``[100, 200]``.
    effects : EffectType, optional
        List of single value of relative effects.
        For example: 1.05, [1.05, 1.2].
    first_type_errors : StatErrorType, default: ``(0.05, )``
        I type error bounds
        P (detect difference for equal) < alpha.
    second_type_errors : StatErrorType, default: ``(0.2,)``
        II type error bounds
        P (suppose equality for different groups) < beta.
    method: str, default: ``"theory"``
        Supports 2 methods: ``"theory"`` and ``"binary"``
        ``"theory"`` ~ by formula using statsmodels solve_power mechanism
        ``"binary"`` ~ using different types of intervals
    groups_ratio : float, default: ``1.0``
        Ratio between two groups.
    alternative : str, default: ``"two-sided"``
        Alternative hypothesis, can be ``"two-sided"``, ``"greater"``
        or ``"less"``.
        ``"greater"`` - if effect is positive.
        ``"less"`` - if effect is negative.
    stabilizing_method : str, default: ``"asin"``
        Effect trasformation. Can be ``"asin"`` and ``"norm"``.
        For non-binary metrics: only ``"norm"`` is accceptable.
        For binary metrics: ``"norm"`` and ``"asin"``, but ``"asin"``
        is more robust and accurate.
    **kwargs : Dict
        Other keyword arguments.

    Returns
    -------
    result_table : pd.DataFrame
        Table with results of design.
    """
    if to_design == SIZE:
        return design_binary_size(
            prob_a,
            effects,
            first_type_errors,
            second_type_errors,
            method,
            groups_ratio,
            alternative,
            stabilizing_method,
            **kwargs,
        )
    elif to_design == EFFECT:
        return design_binary_effect(
            prob_a,
            sizes,
            first_type_errors,
            second_type_errors,
            method,
            groups_ratio,
            alternative,
            stabilizing_method,
            **kwargs,
        )
    elif to_design == POWER:
        return design_binary_power(
            prob_a, sizes, effects, first_type_errors, method, groups_ratio, alternative, stabilizing_method, **kwargs
        )
    else:
        raise ValueError(f"Only {SIZE}, {EFFECT} and {POWER} parameters of the binary experiment could be designed.")


import base64
import datetime
import hashlib
import hmac
import logging
import time

import six

LOG = logging.getLogger(__name__)

def hash_it(data, hash_type="sha256"):
    """Returns hashed value of data."""
    if hash_type == "sha1":
        hash_obj = hashlib.sha1()
    elif hash_type == "md5":
        hash_obj = hashlib.md5()
    else:
        hash_obj = hashlib.sha256()
    try:
        hash_obj.update(data.encode())
        return hash_obj.hexdigest()
    except (TypeError, AttributeError) as e:
        LOG.error("Couldn't hash the data, exception raised: %s", e)
        return hash(data)

def hmac_it(data, key, hash_type="sha256"):
    """Returns HMAC based on the hash algorithm, data and key."""
    if hash_type == "md5":
        hash_obj = hashlib.md5
    elif hash_type == "sha1":
        hash_obj = hashlib.sha1
    else:
        hash_obj = hashlib.sha256
    try:
        h_digest = hmac.new(key.encode(), data.encode(), hash_obj)
        return h_digest.hexdigest()
    except (TypeError, AttributeError) as e:
        LOG.error("Couldn't hash the data, exception raised: %s", e)

def epoch_time(offset=0):
    """Returns time since epoch."""
    try:
        return time.time() - offset
    except TypeError as e:
        LOG.error("Couldn't reduce offset, %s, from epoch time, ex %s.",
                  offset, e)
        return time.time()

def utc_datetime():
    """Returns utc date time."""
    epoch = epoch_time()
    ts = datetime.datetime.fromtimestamp(epoch).strftime("%Y-%m-%d %H:%M:%S")
    return ts

def base64_encode(data):
    """Returns base 64 encoded value of data."""
    try:
        data = base64.b64encode(data.encode())
    except TypeError as e:
        LOG.error("Couldn't encode data to base64: %s", e)
    return data

def url_encode(url):
    """Returns encoded URL."""
    try:
        return six.moves.urllib.parse.quote_plus(url)
    except TypeError as e:
        LOG.error("Couldn't encode the URL: %s", e)
        return url


import re

from state import State
from kik.messages.responses import TextResponse
from lib.message_types import MessageType

order = 1

class KeywordResponder(object):
    def __init__(self, fn, keyword, *alternates):
        global order
        self.fn = fn
        self.keyword = keyword

        # Create a regexp that has each of the tests for these words in a group with word boundaries. Drop all word-
        # boundary characters from the matchable list.
        words = [keyword] + list(alternates)
        escaped_words = (re.escape(w) for w in words)
        self.regexp = re.compile(unicode(r'(^|\s|\b)({})($|\s|\b)').format('|'.join(escaped_words)), flags=re.I)
        self.order = order
        order += 1

    def __call__(self, *args, **kwargs):
        return self.fn(*args, **kwargs)

    def match(self, body):
        if not body:
            return False
        return self.regexp.search(body)

def keyword_response(keyword, *alternates):
    def wrap(func):
        return KeywordResponder(func, keyword, *alternates)

    return wrap

class KeywordState(State):
    @staticmethod
    def type():
        raise NotImplementedError('KeywordState is an abstract class and should not be directly used')

    def __init__(self, *args, **kwargs):
        super(KeywordState, self).__init__(*args, **kwargs)
        self.keyword_responses = []
        self.hide_keyboard = False

        # Have to climb the inheritance hierarchy for the instance to make sure we get all keyword responses.
        inspect_class = self.__class__
        while not inspect_class == object:
            for x, y in inspect_class.__dict__.items():
                if isinstance(y, KeywordResponder):
                    self.keyword_responses.append(y)
            inspect_class = inspect_class.__bases__[0]

        self.keyword_responses.sort(key=lambda sr: sr.order)
        if len(self.keyword_responses) <= 20:
            self.suggested_responses = [TextResponse(body=kw.keyword)for kw in self.keyword_responses]
        else:
            self.suggested_responses = None

    def on_message(self, message):
        transition = None
        for sr in self.keyword_responses:
            if message.type == MessageType.TEXT and sr.match(message.body):
                transition = sr(self, message)
                break
        if not transition:
            transition = self.handle_unmatched(message)
        return transition

    def handle_unmatched(self, message):
        """
        Method that subclasses must implement in order to respond to messages that do not match the state's suggested
        responses list.
        :param message: The incoming message.
        :return: A Transition entity
        """
        raise NotImplementedError('KeywordState must return a Transition entity from its `handle_unmatched` method')


from django.urls import path

from .views import GameListView, GameDetailView, ReviewCreateViewSet, GameCreateView, \
    GameDeleteOrUpdateView

urlpatterns = [
    path('game/', GameListView.as_view({'get': 'list'})),
    path('game/<int:pk>/', GameDetailView.as_view({'get': 'retrieve'})),
    path('review/', ReviewCreateViewSet.as_view({'post': 'create'})),
    path('game/create/',  GameCreateView.as_view({'post': 'create'})),
    path('game/<int:pk>/edit-delete/', GameDeleteOrUpdateView.as_view(), name='game-edit-delete'),
]


from typing import Optional

from pydantic import BaseModel, EmailStr, Field
class ItemReward(BaseModel):
    name: str = Field(...)
    amount: int = Field(...)
    rate: int = Field(...)

    class Config:
        schema_extra = {
            "example": {
                "name": "Cheetos",
                "amount": 50,
                "rate": 2,
            }
        }

def ResponseModel(data, message):
    return {
        "data": [data],
        "code": 200,
        "message": message,
    }

def ErrorResponseModel(error, code, message):
    return {"error": error, "code": code, "message": message}


from tkinter import *
from PIL import Image,ImageTk
from random import choice

myFrame = Tk()

myFrame.title("Rock Paper Scissors")
myFrame.configure(background= "SteelBlue1")

# pictures
rockImage = ImageTk.PhotoImage(Image.open("imgs/rockUser.png"))
paperImage = ImageTk.PhotoImage(Image.open("imgs/paperUser.png"))
scissorsImage = ImageTk.PhotoImage(Image.open("imgs/scissorsUser.png"))
rockImageComp = ImageTk.PhotoImage(Image.open("imgs/rockComp.png"))
paperImageComp = ImageTk.PhotoImage(Image.open("imgs/paperComp.png"))
scissorsImageComp = ImageTk.PhotoImage(Image.open("imgs/scissorsComp.png"))
# insert pictures
userLabel = Label(myFrame,image=scissorsImage,bg="SteelBlue1")
compLabel = Label(myFrame,image=scissorsImageComp,bg="SteelBlue1")
userLabel.grid(row=1,column=4)
compLabel.grid(row=1,column=0)
# scors
playerScore = Label(myFrame,text=0,font=100,bg="SteelBlue1",fg="white")
compScore = Label(myFrame,text=0,font=100,bg="SteelBlue1",fg="white")

playerScore.grid(row=1,column=3)
compScore.grid(row=1,column=1)

# indicators

userIndicator = Label(myFrame,font=50,text="USER",bg="SteelBlue1",fg="white").grid(row=0,column=3)
compIndicator = Label(myFrame,font=50,text="COMPUTER",bg="SteelBlue1",fg="white").grid(row=0,column=1)

message = Label(myFrame,font=50,bg="SteelBlue1",fg="white")
message.grid(row=3,column=2)

# update message
def updateMessage(msg):
    message['text'] = msg

def updateUserScore():
    score = int(playerScore["text"])
    score+=1
    playerScore['text'] = str(score)

def updateComputerScore():
    score = int(compScore["text"])
    score+=1
    compScore['text'] = str(score)

def checkWiner(player,computer):
    if player == computer:
        updateMessage("it's a tie")
    elif player == 'rock':
        if computer == 'paper':
            updateMessage('You Loose')
            updateComputerScore()
        else:
            updateMessage('You Win')
            updateUserScore()
    elif player == 'paper':
        if computer == 'scissors':
            updateMessage('You Loose')
            updateComputerScore()
        else:
            updateMessage('You Win')
            updateUserScore()
    elif player == 'scissors':#scissors
        if computer == 'rock':
            updateMessage('You Loose')
            updateComputerScore()
        else:
            updateMessage('You Win')
            updateUserScore()
    else:
        pass

# set the choices
choices = ['rock','paper','scissors']
# update choices
def updateChoices(click):
    # for computer
    compChoice = choice(choices)
    if compChoice == 'rock':
        compLabel.configure(image= rockImageComp)
    elif compChoice == "paper":
        compLabel.configure(image= paperImageComp)
    else:
        compLabel.configure(image= scissorsImageComp)
    # for user
    if click == 'rock':
        userLabel.configure(image= rockImage)
    elif click == "paper":
        userLabel.configure(image= paperImage)
    else:
        userLabel.configure(image= scissorsImage)

    checkWiner(click,compChoice)

# button
rock = Button(myFrame,width=20,height=2,text="ROCK",bg="#ff3e4d",fg="white",command=lambda:updateChoices("rock")).grid(row=2,column=1)
paper = Button(myFrame,width=20,height=2,text="PAPER",bg="#ff3e4d",fg="white",command=lambda:updateChoices("paper")).grid(row=2,column=2)
scissors = Button(myFrame,width=20,height=2,text="SCISSORS",bg="#ff3e4d",fg="white",command=lambda:updateChoices("scissors")).grid(row=2,column=3)

myFrame.mainloop()


from abc import ABC, abstractmethod
from itertools import islice
import numpy as np
from ..operators import apply_changes

class AcceptanceCondition(ABC):
    @abstractmethod
    def acceptable(self, solution, changes, delta):
        raise NotImplementedError

    @abstractmethod
    def accept(self, solution, changes, delta):
        raise NotImplementedError

    @abstractmethod
    def reject(self):
        raise NotImplementedError

def local_search(solution, _evaluate, partial_evaluate,
                 neighbourhood,
                 acceptance_condition,
                 report_period=None,
                 idle_threshold=None,
                 explore_size=30,
                 min_iters=200,
                 max_iters=1000):
    """Implements a general tabu search heuritstic
    that is independent of the specific TabuList
    """
    if idle_threshold is None:
        idle_threshold = 1

    # the solution is modified in place to get new solutions
    # make copy as to not alter the original solution
    current_solution = np.copy(solution)
    current_delta = 0

    best_solution = current_solution
    best_delta = 0

    i = 0
    idle = 0

    while (not (i > min_iters and idle > idle_threshold*i)) and i < max_iters:
        neighbours = ((changes,
                       partial_evaluate(current_solution, changes))
                      for changes in neighbourhood(current_solution))

        acceptable = ((changes, delta)
                      for changes, delta in islice(neighbours, explore_size)
                      if acceptance_condition.acceptable(current_solution,
                                                         changes,
                                                         delta))

        best_neighbour = min(acceptable,
                             key=lambda neighbour: neighbour[1],
                             default=None)

        if best_neighbour is not None:
            # accept the best neighbour
            changes, delta = best_neighbour
            if delta < 0:
                idle = 0
            else:
                idle += 1

            acceptance_condition.accept(current_solution, changes, delta)
            apply_changes(current_solution, changes)
            current_delta += delta

            # save the best solution
            if current_delta < best_delta:
                best_solution = np.copy(current_solution)
                best_delta = current_delta
        else:
            idle += 1
            acceptance_condition.reject()

        if report_period is not None and (i+1) % report_period == 0:
            print(f"{i+1}\t\t{-current_delta}")

        i += 1

    return best_solution


from matplotlib import pyplot as plt
import matplotlib
import numpy as np
import warnings
import sys

from _common import *

def create_icmap_example_plot(seed=73323):
    import rescomp as rc

    # fix the seed for consistency
    np.random.seed(seed)
    # ignore a FutureWarning in rescomp
    warnings.filterwarnings('ignore', category=FutureWarning, module='networkx')

    # Generate a system trajectory from the Lorenz attractor
    t, U = rc.orbit('lorenz', trim=True, duration=30)
    num_pts=300
    tr_t = t[1000:1000+num_pts]
    tr_signal = U[1000:1000+num_pts]

    # Create a reservoir computer
    res_sz = 50
    rescomp = rc.ResComp(res_sz=res_sz, mean_degree=0.1, map_initial='activ_f')
    r0 = rescomp.initial_condition(tr_signal[0])
    res_signal = rescomp.internal_state_response(tr_t, tr_signal, r0)

    try:
        # figure time
        fig = plt.figure(figsize=(12,6))
        ax_sys = fig.add_subplot(1,2,1,projection='3d')
        ax_res = fig.add_subplot(1,2,2)

        # This one is for arrows, &c
        ax_overlay = fig.add_subplot(1,1,1)
        ax_overlay.axis([0,1,0,1])
        ax_overlay.axis('off')

        # Plot the trajectory
        train_split = num_pts - 10
        ax_sys.plot(U[:,0], U[:,1], U[:,2], '-', alpha=0.7, color=(0.0, 0.3, 0.8))
        train_color = (0.3, 0.3, 0.1)
        ax_sys.plot(tr_signal[:train_split,0], tr_signal[:train_split,1], tr_signal[:train_split,2], '-', color=train_color)
        ax_sys.plot(tr_signal[train_split:,0], tr_signal[train_split:,1], tr_signal[train_split:,2], '--', color=train_color)
        ax_sys.plot(tr_signal[0,0], tr_signal[0,1], tr_signal[0,2], 'k*', markersize=12.)

        clear_3d_axis_labels(ax_sys)
        #ax_sys.axis('off')

        # Plot reservoir states
        order = np.argsort(res_signal[0])
        for i,c in enumerate(plt.get_cmap('plasma')(np.linspace(0,0.8,res_sz))):
            ax_res.plot(tr_t, res_signal[:, order[i]], '-', color=c, alpha=0.8)
        ax_res.plot([tr_t[0]]*res_sz, res_signal[0], 'k.')

        ax_res.spines['top'].set_visible(False)
        ax_res.spines['right'].set_visible(False)
        ax_res.spines['bottom'].set_position('zero')
        ax_res.set_xticks([])
        ax_res.set_yticks([-1,0,1])

        # Label for initial conditions
        textparams = {
            'fontsize': 16,
            'horizontalalignment': 'center',
        }
        ax_sys.text(tr_signal[0,0], tr_signal[0,1], tr_signal[0,2]+5, r"$\mathbf{u}_0$", **textparams)
        ax_res.text(tr_t[0], 1.0, r"$\mathbf{r}_0$", **textparams)

        bounding_box = ax_res.get_position()
        height = 0.5
        bounding_box.y0 = 0.5 - height/2
        bounding_box.y1 = 0.5 + height/2
        ax_res.set_position(bounding_box)

        # temporary; show bounds of the overlay subplot
        #ax_overlay.plot([0,0,1,1,0],[0,1,1,0,0], 'k:')
        #ax_overlay.plot([0,1],[1,0], 'k:')
        #ax_overlay.plot([0,1],[0,1], 'k:')

        # Arrow showing mappings
        path_t = np.linspace(-1,1,3)
        path_x = 0.5 + path_t * 0.15
        path_y = 0.15*(1-path_t**2)
        codes = [
            matplotlib.path.Path.MOVETO
        ] + [
            matplotlib.path.Path.CURVE3
        ] * (len(path_t) - 1)

        path1 = matplotlib.path.Path(np.column_stack((path_x, 0.85+path_y)), codes=codes)
        path2 = matplotlib.path.Path(np.column_stack((path_x, 0.18-path_y)), codes=codes)

        ax_overlay.add_patch(matplotlib.patches.FancyArrowPatch(path=path1, arrowstyle="->", mutation_scale=20.))
        ax_overlay.add_patch(matplotlib.patches.FancyArrowPatch(path=path2, arrowstyle="<-", mutation_scale=20.))
        textparams = {
            'fontsize': 18,
            'horizontalalignment': 'center',
            'transform': ax_overlay.transAxes,
        }
        ax_overlay.text(0.5, 0.95, r'$\Phi$', **textparams)
        ax_overlay.text(0.5, 0.05, r'$W_{\mathrm{out}}\approx \Phi^{-1}$', **textparams)

        plt.show()
    except Exception as e:
        # cleanup
        plt.close('all')
        raise


# -*- coding: utf-8 -*-
"""
Created on Mon Apr 16 13:12:45 2018

@author: Luna
"""

#Лоренц Джинни

import numpy as np
import matplotlib.pyplot as plt
N = 10
x = np.arange(0,N+1,1)
x = x/N
y = np.array( [2,2,2,2,2,2,5,5,9,   13])
y2 = np.array([1,1,1,1,1,1,1,1,13.5,13.5])
y3 = np.array([0.1,0.3,0.3,2.5,2.9,4.2,4,5,6,   6])

def cum(y):
    y_max = y.sum()
    a = 0
    y_cum = np.zeros(N+1)

    for (y_next,i) in zip(y, np.arange(1,N+1)):
        a = a + y_next
        y_cum[i]= a/y_max
    return y_cum
#Gini=[]
#Gini.append(1 - np.sum((X_k - X_k_1) * (Y_k + Y_k_1)))

def ginny(y):

    h = x[1]
    S= h*y[N]/2 + h*y[1:N].sum()

    return (0.5-S)/0.5

def ginny2(x,y):

    S = 0
    for i in np.arange(len(x)-1):
        dx = x[i+1]-x[i]
        s = 0.5*dx*(y[i+1]-y[i])+(y[i]*dx)
        S +=s

    return (0.5-S)/0.5

plt.plot(x, cum(y), label ="1, {:.3f}".format(ginny(cum(y))))
#plt.plot(x, cum(y2),label = "1, {}".format(ginny(cum(y2))))
plt.plot(x, cum(y3),label = "1, {:.3f}".format(ginny(cum(y3))))
plt.plot(x,x)
plt.legend(loc = 'best')

print("Ginny",ginny2(x, cum(y3)))


from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.multiclass import OneVsRestClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import recall_score, f1_score, precision_score
from merger import train_bl as train, valid_bl as valid, test_bl as test, y_train, y_valid, y_test

vectorizer = TfidfVectorizer(max_features=2000)
x_train = vectorizer.fit_transform(train).toarray()
x_valid = vectorizer.fit_transform(valid).toarray()
x_test = vectorizer.fit_transform(test).toarray()

x_train.shape

lr_td_idf = OneVsRestClassifier(estimator=LogisticRegression()).fit(x_train, y_train)

preds_valid_td_idf = lr_td_idf.predict(x_valid)
#Accuracy
print(recall_score(y_valid, preds_valid_td_idf, average='weighted'))
#Recall
print(recall_score(y_valid.T, preds_valid_td_idf.T, average='macro'))
#Precision
print(precision_score(y_valid.T, preds_valid_td_idf.T, average='macro'))
#F1
print(f1_score(y_valid.T, preds_valid_td_idf.T, average='macro'))

preds_test_td_idf = lr_td_idf.predict(x_test)
#Accuracy
print(recall_score(y_test, preds_test_td_idf, average='weighted'))
#Recall
print(recall_score(y_test.T, preds_test_td_idf.T, average='macro'))
#Precision
print(precision_score(y_test.T, preds_test_td_idf.T, average='macro'))
#F1
print(f1_score(y_test.T, preds_test_td_idf.T, average='macro'))


import matplotlib.pyplot as plt
import pandas as pd
import spacy
import streamlit as st
import string

from joblib import dump, load
from spacy.lang.pt.stop_words import STOP_WORDS

from sklearn.feature_extraction.text import CountVectorizer
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.naive_bayes import MultinomialNB

from tweepy import API
from tweepy import Cursor
from tweepy import OAuthHandler

# File storing the user credentials to access the Twitter API.
import my_twitter_credentials

VOCABULARY_FILENAME = 'better_learned_vocabulary.joblib'
MODEL_FILENAME = 'better_fitted_multinomial_nb.joblib'

###############################################################################
# Code related to Tweepy.
###############################################################################
class TwitterAuthenticator():
    """
    Class for authenticating our requests to Twitter.
    """
    def authenticate_app(self):
        # The keys are unique identifiers that authenticate the app.
        auth = OAuthHandler(my_twitter_credentials.CONSUMER_KEY,
                            my_twitter_credentials.CONSUMER_KEY_SECRET)
        # The tokens allow the app to gain specific access to Twitter data.
        auth.set_access_token(my_twitter_credentials.ACCESS_TOKEN,
                              my_twitter_credentials.ACCESS_TOKEN_SECRET)
        return auth

class TwitterClient():
    """
    Class for browsing through tweets via the Twitter API.
    """
    def __init__(self):
        self.auth = TwitterAuthenticator().authenticate_app()
        self.api = API(self.auth)

    def search_tweet(self, query_string):
        cursor = Cursor(self.api.search, q=query_string, lang='pt', count=5)
        return cursor.items(5)
###############################################################################

###############################################################################
# Code related to spaCy.
###############################################################################
nlp = spacy.load('pt_core_news_sm')
STOP_WORDS_LIST = list(STOP_WORDS)
PUNCTUATION_CHARACTERS_LIST = list(string.punctuation)

# Tokenizer used by the CountVectorizer.
def spacy_tokenizer(tweet):
    # Tokenizes tweet and lemmatizes the tokens.
    # It also gets rid of stop words and punctuation characters.
    doc = nlp(tweet)
    tokens = [token.lemma_ for token in doc]
    tokens = [token for token in tokens
              if token not in STOP_WORDS_LIST
              and token not in PUNCTUATION_CHARACTERS_LIST]
    return tokens
###############################################################################

###############################################################################
# Execution code.
###############################################################################
def get_training_data():
    df = pd.read_csv('50k.csv', sep=';')
    X = df['tweet_text']
    y = df['sentiment']
    return df, train_test_split(X, y, test_size=0.01, random_state=17)

def execute_pipeline(X_train, X_test, y_train, y_test):
    loaded = False
    try:
        vectorizer = load(VOCABULARY_FILENAME)
        classifier = load(MODEL_FILENAME)
        pipeline = Pipeline([('vectorizer', vectorizer),
                             ('classifier', classifier)], verbose=True)
        loaded = True
    except OSError:
        # The parameter to the tokenizer is a callable.
        vectorizer = CountVectorizer(tokenizer=spacy_tokenizer)
        classifier = MultinomialNB()
        # Pipeline for cleaning, vectorizing and classifying tweets.
        pipeline = Pipeline([('vectorizer', vectorizer),
                             ('classifier', classifier)], verbose=True)
        status = st.text('[STATUS] Fitting model...')
        pipeline.fit(X_train, y_train)
        status.text('[STATUS] Model was fit.')
        dump(vectorizer, VOCABULARY_FILENAME)
        dump(classifier, MODEL_FILENAME)
    return loaded, pipeline

def plot_chart(input_df):
    input_df['tweet_date'] = pd.to_datetime(input_df['tweet_date'])
    input_df.sort_values(by=['tweet_date'], inplace=True)

    positive_df = input_df[input_df['sentiment'] == 1]
    positive_df['month'] = positive_df['tweet_date'].map(lambda d: d.month)
    positive_df['day'] = positive_df['tweet_date'].map(lambda d: d.day)
    agg_positive_df = positive_df.groupby(['month', 'day']).count()
    positive_counts = agg_positive_df['id'].tolist()

    negative_df = input_df[input_df['sentiment'] == 0]
    negative_df['month'] = negative_df['tweet_date'].map(lambda d: d.month)
    negative_df['day'] = negative_df['tweet_date'].map(lambda d: d.day)
    agg_negative_df = negative_df.groupby(['month', 'day']).count()
    negative_counts = agg_negative_df['id'].tolist()

    x_labels = pd.date_range(start='09/28/2018', end='10/12/2018')
    x_labels = [str(d).split()[0] for d in x_labels.to_series().tolist()]

    positive_dates = [
        '2018-09-28', '2018-09-30', '2018-10-01',
        '2018-10-02', '2018-10-03', '2018-10-05',
        '2018-10-06', '2018-10-08', '2018-10-12'
    ]
    negative_dates = [
        '2018-10-01', '2018-10-02', '2018-10-08'
    ]

    positive_y_values = [0] * len(x_labels)
    negative_y_values = [0] * len(x_labels)
    for i in range(len(x_labels)):
        if x_labels[i] in positive_dates:
            positive_y_values[i] = positive_counts.pop(0)
        if x_labels[i] in negative_dates:
            negative_y_values[i] = negative_counts.pop(0)

    plt_fig, ax = plt.subplots()
    ax.plot(range(len(x_labels)), positive_y_values,
            label='Positive', color='#4287f5')
    ax.plot(range(len(x_labels)), negative_y_values,
            label='Negative', color='#f54242')
    ax.set_title('Tweet sentiment evolution of training set')
    ax.set_xlabel('Day')
    ax.set_xticks(range(len(x_labels)))
    ax.set_xticklabels(x_labels, rotation='vertical')
    ax.set_ylabel('Number of tweets')
    ax.legend()
    st.pyplot(fig=plt_fig)

if __name__ == '__main__':
    st.title('Twitter App')
    client = TwitterClient()

    df, (X_train, X_test, Y_train, Y_test) = get_training_data()
    loaded, pipeline = execute_pipeline(X_train, X_test, Y_train, Y_test)
    if loaded:
        st.write('Vocabulary and model successfully loaded.')
    st.text(f'Model accuracy on test data: {pipeline.score(X_test, Y_test)}')

    theme_options = ('Weather', 'Politics')
    theme = st.radio("Pick your theme to search for a tweet.", theme_options)
    if theme == 'Weather':
        keywords = 'calor OR quente OR aquecimento OR queimadas'
    else:
        keywords = 'bolsonaro OR mito OR lula OR pt OR trump'

    # Shows tweets with predictions.
    count = 0
    tweets = client.search_tweet(keywords)
    for tweet in tweets:
        area_key = 'a' + str(count)
        st.text_area('Fetched tweet', value=tweet.text, key=area_key)
        count += 1

        prediction = pipeline.predict([tweet.text])
        if prediction[0] == 0:
            st.info('Prediction: negative')
        else:
            st.info('Prediction: positive')

    # Shows plot.
    plot_chart(df)
###############################################################################


import logging

class LoggingFramework:
    """ Yara Learner Logging Framework """
    def __init__(self):
        self.logger = logging.getLogger('yLogger')
        self.logger.setLevel(logging.INFO)
        formatter = logging.Formatter("[%(levelname)s|%(filename)s:%(lineno)s] %(asctime)s >> %(message)s")

        fHandle = logging.FileHandler("yaralearn.log")
        sHandle = logging.StreamHandler()

        fHandle.setFormatter(formatter)
        sHandle.setFormatter(formatter)

        self.logger.addHandler(fHandle)
        self.logger.addHandler(sHandle)

    def __del__(self):
        del self


""" HOSPITAL MODEL CREATION """
from scipy.stats import truncnorm
from classes import Localization, TypeLocalization
import config

# Function that initialize a new hospital
# er_nbeds = nº beds in the ER
# icu_nbeds = nº beds in the ICU
# nwards = nº of wards
# wards_nrooms = nº of rooms in each ward
# room_nbeds = nº of beds in each ward room
# sx_nrooms = nº of operation rooms
# rx_nrooms = nº of radiology rooms
# rx_nbeds = nº of beds in each radiology room
def initialize_hospital(er_nbeds, icu_nbeds, nwards, wards_nrooms, sx_nrooms, rx_nrooms, room_nbeds, rx_nbeds):
    # ER, ICU
    er = Localization(0, TypeLocalization.ER)
    icu = Localization(1, TypeLocalization.ICU)
    L = [er, icu]

    # WARDS
    index = 2
    wards = []
    for i in range(nwards):
        ward = Localization(index, TypeLocalization.Ward)
        wards.append(ward)
        L.append(ward)
        index = index + 1

    # OPERATING ROOMS
    for i in range(sx_nrooms):
        surgery = Localization(index, TypeLocalization.Surgery)
        L.append(surgery)
        index = index + 1

    # RADIOLOGY ROOMS
    rxs = []
    for i in range(rx_nrooms):
        rx = Localization(index, TypeLocalization.Radiology)
        rxs.append(rx)
        L.append(rx)
        index = index + 1

    # ER AND ICU BEDS
    beds_er, index = create_beds(er_nbeds, index, er, 0)
    L = L + beds_er
    beds_icu, index = create_beds(icu_nbeds, index, icu, 1)
    L = L + beds_icu

    # WARDS ROOMS AND BEDS
    for i in range(nwards):
        rooms, beds, index = create_rooms(wards_nrooms[i], room_nbeds, index, wards[i])
        L = L + rooms
        L = L + beds

    # RADIOLOGY ROOMS BEDS
    for i in range(rx_nrooms):
        beds, index = create_beds(rx_nbeds, index, rxs[i], 0)
        L = L + beds

    return L

# Function that returns new nbeds beds following the id index, located in located_in and in the floor floor
def create_beds(nbeds, index, located_in, floor):
    beds = []
    for i in range(nbeds):
        beds.append(Localization(index, TypeLocalization.Bed, located_in))
        index = index + 1
    for b in beds:
        b.adjacent = [bed for bed in beds if bed is not b]
    located_in.children = beds
    return beds, index

# Function that returns new nrooms rooms with room_nbeds beds following the id index, located in located_in
def create_rooms(nrooms, room_nbeds, index, located_in):
    rooms = []
    beds = []
    for i in range(nrooms):
        r = Localization(index, TypeLocalization.Room, located_in)
        rooms.append(r)
        index = index + 1
        bs, index = create_beds(room_nbeds, index, r, 1)
        beds.extend(bs)

    located_in.children = rooms
    return rooms, beds, index

# Function that returns the daily hospital occupancy rate following a normal distribution
def normal_distr_occupancy_rate():
    max_beds = round(config.hosp_occupancy_rate / config.required_parameters['n_patients'])
    mean = config.hosp_occupancy_rate
    sd = 30
    tn = truncnorm((0 - mean) / sd, (max_beds - mean) / sd, loc=mean, scale=sd)
    return int(tn.rvs())


import itertools

test_input_1 = [
    [[-6, 0, 0], [3, 0, 0], [0, 0, 0]],
    [[-4, 0, 0], [2, 0, 0], [0, 0, 0]],
    [[-2, 0, 0], [1, 0, 0], [0, 0, 0]],
    [[3, 0, 0], [-1, 0, 0], [0, 0, 0]]
]

def parse_input():
    with open('data.txt') as file:
        lines = file.read().split('\n')

    input_data = []

    for line in lines:
        particle = []
        for parameter in line.split(', '):
            particle.append(list(map(int, parameter[3:-1].split(","))))
        input_data.append(particle)

    return input_data

def simulate(particles, expected_confidence):
    confidence = 0
    current_count = 0

    while confidence < expected_confidence:
        # update the velocities and positions
        for particle in particles:
            particle[1][0] += particle[2][0]
            particle[1][1] += particle[2][1]
            particle[1][2] += particle[2][2]
            particle[0][0] += particle[1][0]
            particle[0][1] += particle[1][1]
            particle[0][2] += particle[1][2]

        # mark collided particles for deletion
        to_delete = []
        for particle_1, particle_2 in itertools.combinations(particles, 2):
            if particle_1[0] == particle_2[0]:
                to_delete.append(particle_1)
                to_delete.append(particle_2)

        # delete particles that collided
        for particle in to_delete:
            try:
                particles.remove(particle)
            except ValueError:
                pass

        # count the particles
        new_count = len(particles)

        # if particle count didn't change in this step, increase the confidence
        # otherwise change the particle count and reset the confidence
        if new_count == current_count:
            confidence += 1
        else:
            current_count = new_count
            confidence = 0

    return current_count

def main():
    test_1 = simulate(test_input_1, 10)
    print("test_1:", test_1)

    answer = simulate(parse_input(), 10)
    print("answer:", answer)

if __name__ == "__main__":
    main()


import heapq
from collections import defaultdict

class PriorityQuene:
    def __init__(self):
        self.heap = []
        self.count = defaultdict(int)

    def push(self, x):
        heapq.heappush(self.heap, x)
        self.count[x] += 1

    def pop(self):
        res = self.top()
        self.remove(res)
        return res

    def top(self):
        return self.heap[0]

    def remove(self, x):
        if self.count[x] == 0:
            return
        self.count[x] -= 1
        while self.heap:
            if self.count[self.top()]:
                break
            heapq.heappop(self.heap)

    def __bool__(self):
        return bool(self.heap)

n = int(input())
int0 = lambda x: int(x) - 1
(*a,) = map(int0, input().split())
(*b,) = map(int0, input().split())
(*p,) = map(int0, input().split())
if any(a[i] <= b[p[i]] and i != p[i] for i in range(n)):
    print(-1)
    exit()
q = [None] * n
for i in range(n):
    q[p[i]] = i
heap = PriorityQuene()
for i in range(n):
    heap.push((a[i], i))
ans = []
while heap:
    _, i = heap.pop()
    j = i
    while q[j] != i:
        ans.append(j)
        j = q[j]
        heap.remove((a[j], j))
print(len(ans))
for j in ans:
    print(j + 1, q[j] + 1)


import logging
import os
import shutil
from time import strftime
from time import time
from typing import Dict, Tuple, Type

import numpy as np
import tensorflow as tf
from sklearn.metrics import roc_auc_score
from sklearn.metrics import average_precision_score

from .samplers import Sampler
from .utils import initialize_logger
from .utils import load_model_from_state_dict
from .utils import PerformanceMetrics

logger = logging.getLogger("skylar")

def _metrics_logger(name: str, out_filepath: str) -> logging:
    my_logger = logging.getLogger("{0}".format(name))
    my_logger.setLevel(logging.INFO)
    formatter = logging.Formatter("%(message)s")
    file_handle = logging.FileHandler(os.path.join(out_filepath, "{0}.txt".format(name)))
    file_handle.setFormatter(formatter)
    my_logger.addHandler(file_handle)
    return my_logger

class TrainModel(object):
    def __init__(self,
                 model: tf.Module,
                 data_sampler: Sampler,
                 loss_criterion: tf.keras.losses.Loss,
                 optimizer_class: Type[tf.keras.optimizers.Optimizer],  # currently tfa.optimizers.SGDW
                 optimizer_kwargs: Dict,
                 batch_size: int,
                 max_steps: int,
                 report_stats_every_n_steps: int,
                 output_dir: str,
                 save_checkpoint_every_n_steps: int = 1000,
                 save_new_checkpoints_after_n_steps: int = None,
                 report_gt_feature_n_positives: int = 10,
                 n_validation_samples: int = None,
                 n_test_samples: int = None,
                 use_cuda: bool = False,
                 data_parallel: bool = False,
                 logging_verbosity: int = 2,
                 checkpoint_resume: str = None,
                 use_scheduler: bool = True):
        self._test_data = self._all_test_targets = self.step = self._time_per_step = \
            self._train_loss = self._min_loss = None
        self.model = model
        self.sampler = data_sampler
        self.criterion = loss_criterion
        self._use_scheduler = use_scheduler
        step = tf.Variable(0, trainable=False)  # ==> torch.no_grad()
        schedule = tf.optimizers.schedules.PiecewiseConstantDecay([10000, 15000], [1e-0, 1e-1, 1e-2])
        weight_decay = optimizer_kwargs["weight_decay"]
        optimizer_kwargs.pop("weight_decay")
        weight_decay = lambda: weight_decay * schedule(step)
        self.optimizer = optimizer_class(weight_decay, **optimizer_kwargs)  # self.model.variables

        self.batch_size = batch_size
        self.max_steps = max_steps
        self.nth_step_report_stats = report_stats_every_n_steps
        self.nth_step_save_checkpoint = None

        if not save_checkpoint_every_n_steps:
            self.nth_step_save_checkpoint = report_stats_every_n_steps
        else:
            self.nth_step_save_checkpoint = save_checkpoint_every_n_steps

        self._save_new_checkpoints = save_new_checkpoints_after_n_steps

        logger.info("Training parameters set: batch size {0}, "
                    "number of steps per 'epoch': {1}, "
                    "maximum number of steps: {2}".format(self.batch_size,
                                                          self.nth_step_report_stats,
                                                          self.max_steps))

        self.use_cuda = use_cuda
        self.data_parallel = data_parallel

        if self.data_parallel:
            self.model = DataParallel(model)
            logger.debug("Wrapped model in DataParallel")

        if self.use_cuda:
            self.model.cuda()
            self.criterion.cuda()
            logger.debug("Set modules to use CUDA")

        os.makedirs(output_dir, exist_ok=True)
        self.output_dir = output_dir

        initialize_logger(
            os.path.join(self.output_dir, "{0}.log".format(__name__)),
            verbosity=logging_verbosity)

        self._report_gt_feature_n_positives = report_gt_feature_n_positives
        self._metrics = dict(roc_auc=roc_auc_score, average_precision=average_precision_score)
        self._n_validation_samples = n_validation_samples
        self._n_test_samples = n_test_samples

        self._init_train()
        self._init_validate()
        if "test" in self.sampler.modes:
            self._init_test()
        if checkpoint_resume is not None:
            self._load_checkpoint(checkpoint_resume)

    def _load_checkpoint(self, checkpoint_resume: str) -> None:
        checkpoint = tf.saved_model.load(checkpoint_resume, map_location=lambda storage, location: storage)
        if "state_dict" not in checkpoint:
            raise ValueError("'state_dict' not found in file {0} ".format(checkpoint_resume))

        self.model = load_model_from_state_dict(checkpoint["state_dict"], self.model)

        self._start_step = checkpoint["step"]
        if self._start_step >= self.max_steps:
            self.max_steps += self._start_step

        self._min_loss = checkpoint["min_loss"]
        self.optimizer.load_state_dict(checkpoint["optimizer"])
        if self.use_cuda:
            for state in self.optimizer.state.values():
                for k, v in state.items():
                    if isinstance(v, tf.Tensor):
                        state[k] = v.cuda()

        logger.info("Resuming from checkpoint: step {0}, min loss {1}".format(self._start_step, self._min_loss))

    def _init_train(self) -> None:
        self._start_step = 0
        self._train_logger = _metrics_logger("{0}.train".format(__name__), self.output_dir)
        self._train_logger.info("loss")
        # if self._use_scheduler:
        #    self.scheduler = tf.keras.optimizers.schedules.LearningRateSchedule(
        #        self.optimizer, patience=16, verbose=True, factor=0.8)
        self._time_per_step = []
        self._train_loss = []

    def _init_validate(self) -> None:
        self._min_loss = float("inf")
        self._create_validation_set(n_samples=self._n_validation_samples)
        self._validation_metrics = PerformanceMetrics(
            self.sampler.get_feature_from_index,
            report_gt_feature_n_positives=self._report_gt_feature_n_positives,
            metrics=self._metrics)
        self._validation_logger = _metrics_logger("{0}.validation".format(__name__), self.output_dir)

        self._validation_logger.info(
            "\t".join(["loss"] + sorted([x for x in self._validation_metrics.metrics.keys()])))

    def _init_test(self) -> None:
        self._n_test_samples = self._n_test_samples
        self._test_metrics = PerformanceMetrics(
            self.sampler.get_feature_from_index,
            report_gt_feature_n_positives=self._report_gt_feature_n_positives,
            metrics=self._metrics)

    def _create_validation_set(self, n_samples: int = None) -> None:
        logger.info("Creating validation dataset.")
        t_i = time()
        self._validation_data, self._all_validation_targets = \
            self.sampler.get_validation_set(self.batch_size, n_samples=n_samples)
        t_f = time()
        logger.info(("{0} s to load {1} validation examples ({2} validation "
                     "batches) to evaluate after each training step.").format(
            t_f - t_i,
            len(self._validation_data) * self.batch_size,
            len(self._validation_data)))

    def create_test_set(self) -> None:
        logger.info("Creating test dataset.")
        t_i = time()
        self._test_data, self._all_test_targets = \
            self.sampler.get_test_set(self.batch_size, n_samples=self._n_test_samples)
        t_f = time()
        logger.info(("{0} s to load {1} test examples ({2} test batches) "
                     "to evaluate after all training steps.").format(
            t_f - t_i,
            len(self._test_data) * self.batch_size,
            len(self._test_data)))
        np.savez_compressed(
            os.path.join(self.output_dir, "test_targets.npz"),
            data=self._all_test_targets)

    def _get_batch(self) -> Tuple:
        t_i_sampling = time()
        batch_sequences, batch_targets = self.sampler.sample(batch_size=self.batch_size)
        t_f_sampling = time()
        logger.debug("[BATCH] Time to sample {0} examples: {1} s.".format(
            self.batch_size, t_f_sampling - t_i_sampling))
        return batch_sequences, batch_targets

    def _checkpoint(self) -> None:
        checkpoint_dict = {
            "step": self.step,
            "arch": self.model.__class__.__name__,
            "state_dict": self.model.state_dict(),
            "min_loss": self._min_loss,
            "optimizer": self.optimizer.state_dict()
        }
        if self._save_new_checkpoints is not None and self._save_new_checkpoints >= self.step:
            checkpoint_filename = "checkpoint-{0}".format(strftime("%m%d%H%M%S"))
            self._save_checkpoint(checkpoint_dict, False, filename=checkpoint_filename)
            logger.debug("Saving checkpoint `{0}.pb.tar`".format(checkpoint_filename))
        else:
            self._save_checkpoint(checkpoint_dict, False)

    def train_and_validate(self) -> None:
        for step in range(self._start_step, self.max_steps):
            self.step = step
            self.train()
            if step % self.nth_step_save_checkpoint == 0:
                self._checkpoint()
            if self.step and self.step % self.nth_step_report_stats == 0:
                self.validate()
        self.sampler.save_dataset_to_file("train", close_filehandle=True)

    def train(self) -> None:
        t_i = time()
        # self.model.train()
        self.sampler.set_mode("train")

        inputs, targets = self._get_batch()
        inputs = tf.constant(inputs)
        targets = tf.constant(targets)

        if self.use_cuda:
            inputs = inputs.cuda()
            targets = targets.cuda()

        inputs = tf.Variable(inputs)
        targets = tf.Variable(targets)

        predictions = self.model(tf.transpose(inputs, perm=[1, 2, 0]))
        loss = self.criterion(predictions, targets)

        self.optimizer.zero_grad()
        loss.backward()
        self.optimizer.step()
        self._train_loss.append(loss.item())
        t_f = time()

        self._time_per_step.append(t_f - t_i)
        if self.step and self.step % self.nth_step_report_stats == 0:
            logger.info("[STEP {0}] average number of steps per second: {1:.1f}".format(
                self.step, 1. / np.average(self._time_per_step)))
            self._train_logger.info(np.average(self._train_loss))
            logger.info("training loss: {0}".format(np.average(self._train_loss)))
            self._time_per_step = []
            self._train_loss = []

    def _evaluate_on_data(self, data_in_batches: Dict) -> Tuple:
        self.model.eval()

        batch_losses = list()
        all_predictions = list()

        for inputs, targets in data_in_batches:
            inputs = tf.constant(inputs)
            targets = tf.constant(targets)

            if self.use_cuda:
                inputs = inputs.cuda()
                targets = targets.cuda()

            inputs = tf.Variable(inputs, trainable=False)
            targets = tf.Variable(targets, trainable=False)

            predictions = self.model.forward(tf.transpose(inputs, perm=[1, 2, 0]))
            loss = self.criterion(predictions, targets)

            all_predictions.append(predictions.data.cpu().numpy())

            batch_losses.append(loss.item())
        all_predictions = np.vstack(all_predictions)
        return np.average(batch_losses), all_predictions

    def validate(self) -> None:
        validation_loss, all_predictions = self._evaluate_on_data(self._validation_data)
        valid_scores = self._validation_metrics.update(all_predictions, self._all_validation_targets)
        for name, score in valid_scores.items():
            logger.info("validation {0}: {1}".format(name, score))

        valid_scores["loss"] = validation_loss

        to_log = [str(validation_loss)]
        for k in sorted(self._validation_metrics.metrics.keys()):
            if k in valid_scores and valid_scores[k]:
                to_log.append(str(valid_scores[k]))
            else:
                to_log.append("NA")
        self._validation_logger.info("\t".join(to_log))

        # scheduler update
        # if self._use_scheduler:
        #    self.scheduler.step(math.ceil(validation_loss * 1000.0) / 1000.0)

        # save best_model
        if validation_loss < self._min_loss:
            self._min_loss = validation_loss
            self._save_checkpoint({
                "step": self.step,
                "arch": self.model.__class__.__name__,
                "state_dict": self.model.state_dict(),
                "min_loss": self._min_loss,
                "optimizer": self.optimizer.state_dict()}, True)
            logger.debug("Updating `best_model.pb.tar`")
        logger.info("validation loss: {0}".format(validation_loss))

    def evaluate(self) -> Tuple:
        if self._test_data is None: self.create_test_set()
        average_loss, all_predictions = self._evaluate_on_data(self._test_data)
        average_scores = self._test_metrics.update(all_predictions, self._all_test_targets)
        np.savez_compressed(
            os.path.join(self.output_dir, "test_predictions.npz"),
            data=all_predictions)
        for name, score in average_scores.items():
            logger.info("test {0}: {1}".format(name, score))
        test_performance = os.path.join(self.output_dir, "test_performance.txt")
        feature_scores_dict = self._test_metrics.write_feature_scores_to_file(test_performance)
        average_scores["loss"] = average_loss
        self._test_metrics.visualize(all_predictions, self._all_test_targets, self.output_dir)
        return average_scores, feature_scores_dict

    def _save_checkpoint(self, state: Dict, is_best: bool, filename: str = "checkpoint") -> None:
        logger.debug("[TRAIN] {0}: Saving model state to file.".format(state["step"]))
        cp_filepath = os.path.join(self.output_dir, filename)
        tf.saved_model.save(state, "{0}.pb.tar".format(cp_filepath))
        if is_best:
            best_filepath = os.path.join(self.output_dir, "best_model")
            shutil.copyfile("{0}.pb.tar".format(cp_filepath), "{0}.pb.tar".format(best_filepath))


#%% INPUT
wdir  = r"E:\Tulips\DEM"
files = ["1","2","3"]

#%% PACKAGES AND FUNCTIONS
import rasterio
import gdal
import cv2
import numpy as np
import numpy.matlib
import matplotlib.pyplot as plt
from scipy import ndimage
import os

def calculate_slope(DEM):
        gdal.DEMProcessing('slope.tif', DEM, 'slope', alg = 'ZevenbergenThorne', scale = 500000)
        with rasterio.open('slope.tif') as dataset:
            slope=dataset.read(1)
        return slope

def odd(f):
        return np.ceil(f / 2.) * 2 + 1

def raster_calc(slope,p,u):
    out = np.ones(slope.shape)
    hist = np.histogram(slope[slope>0],bins=90,density=True)
    for h in range(10,len(hist[0])):
        if hist[0][h] <= p:
            t1=h
            break
    t2=u
    for h in range(u,len(hist[0])-1):
        if hist[0][h+1] > hist[0][h]:
            t2=h
    out[slope>t2] = 0
    out[slope<t1] = 0
    return out

#%% CREATE PATH
path = []
for x in range(len(files)):
    path.append(wdir+"\\"+files[x]+".tif")

#%% LOAD ARRAY AND SLOPE, SLOPE MANIPULATION, GAUSSIAN FILTER, SAVING IMAGE
for i in range(len(path)):
    file = gdal.Open(path[i])
    band = file.GetRasterBand(1)
    exec("gt"+str(i)+" = file.GetGeoTransform()")
    exec("array"+str(i)+" = band.ReadAsArray()")
    exec("slope"+str(i)+" = calculate_slope(path[i])")
    exec("slope"+str(i)+"_p = raster_calc(slope"+str(i)+",1/2000,75)")
    exec("slope"+str(i)+"_pp = ndimage.gaussian_filter(slope"+str(i)+"_p, sigma=10, order=0)")

    plt.clf()
    exec("fig = plt.imshow(slope"+str(i)+"_pp, cmap='hot', interpolation='nearest')")
    plt.axis('off')
    fig.axes.get_xaxis().set_visible(False)
    fig.axes.get_yaxis().set_visible(False)
    plt.savefig(wdir+"\\temp"+str(i)+".png", bbox_inches='tight', pad_inches = 0)

#%% HARRIS CORNER DETECTION
for i in range(len(path)):
    exec("size_x = len(slope"+str(i)+"[:,0])")
    exec("size_y = len(slope"+str(i)+"[0,:])")
    bsize  = int(odd(min(size_x,size_y)/200))
    ksize  = int(min(31,odd(min(size_x,size_y)/1000)))
    target = 4
    thresh = 0.01
    k      = 0.01
    for j in range(0,500):
        img = cv2.imread(wdir+"\\temp"+str(i)+".png")
        gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
        gray = np.float32(gray)
        dst = cv2.cornerHarris(gray,bsize,ksize,k)
        ret, dst = cv2.threshold(dst,0.1*dst.max(),255,0)
        dst = np.uint8(dst)
        ret, labels, stats, centroids = cv2.connectedComponentsWithStats(dst)
        criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 200, 0.001)
        corners = cv2.cornerSubPix(gray,np.float32(centroids),(20,20),(-1,-1),criteria)
        if len(corners) == target+1:
            break
        elif len(corners) <= target:
            thresh = thresh+0.01
            k=k+0.0001
        elif len(corners) >= target+2:
            thresh = thresh-0.01
            k=k+0.0001
    fact = size_x/len(img)
    file = gdal.Open(path[i])
    gt = file.GetGeoTransform()
    corn = corners * fact
    exec("corn"+str(i)+" = corn[1:,:]")
    exec("coor"+str(i)+" = np.matrix([gt[0] + gt[1]*corn[:,0] + gt[2]*corn[:,1],gt[3] + gt[4]*corn[:,0] + gt[5]*corn[:,1]]).transpose()")
    print('Image nr '+str(i)+' processed after '+str(j)+' alternations. Found '+str(len(corners)-1)+' corners.')

#%% GEOREFERENCE THE POINTS
for i in range(len(path)):
    if i >= 1:
        exec("coordinates = coor"+str(i))
        os.system("gdal_translate -of GTiff -gcp " + str(coordinates[0,0]) +" "+ str(coordinates[0,1]) +" "+ str(coor0[0,0]) +" "+ str(coor0[0,1])
        +" -gcp " + str(coordinates[1,0]) +" "+ str(coordinates[1,1]) +" "+ str(coor0[1,0]) +" "+ str(coor0[1,1])
        +" -gcp " + str(coordinates[2,0]) +" "+ str(coordinates[2,1]) +" "+ str(coor0[2,0]) +" "+ str(coor0[2,1])
        +" -gcp " + str(coordinates[3,0]) +" "+ str(coordinates[3,1]) +" "+ str(coor0[3,0]) +" "+ str(coor0[3,1])
        +" \""+path[i]+"\" \""+wdir+"\\temp"+files[i]+".tif\"")
        os.system("gdalwarp -r near -tps -co COMPRESS=NONE \""+wdir+"\\temp.tif\" \""+wdir+"\\"+files[i]+"_adjusted.tif\"")

#%% PLOT WHATEVER YOU WANT
check = corn2
array = array2
plt.imshow(array, cmap='hot', interpolation='nearest')
for l in range(len(check)):
    plt.scatter(check[l,0],check[l,1])


'''
Functions to get execution_tree from klee and to draw tree
'''
from __future__ import division
import struct
import matplotlib.pyplot as plt

# funkcija koja cita mapu iz symPaths.ts fajla:

def get_paths(path):
    '''
    Get tree from binary filed provided from klee
    :param path: to binary file
    :return: klee tree paths
    '''
    data = open(path, 'rb').read()  # Otvaramo binarni fajl za citanje
    paths = {0: ''}  # Mapa u kojoj cemo cuvati putanje
    pos = 0  # Indikator dokle smo stigli sa citanjem
    # prolazak kroz fajl i popunjavanje mape
    while pos < len(data):
        identificator, tag = struct.unpack('II', data[pos:pos+8])
        pos += 8
        if tag & (1 << 31):
            child = tag ^ (1 << 31)
            paths[child] = paths[identificator]
        else:
            size = tag
            paths[identificator] += data[pos:pos+size]
            pos += size
    if pos != len(data):
        raise IOError
    return paths

# funkcija koja cita strukturu stabla iz liste:

# [(0, ''), (1, '1'), (2, '0')]
# Stablo:
#  /\

# [(0, ''), (1, '11'), (2, '01'), (3, '10'), (4, '00')]
# Stablo:
#  /\
# /\/\

# [(0, ''), (1, '11'), (2, '0'), (3, '10')]
# Stablo:
#  /\
#   /\

def get_tree(tree_list):
    '''
    Get tree from list
    :param tree_list:
    :return:
    '''
    root = Node(0)
    k = 1
    for i in range(1, len(tree_list)):
        branch = (tree_list[i])[1]
        tmp = root
        for j in range(0, len(branch)):
            if branch[j] == '1':
                if not tmp.left:
                    tmp.left = Node(k)
                    tmp.leave = False
                    k = k+1
                tmp = tmp.left
            elif branch[j] == '0':
                if not tmp.right:
                    tmp.right = Node(k)
                    tmp.leave = False
                    k = k+1
                tmp = tmp.right
    return root

# funkcija dobija strukturu stabla i iscrtava ga

def draw_tree(file_name, execution_tree, leaves, limit=None):
    '''
    Draw tree from structure
    :param execution_tree:
    :param leaves:
    :param limit:
    :return:
    '''
    def text(x_coordinate, y_coordinate, t_arg, size=6, **kwargs):
        '''
        Fill tree nodes with text
        :param x_coordinate:
        :param y_coordinate:
        :param t_arg:
        :param size:
        :param kwargs:
        :return:
        '''
        plt.text(x_coordinate, y_coordinate, t_arg, ha='center', va='center', size=size,
                 bbox=dict(boxstyle='round', ec='k', fc='w'), **kwargs)

    def draw_lines(tree, x_coordinate, y_coordinate, d_x, d_y, limitation=None):
        '''
        Draw branches and nodes of execution tree
        :param tree:
        :param x_coordinate:
        :param y_coordinate:
        :param d_x:
        :param d_y:
        :param limitation:
        :return:
        '''
        if limitation is not None and limitation == 0:
            text(x_coordinate, y_coordinate, "...", alpha=0.4)
            return
        ind = False
        d_x = d_x * 0.5
        d_y = d_y * 1.2
        if tree.left:
            ind = True
            text(x_coordinate-d_x/2, y_coordinate-d_y/2, "True", alpha=0.4)
            plt.plot([x_coordinate-d_x, x_coordinate], [y_coordinate-d_y, y_coordinate], '-k')
            if limitation is None:
                draw_lines(tree.left, x_coordinate-d_x, y_coordinate-d_y, d_x, d_y, limitation)
            else:
                draw_lines(tree.left, x_coordinate-d_x, y_coordinate-d_y, d_x, d_y, limitation-1)
        if tree.right:
            ind = True
            text(x_coordinate+d_x/2, y_coordinate-d_y/2, "False", alpha=0.4)
            plt.plot([x_coordinate, x_coordinate+d_x], [y_coordinate, y_coordinate-d_y], '-k')
            if limitation is None:
                draw_lines(tree.right, x_coordinate+d_x, y_coordinate-d_y, d_x, d_y, limitation)
            else:
                draw_lines(tree.right, x_coordinate+d_x, y_coordinate-d_y, d_x, d_y, limitation-1)
        if ind:
            text(x_coordinate, y_coordinate, tree.data, 12)  # condition
        else:
            text(x_coordinate, y_coordinate, tree.data, 6)  # "leave"

    x_coord = 0.5
    y_coord = 1
    d_x = 0.05*(2**(leaves-1))
    d_y = 0.1
    if limit is None:
        draw_lines(execution_tree, x_coord, y_coord, d_x, d_y)
    else:
        draw_lines(execution_tree, x_coord, y_coord, d_x, d_y, limit)
    plt.axis('off')
    manager = plt.get_current_fig_manager()
    manager.resize(*manager.window.maxsize())

    plt.savefig("./results/"+file_name[:-2] + ".png", bbox_inches='tight', format='png', dpi=150)
    #plt.show()

def fill_the_leaves(tree, cond):
    '''
    Fill leaves of the tree with summed conditions
    :param tree:
    :param cond:
    :return:
    '''
    ind = False
    if tree.left:
        ind = True
        if cond != "":
            fill_the_leaves(tree.left, cond + " && " + tree.data)
        else:
            fill_the_leaves(tree.left, tree.data)
    if tree.right:
        ind = True
        if cond != "":
            fill_the_leaves(tree.right, cond + " && !(" + tree.data + ")")
        else:
            fill_the_leaves(tree.right, "!(" + tree.data + ")")
    if not ind:
        tree.data = cond

# some useful data related to tree, and preorder tree traversal:
class Node:
    '''
    Node of the tree
    '''
    def __init__(self, data):
        '''
        Create Node of the tree
        :param data:
        '''
        self.left = None
        self.right = None
        self.data = data
        self.leave = True

#    def PreorderTraversal(self, root):
#        res = []
#        if root:
#            res.append(root.data)
#            res = res + self.PreorderTraversal(root.left)
#            res = res + self.PreorderTraversal(root.right)
#        return res

# the function PreorderTraversal give us conditions and leaves, but we need just conditions
# kasnije ce nam trebati i listovi...
# def formConditionNodes(tree, preorder):
#    ind = False
#    if(tree.left):
#        ind = True
#        formConditionNodes(tree.left, preorder)
#    if(tree.right):
#        ind = True
#        formConditionNodes(tree.right, preorder)
#    if(ind == False):
#        preorder.remove(tree.data)
#    return preorder

#############################################################################################
# Funkcija koja popunjava cvorove stabla

def join_tree_and_line_numbers(root, lines, map_lines):
    '''
    Merge tree with lines from source codes
    :param root:
    :param lines:
    :param map_lines:
    :return:
    '''
    def joinTALN(root, lines, n, cond):
        '''
        Helper function to merge tree with lines from source code
        :param root:
        :param lines:
        :param n:
        :param cond:
        :return:
        '''
        if not root.leave:
            x = 0
            for l in lines:
                if l > n:
                    x = l
                    break
            if not x == 0:
                root.data = map_lines[x]  # writing condition into a node
                lines.remove(x)
                if root.left:
                    if cond != "":
                        joinTALN(root.left, lines, x,
                                 cond + "\n&&\n" + root.data)
                    else:
                        joinTALN(root.left, lines, x, root.data)
                if root.right:
                    if cond != "":
                        joinTALN(root.right, lines, x, cond +
                                 "\n&&\n!(" + root.data + ")")
                    else:
                        joinTALN(root.right, lines, x, "!(" + root.data + ")")
            else:
                print("Some error....")
        if root.leave:
            root.data = cond

    joinTALN(root, lines, 0, "")


import asyncio
import logging
import re
from datetime import datetime
from typing import Dict, List

from aiohttp import ClientSession, ClientTimeout
from bs4 import BeautifulSoup

from structs import ScheduleForTheDay, Lesson

class SsauAPI:
    HEADERS = {
        'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:103.0) Gecko/20100101 Firefox/103.0'
    }
    HOST = 'https://ssau.ru'

    async def _request(self, url: str, tries_count=10) -> str:
        async with ClientSession(timeout=ClientTimeout(total=500), headers=self.HEADERS) as session:
            status = None
            while status != 200:
                if tries_count == 0:
                    raise ConnectionError('Не удалось получить данные')
                async with session.get(f'{self.HOST}{url}') as response:
                    status = response.status
                    logging.info(f'Запрос к {self.HOST}{url}. Статус {status}')
                    if status != 200:
                        await asyncio.sleep(.5)
                        tries_count -= 1
                        continue
                    return await response.text()

    async def get_institutes_and_faculties(self) -> Dict[str, int]:
        page = await self._request('/rasp')
        soup = BeautifulSoup(page, features='html.parser')
        return {item.a.text.strip(): int(re.search(r'\d+', item.a.attrs['href']).group())
                for item in soup.find_all('div', class_='faculties__item')}

    async def get_groups(self, faculty: int, course: int) -> Dict[str, int]:
        page = await self._request(f'/rasp/faculty/{faculty}?course={course}')
        soup = BeautifulSoup(page, features='html.parser')
        return {container.text.strip(): int(re.search(r'\d+', container.attrs['href']).group())
                for container in soup.find_all('a', class_='group-catalog__group')}

    async def get_timetable_by(self, group_id: int, week: int) -> List[ScheduleForTheDay]:
        page = await self._request(f'/rasp?groupId={group_id}&selectedWeek={week}')
        soup = BeautifulSoup(page, features='html.parser')
        raw_table = soup.find('div', class_='schedule__items')

        cells = iter(raw_table.find_all('div', class_='schedule__item'))
        time = [next(cells).text.strip()]
        table = [[] for _ in range(6)]
        for i, cell in enumerate(cells):
            table[i % 6].append(cell)

        time.extend([tuple(t.text.strip() for t in cell.find_all('div', class_='schedule__time-item'))
                     for cell in raw_table.find_all('div', class_='schedule__time')])

        timetable = []

        for day in table:
            day = iter(day)
            title = next(day)
            date_container = title.find('div', class_='schedule__head-date')
            date = datetime.strptime(date_container.text.strip(), '%d.%m.%Y').date()
            lessons = []
            for i, raw_lesson in enumerate(day, start=1):
                if not raw_lesson.text:
                    break
                start, end = time[i]
                place = raw_lesson.find('div', class_='schedule__place').text.strip()

                lessons.append(Lesson(
                    title=raw_lesson.find('div', class_='schedule__discipline').text.strip(),
                    is_online='online' in ''.join(filter(str.isalpha, place)).lower(),
                    place=place,
                    teacher=raw_lesson.find('div', class_='schedule__teacher').a.text.strip(),
                    groups=[c.text.strip() for c in
                            raw_lesson.find('div', class_='schedule__groups').find_all(class_='caption-text')],
                    start=start,
                    end=end
                ))
            timetable.append(ScheduleForTheDay(date=date,
                                               lessons=lessons))

        return timetable


from enum import Enum

from utils.base import Day

class Move(Enum):
    ROCK = 1
    PAPER = 2
    SCISSORS = 3

class Outcome(Enum):
    WIN = 1,
    LOSE = 2,
    DRAW = 3

class Day2(Day):

    def __init__(self, args):
        self.strategy_guide = list(map(lambda x: x, args[0]))
        self.rival_moves = {
            'A': Move.ROCK,
            'B': Move.PAPER,
            'C': Move.SCISSORS
        }

    def part1(self):
        player_moves = {
            'X': Move.ROCK,
            'Y': Move.PAPER,
            'Z': Move.SCISSORS
        }
        score = 0

        for entry in self.strategy_guide:
            player_move = (entry.split(' ')[1]).strip()
            rival_move = entry.split(' ')[0]
            score += Day2.get_player_score(player_moves[player_move], self.rival_moves[rival_move])

        return score

    def part2(self):
        outcome = {
            'X': Outcome.LOSE,
            'Y': Outcome.DRAW,
            'Z': Outcome.WIN
        }
        score = 0

        for entry in self.strategy_guide:
            desired_outcome = (entry.split(' ')[1]).strip()
            rival_move = entry.split(' ')[0]
            player_move = Day2.calculate_player_move(self.rival_moves[rival_move], outcome[desired_outcome])
            score += Day2.get_player_score(player_move, self.rival_moves[rival_move])

        return score

    @staticmethod
    def get_player_score(player_move, rival_move):
        if player_move.value == rival_move.value:
            return 3 + player_move.value
        elif player_move.value == rival_move.value % 3 + 1:
            return 6 + player_move.value
        else:
            return 0 + player_move.value

    @staticmethod
    def calculate_player_move(rival_move, outcome):
        if outcome == Outcome.DRAW:
            return rival_move
        elif outcome == Outcome.WIN:
            winning_move = rival_move.value % 3 + 1
            return Move(winning_move)
        else:
            losing_move = (rival_move.value % 3 - 2) % 3 + 1
            return Move(losing_move)