from typing import Tuple, List, Union
import numpy as np
import abc

import tensorflow as tf
from keras.layers import Dropout, InputSpec
from keras.backend import floatx

from ..utils import range_dims, convert_to_list
from .base_layer import UnivariateLayer
from ..asserts import greater_or_equal, equal_length, is_between
from ..constant import TENSOR_TYPE

class BaseBlock(UnivariateLayer):
    """
    Base class of a nbeats block.

    Your custom block needs to inherit from it.

    Parameters
    ----------
    label_width : int
        Horizon time to forecast.
    n_neurons : int
        Number of neurons in the fully connected layers.
    drop_rate : float
        Rate of the dropout layer.
        This is used to estimate the epistemic error.
        Expected a value between 0 and 1. Default to 0.
    name : str
        The name of the layer. It defines also the `block_type` attribute.

    Attributes
    ----------
    label_width : int
    input_width : int
    input_spec : `ÌnputSpec`
    drop_rate : float
    is_interpretable : bool
    is_g_trainable : bool
    block_type : str

    Raises
    ------
    ValueError
        If `name` doesn't contain `Block`.
        `drop_rate` is not between 0 and 1.
        All others arguments are not strictly positive integers.

    """

    NOT_INSPECT = ["call", "_output", "build"]

    def __init__(
        self,
        label_width: int,
        n_neurons: int,
        drop_rate: float,
        g_trainable: bool = False,
        interpretable: bool = False,
        block_type: str = "BaseBlock",
        **kwargs: dict,
    ):

        super().__init__(**kwargs)

        self._label_width = label_width
        self._drop_rate = drop_rate
        self._n_neurons = n_neurons
        self._is_g_trainable = g_trainable
        self._is_interpretable = interpretable
        self._block_type = block_type

    # TODO: put input_spec in UnivariateLayer
    def build(self, inputs_shape: tf.TensorShape) -> None:
        """See tensorflow documentation."""

        dtype = tf.as_dtype(self.dtype or tf.float32())
        if not (dtype.is_floating or dtype.is_complex):
            raise TypeError(
                f"Unable to build `{self.name}` layer with "
                "non-floating point dtype %s" % (dtype,)
            )

        inputs_shape = tf.TensorShape(inputs_shape)
        self._input_width = tf.compat.dimension_value(inputs_shape[-1])
        if self.input_width is None:
            raise ValueError(
                f"The last dimension of the inputs"
                f" should be defined. Found {self.input_width}."
            )

        self.input_spec = InputSpec(min_ndim=2, axes={-1: self.input_width})

        # Computing fc layers
        dim = self.input_width
        self.fc_stack = []
        for count in range(4):
            self.fc_stack.append(
                (
                    self.add_weight(
                        shape=self.n_variates + [dim, self._n_neurons],
                        name=f"fc_kernel_{self.name}_{count}",
                    ),
                    self.add_weight(
                        shape=self.n_variates + [1, self._n_neurons],
                        initializer="zeros",
                        name=f"fc_bias_{self.name}_{count}",
                    ),
                )
            )
            dim = self._n_neurons

        self.dropout = Dropout(self.drop_rate)

        self.fc_forecast, self.forecast_coef = self._build_branch(
            self.label_width, branch_name="forecast"
        )
        self.fc_backcast, self.backcast_coef = self._build_branch(
            self.input_width, branch_name="backcast"
        )

        super().build(inputs_shape)

    def _build_branch(self, output_last_dim: int, branch_name: str) -> None:
        """
        Build forecast and backcast branches.
        """

        coef = self.get_coefficients(output_last_dim, branch_name=branch_name)

        # If the model is compiling with a loss error defining uncertainty then
        # broadcast the output to take into account this uncertainty.
        shape_fc = [self._n_neurons, coef.shape[-2]]

        if branch_name == "forecast":
            # We place quantiles after multivariates
            shape_fc = self.get_additional_shapes(0) + shape_fc
        elif branch_name == "backcast":
            shape_fc = self.n_variates + shape_fc

        # If multivariate inputs then we modify the shape of fc layers
        fc = self.add_weight(shape=shape_fc, name=f"fc_{branch_name}_{self.name}")

        return fc, coef

    @abc.abstractmethod
    def get_coefficients(self, output_last_dim: int, branch_name: str) -> tf.Tensor:
        """
        Return the coefficients used in the forecast and backcast layer a.k.a g layer
        by calling coefficient_factory.
        This method needs to be overriden.

        Raises
        ------
        NotImplementedError
            If not overriden
        ValueError
            Raise an error if the coefficients tensor shape is not equal to
            (d0, ..., output_first_dim_forecast, label_width).
        """
        raise NotImplementedError(
            "When subclassing the `BaseBlock` class, you should "
            "implement a `_get_forecast_coefficients` method."
        )

    @abc.abstractmethod
    def coefficient_factory(self, *args: list, **kwargs: dict) -> tf.Tensor:
        """
        Create the coefficients used in the last layer a.k.a g constrained layer.
        This method needs to be overriden.

        Raises
        ------
        NotImplementedError
            If not overriden
        """
        raise NotImplementedError(
            "When subclassing the `BaseBlock` class, you should "
            "implement a `coefficient_factory` method."
        )

    def call(
        self, inputs: TENSOR_TYPE, **kwargs: dict
    ) -> TENSOR_TYPE:  # pylint: disable=arguments-differ
        """See tensorflow documentation."""

        for kernel, bias in self.fc_stack:
            # shape: (Batch_size, n_neurons)
            inputs = tf.add(tf.matmul(inputs, kernel), bias)
            inputs = tf.keras.activations.relu(inputs)
            # TODO: change dropout to let the user choose
            inputs = self.dropout(inputs, training=True)

        # shape: (Batch_size, backcast)
        reconstructed_inputs = self._output(
            inputs, self.fc_backcast, self.backcast_coef
        )  # layers fc and coef created in _build_branch

        # shape: (quantiles, Batch_size, forecast)
        outputs = self._output(
            inputs, self.fc_forecast, self.forecast_coef
        )  # layers fc and coef created in _build_branch

        return reconstructed_inputs, outputs

    def _output(
        self, inputs: tf.Tensor, fc: tf.Tensor, coef: tf.Tensor
    ) -> Tuple[tf.Tensor]:  # pylint: disable=arguments-differ
        """Call method."""

        # shape: (Batch_size, output_first_dim_backcast)
        theta = tf.matmul(inputs, fc)
        return tf.matmul(theta, coef)

    def compute_output_shape(
        self, inputs_shape: tf.TensorShape
    ) -> Tuple[tf.TensorShape]:
        """See tensorflow documentation."""

        inputs_shape = tf.TensorShape(inputs_shape)
        inputs_shape = inputs_shape.with_rank_at_least(2)
        last_dim_input = tf.compat.dimension_value(inputs_shape[-1])
        if last_dim_input is None:
            raise ValueError(
                "The innermost dimension of inputs_shape must be defined, but saw: %s"
                % (inputs_shape,)
            )

        output_shape_forecast = (
            [inputs_shape[0]] + self.get_additional_shapes(0) + [self.label_width]
        )

        return [
            inputs_shape,
            tf.TensorShape(output_shape_forecast),
        ]

    @property
    def label_width(self) -> int:
        """Return the label_width."""
        return self._label_width

    @property
    def input_width(self) -> int:
        """Return the input_width."""
        return self._input_width

    @property
    def drop_rate(self) -> float:
        """Return the drop rate."""
        return self._drop_rate

    @property
    def is_interpretable(self) -> bool:
        """Return True if the block is interpretable."""
        return self._is_interpretable

    @property
    def is_g_trainable(self) -> bool:
        """Return True if the last layer is trainable."""
        return self._is_g_trainable

    @property
    def block_type(self) -> str:
        """Return the block type. Default to `BaseBlock`."""
        return self._block_type

    def __repr__(self) -> str:
        """Return the representation."""
        return self._block_type

    def _val___init__(
        self, output: None, *args: list, **kwargs: dict
    ) -> None:  # pylint: disable=unused-argument

        is_between(self.drop_rate, 0, 1, "drop_rate")
        greater_or_equal(self.label_width, 0, "label_width")
        greater_or_equal(self._n_neurons, 0, "n_neurons")

        if "Block" not in self.block_type:
            raise ValueError(f"`name` has to contain `Block`. Got {self.name}")

class TrendBlock(BaseBlock):
    """
    Trend block definition.

    This layer represents the smaller part of the nbeats model.
    Final layers are constrained which define a polynomial function of small degree p.
    Therefore it is possible to get explanation from this block.

    Parameters
    ----------
    label_width : int
        Horizon time to forecast.
    p_degree : int | float
        Degree of the polynomial function.
    n_neurons : int
        Number of neurons in Fully connected layers.
    drop_rate : float
        Rate of the dropout layer. This is used to estimate the epistemic error.
        Expected a value between 0 and 1. Default to 0.

    Attributes
    ----------
    label_width : int
    input_width : int
    p_degree : int
    input_spec : `ÌnputSpec`
    drop_rate : float
    is_interpretable : bool
    is_g_trainable : bool
    block_type : str

    Raises
    ------
    ValueError
        `drop_rate` is not between 0 and 1.
        All others arguments are not strictly positive integers.

    Examples
    --------
    >>> from autopycoin.layers import TrendBlock, SeasonalityBlock
    >>> from autopycoin.models import Stack, NBEATS
    >>> from autopycoin.losses import QuantileLossError
    >>> trend_block = TrendBlock(label_width=10,
    ...                          p_degree=2,
    ...                          n_neurons=16,
    ...                          drop_rate=0.1,
    ...                          name="trend_block")
    >>> seasonality_block = SeasonalityBlock(label_width=10,
    ...                                      forecast_periods=[10],
    ...                                      backcast_periods=[20],
    ...                                      forecast_fourier_order=[10],
    ...                                      backcast_fourier_order=[20],
    ...                                      n_neurons=15,
    ...                                      drop_rate=0.1,
    ...                                      name="seasonality_block")
    >>> trend_blocks = [trend_block for _ in range(3)]
    >>> seasonality_blocks = [seasonality_block for _ in range(3)]
    >>> trend_stacks = Stack(trend_blocks, name="trend_stack")
    >>> seasonality_stacks = Stack(seasonality_blocks, name="seasonality_stack")
    >>> model = NBEATS([trend_stacks, seasonality_stacks], name="interpretable_NBEATS")

    Notes
    -----
    input shape:
    N-D tensor with shape: (..., batch_size, time step).
    The most common situation would be a 2D input with shape (batch_size, time step).

    output shape:
    N-D tensor with shape: (..., batch_size, units).
    For instance, for a 2D input with shape (batch_size, units),
    the output would have shape (batch_size, units).
    With a QuantileLossError with 2 quantiles or higher the output would have shape (quantiles, batch_size, units).
    If you add 2 variables, the output would have shape (variables, quantiles, batch_size, units).
    """

    def __init__(
        self,
        label_width: int,
        p_degree: Union[int, float] = 2,
        n_neurons: int = 32,
        drop_rate: float = 0.0,
        **kwargs: dict,
    ):

        super().__init__(
            label_width=label_width,
            n_neurons=n_neurons,
            drop_rate=drop_rate,
            g_trainable=False,
            interpretable=True,
            block_type="TrendBlock",
            **kwargs,
        )

        # Shape (-1, 1) in order to broadcast label_width to all p degrees
        self._p_degree = p_degree

    def coefficient_factory(
        self, output_last_dim: int, p_degrees: tf.Tensor
    ) -> tf.Tensor:
        """
        Compute the coefficients used in the last layer a.k.a g layer.

        Parameters
        ----------
        output_last_dim : int
        p_degree: int

        Returns
        -------
        coefficients : `tensor with shape (p_degree, label_width)`
            Coefficients of the g layer.
        """

        return tf.math.pow(
            (tf.range(output_last_dim, dtype=floatx()) / output_last_dim), p_degrees
        )

    def get_coefficients(self, output_last_dim: int, branch_name: str) -> tf.Tensor:
        """
        Return the coefficients calculated by the  `_coefficients_factory` method.

        Parameters
        ----------
        output_last_dim : int
        branch_name : str

        Returns
        -------
        coefficients : `weight with shape (p_degree, label_width)`
            Coefficients of the g layer.
        """

        # Set weights with calculated coef
        coef = self.coefficient_factory(
            output_last_dim, range_dims(self.p_degree + 1, shape=(-1, 1)),
        )
        return self.add_weight(
            shape=coef.shape,
            initializer=tf.constant_initializer(coef.numpy()),
            trainable=self.is_g_trainable,
            name=f"g_{branch_name}_{self.name}",
        )

    def get_config(self) -> dict:
        """get_condig method from tensorflow."""
        config = super().get_config()
        config.update(
            {
                "p_degree": self.p_degree,
                "label_width": self.label_width,
                "n_neurons": self._n_neurons,
                "drop_rate": self.drop_rate,
            }
        )
        return config

    @property
    def p_degree(self) -> int:
        """Return the degree of the trend equation."""
        return self._p_degree

    def _val___init__(
        self, output: None, *args: list, **kwargs: dict
    ) -> None:  # pylint: disable=unused-argument
        """Valid p_degree"""
        greater_or_equal(self.p_degree, 0, "p_degree")

SEASONALITY_TYPE = Union[Union[int, float], List[Union[int, float]]]

class SeasonalityBlock(BaseBlock):
    """
    Seasonality block definition.

    This layer represents the smaller part of nbeats model.
    Its internal layers are defining a fourier series.
    We introduced notion of fourier orders and seasonality periods which is not
    used in the original paper.
    It is possible to get explanation from this block.

    Parameters
    ----------
    label_width : int
        Horizon time to forecast.
    forecast_periods : int | float | List[int | float]
        Defines the periods used in the fourier equation.
        Default to `label_width`/2 as describe in the original paper.
    backcast_periods : int | float | List[int | float]
        Compute the fourier serie period in the backcasting equation.
        Default to `input_width`/2 as describe in the original paper.
    forecast_fourier_order : int | float | List[int | float]
        Compute the fourier orders.
        Each element is the order of its respective period.
        Default to `label_width`/2 as describe in the original paper.
    backcast_fourier_order : int | float | List[int | float]
        Compute the fourier orders.
        Each element is the order of its respective back period.
        Default to `input_width`/2 as describe in the original paper.
    n_neurons : int
        Number of neurons in the fully connected layers.
    drop_rate : float
        Rate of the dropout layer.
        This is used to estimate the epistemic error.
        Expected a value between 0 and 1. Default to 0.

    Attributes
    ----------
    label_width : int
    input_width : int
    input_spec : `InputSpec`
    drop_rate : float
    periods : int | float | list[int | float]
    back_periods : int | float | list[int | float]
        if not provided, then it is set during `build` method.
    forecast_fourier_order : int | float | list[int | float]
    backcast_fourier_order : int | float | list[int | float]
        if not provided, then it is set during `build` method.

    Raises
    ------
    ValueError:
        `drop_rate` not between 0 and 1.
        `periods` and `forecast_fourier_order` or their elements are not strictly positive values
        `back_periods` and `backcast_fourier_order` or their elements are not strictly positive values
        `backcast_fourier_order` and `forecast_fourier_order` don't have the same length.
        all others arguments are not strictly positive integers.

    Examples
    --------
    >>> from autopycoin.layers import TrendBlock, SeasonalityBlock
    >>> from autopycoin.models import Stack, NBEATS
    >>> from autopycoin.losses import QuantileLossError
    >>> trend_block = TrendBlock(label_width=10,
    ...                          p_degree=2,
    ...                          n_neurons=16,
    ...                          drop_rate=0.1,
    ...                          name="trend_block")
    >>> seasonality_block = SeasonalityBlock(label_width=10,
    ...                                      forecast_periods=[10],
    ...                                      backcast_periods=[20],
    ...                                      forecast_fourier_order=[10],
    ...                                      backcast_fourier_order=[20],
    ...                                      n_neurons=15,
    ...                                      drop_rate=0.1,
    ...                                      name="seasonality_block")
    >>> trend_blocks = [trend_block for _ in range(3)]
    >>> seasonality_blocks = [seasonality_block for _ in range(3)]
    >>> trend_stacks = Stack(trend_blocks, name="trend_stack")
    >>> seasonality_stacks = Stack(seasonality_blocks, name="seasonality_stack")
    >>> # model definition and compiling
    >>> model = NBEATS([trend_stacks, seasonality_stacks], name="interpretable_NBEATS")

    Notes
    -----
    input shape:
    N-D tensor with shape: (..., batch_size, time step).
    The most common situation would be a 2D input with shape (batch_size, time step).

    output shape:
    N-D tensor with shape: (..., batch_size, units).
    For instance, for a 2D input with shape (batch_size, units),
    the output would have shape (batch_size, units).
    With a QuantileLossError with 2 quantiles or higher the output would have shape (quantiles, batch_size, units).
    If you add 2 variables, the output would have shape (variables, quantiles, batch_size, units).
    """

    def __init__(
        self,
        label_width: int,
        forecast_periods: SEASONALITY_TYPE = None,
        backcast_periods: SEASONALITY_TYPE = None,
        forecast_fourier_order: SEASONALITY_TYPE = None,
        backcast_fourier_order: SEASONALITY_TYPE = None,
        n_neurons: int = 32,
        drop_rate: float = 0.0,
        **kwargs: dict,
    ):

        super(SeasonalityBlock, self).__init__(
            label_width=label_width,
            n_neurons=n_neurons,
            drop_rate=drop_rate,
            g_trainable=False,
            interpretable=True,
            block_type="SeasonalityBlock",
            **kwargs,
        )

        # forecast periods and fourier order can be calculated if not provided
        # backcast has to wait until `build` is called
        self._forecast_periods = (
            forecast_periods if forecast_periods else int(label_width / 2)
        )
        self._forecast_fourier_order = (
            forecast_fourier_order if forecast_fourier_order else self._forecast_periods
        )
        # backcast_fourier_order and backcast_periods can't be calculated
        self._backcast_periods = backcast_periods
        self._backcast_fourier_order = backcast_fourier_order

    def build(self, inputs_shape: tf.TensorShape):
        """Build method from tensorflow."""

        # if None then set an default value based on the *input shape*
        self._backcast_periods = self._backcast_periods or int(inputs_shape[-1] / 2)
        self._backcast_fourier_order = (
            self._backcast_fourier_order or self._backcast_periods
        )

        super().build(inputs_shape)

    def coefficient_factory(
        self,
        output_last_dim: int,
        periods: Union[int, float, List[Union[int, float]]],
        fourier_orders: Union[int, float, List[Union[int, float]]],
    ) -> tf.Tensor:
        """
        Compute the coefficients used in the last layer a.k.a g constrained layer.

        Parameters
        ----------
        output_last_dim : int
        periods : int | float | Tuple[int | float]
        fourier_orders : int | float | Tuple[int | float]

        Returns
        -------
        coefficients : `tensor with shape (periods * fourier_orders, label_width)`
            Coefficients of the g layer.
        """

        # Shape (-1, 1) in order to broadcast periods to all time units
        periods = tf.reshape(periods, shape=(-1, 1, 1))
        periods = tf.cast(periods, dtype=floatx())
        time_forecast = tf.range(output_last_dim, dtype=floatx())

        seasonality = 2.0 * np.pi * time_forecast / periods
        seasonality = (
            tf.expand_dims(tf.ragged.range(fourier_orders, dtype=floatx()), axis=-1)
            * seasonality
        )
        seasonality = tf.concat((tf.sin(seasonality), tf.cos(seasonality)), axis=0)
        return seasonality.flat_values

    def get_coefficients(self, output_last_dim: int, branch_name: str) -> tf.Tensor:
        """
        Return the coefficients calculated by the  `_coefficients_factory` method.

        Parameters
        ----------
        output_last_dim : int
        branch_name : str
        """

        periods = convert_to_list(getattr(self, branch_name + "_periods"))
        fourier_order = convert_to_list(getattr(self, branch_name + "_fourier_order"))

        # Set weights with calculated coef
        coef = self.coefficient_factory(output_last_dim, periods, fourier_order)

        return self.add_weight(
            shape=coef.shape,
            initializer=tf.constant_initializer(coef.numpy()),
            trainable=self.is_g_trainable,
            name=f"g_{branch_name}_{self.name}",
        )

    def get_config(self) -> dict:
        """get_config method from tensorflow."""
        config = super().get_config()
        config.update(
            {
                "label_width": self.label_width,
                "forecast_periods": self.forecast_periods,
                "backcast_periods": self.backcast_periods,
                "forecast_fourier_order": self.forecast_fourier_order,
                "backcast_fourier_order": self.backcast_fourier_order,
                "n_neurons": self._n_neurons,
                "drop_rate": self.drop_rate,
            }
        )
        return config

    @property
    def forecast_periods(self) -> List[int]:
        """Return periods."""
        return self._forecast_periods

    @property
    def backcast_periods(self) -> List[int]:
        """Return back periods."""
        return self._backcast_periods

    @property
    def forecast_fourier_order(self) -> List[int]:
        """Return fourier order."""
        return self._forecast_fourier_order

    @property
    def backcast_fourier_order(self) -> List[int]:
        """Return fourier order."""
        return self._backcast_fourier_order

    def _val___init__(
        self, output: None, *args: list, **kwargs: dict
    ) -> None:  # pylint: disable=unused-argument
        greater_or_equal(self.forecast_periods, 0, "forecast_periods")
        greater_or_equal(self.forecast_fourier_order, 0, "forecast_fourier_order")
        equal_length(
            self.forecast_periods,
            self.forecast_fourier_order,
            "forecast_periods",
            "forecast_fourier_order",
        )

        if self.backcast_periods is not None:
            greater_or_equal(self.backcast_periods, 0, "backcast_periods")
        if self.backcast_fourier_order is not None:
            greater_or_equal(self.backcast_fourier_order, 0, "backcast_fourier_order")
            equal_length(
                self.backcast_periods,
                self.backcast_fourier_order,
                "backcast_periods",
                "backcast_fourier_order",
            )

class GenericBlock(BaseBlock):
    """
    Generic block definition as described in the paper.

    This layer represents the smaller part of a nbeats model.
    We can't have explanation from this  block because g coefficients are learnt.

    Parameters
    ----------
    label_width : int
        Horizon time to forecast.
    g_forecast_neurons : int
        Dimensionality if the gf layer.
    g_backcast_neurons : int
        Dimensionality if the gb layer.
    n_neurons : int
        Number of neurons in Fully connected layers.
    drop_rate : float
        Rate of the dropout layer. This is used to estimate the epistemic error.
        Expected a value between 0 and 1. Default to 0.1.

    Attributes
    ----------
    label_width : int
    input_width : int
    input_spec : `InputSpec`
    drop_rate : float

    Examples
    --------
    >>> from autopycoin.layers import GenericBlock
    >>> from autopycoin.models import Stack, NBEATS
    >>> from autopycoin.losses import QuantileLossError
    >>> generic_block = GenericBlock(label_width=10,
    ...                          n_neurons=16,
    ...                          g_forecast_neurons=16,
    ...                          g_backcast_neurons=16,
    ...                          drop_rate=0.1,
    ...                          name="generic_block")
    >>> generic_blocks = [generic_block for _ in range(3)]
    >>> generic_stacks = Stack(generic_blocks, name="generic_stack")
    >>> # Model definition and compiling
    >>> model = NBEATS([generic_stacks, generic_stacks], name="generic_NBEATS")

    Notes
    -----
    input shape:
    N-D tensor with shape: (..., batch_size, time step).
    The most common situation would be a 2D input with shape (batch_size, time step).

    output shape:
    N-D tensor with shape: (..., batch_size, units).
    For instance, for a 2D input with shape (batch_size, units),
    the output would have shape (batch_size, units).
    With a QuantileLossError with 2 quantiles or higher the output would have shape (quantiles, batch_size, units).
    If you add 2 variables, the output would have shape (variables, quantiles, batch_size, units).
    """

    def __init__(
        self,
        label_width: int,
        g_forecast_neurons: int = 32,
        g_backcast_neurons: int = 32,
        n_neurons: int = 32,
        drop_rate: float = 0.1,
        **kwargs: dict,
    ):

        super().__init__(
            label_width=label_width,
            n_neurons=n_neurons,
            drop_rate=drop_rate,
            g_trainable=True,
            interpretable=False,
            block_type="GenericBlock",
            **kwargs,
        )

        self._g_forecast_neurons = g_forecast_neurons
        self._g_backcast_neurons = g_backcast_neurons

    def coefficient_factory(self, output_last_dim: int, neurons: int) -> tf.Tensor:
        """
        Compute the coefficients used in the last layer a.k.a g layer.
        This function is used in `_get_forecast_coefficients` and
        `_get_backcast_coefficients`.

        Parameters
        ----------
        output_last_dim : int
        neurons : int

        Returns
        -------
        coefficients : `tensor with shape (label_width, neurons)`
            Coefficients of the g layer.
        """

        coefficients = tf.keras.initializers.GlorotUniform(seed=42)(
            shape=self.n_variates + [neurons, output_last_dim]
        )

        return coefficients

    def get_coefficients(self, output_last_dim: int, branch_name: str) -> tf.Tensor:
        """
        Return the coefficients used in forecast and backcast layer a.k.a g layer.

        Parameters
        ----------
        output_last_dim : int
        branch_name : str

        Returns
        -------
        coefficients: `Tensor` of shape (d0, ..., label_width)
        """

        neurons = getattr(self, "g_" + branch_name + "_neurons")
        # Set weights with calculated coef
        coef = self.coefficient_factory(output_last_dim, neurons)
        return self.add_weight(
            shape=coef.shape,
            initializer=tf.constant_initializer(coef.numpy()),
            trainable=self.is_g_trainable,
            name=f"g_{branch_name}_{self.name}",
        )

    def get_config(self) -> dict:
        """get_config method from tensorflow."""
        config = super().get_config()
        config.update(
            {
                "label_width": self.label_width,
                "g_forecast_neurons": self.g_forecast_neurons,
                "g_backcast_neurons": self.g_backcast_neurons,
                "n_neurons": self._n_neurons,
                "drop_rate": self.drop_rate,
            }
        )
        return config

    @property
    def g_forecast_neurons(self):
        """Return the dimension of the gf layer."""
        return self._g_forecast_neurons

    @property
    def g_backcast_neurons(self):
        """Return the dimension of the gb layer."""
        return self._g_backcast_neurons

    def _val___init__(
        self, output: None, *args: list, **kwargs: dict
    ) -> None:  # pylint: disable=unused-argument
        greater_or_equal(self.g_forecast_neurons, 0, "g_forecast_neurons")
        greater_or_equal(self.g_backcast_neurons, 0, "g_backcast_neurons")


import  matplotlib.pyplot as plt
import csv
from datetime import datetime     # from datetime 模组中 import datetime

# x_cors = [1, 2, 3, 4]
# y_cors = [2, 3, 4, 5]

# plt.plot(x_cors, y_cors)
# plt.show()       # 一定用要呼叫函式, show 在新的视窗上

def showPlot(csv_file):     # 把 csv_file 档案内的图 show 出来
    f = open(csv_file, "r")
    r = csv.DictReader(f)    # 第一行当成标签作为字典
    x = []    # 空的 list
    y = []
    for single_row in r:
        print(single_row)
        y.append(single_row['汇率'])   # 取出 "汇率" 的值后, 再回传到 y 的 list
        x.append(datetime.strptime(single_row['时间'], "%Y-%m-%d %H:%M:%S"))   # 取出 "时间" 内的值回传到 x 的 list
    # print(x)    # 确认 append 的结果是否为 datetime 的 strptime
    # print(y)
    plt.plot(x, y)
    plt.gcf().autofmt_xdate()   # 图中的 x label 改成倾斜标示
    plt.show()

# showPlot('./result/美金(USD).csv')   # 给定汇入的档案位址
file_name = input("请输入欲作图的CSV: ")    # 给使用者自行输入档案
file_name = "./result/" + file_name
showPlot(file_name)


import unittest
import argparse
import sys
import copy
# from your_model_file import ModelA, ModelB  # Import your models from your model file

import numpy as np
import torch
import torch.nn as nn
import torch.distributed as dist
import torch.multiprocessing as mp
import torch.nn.functional as F
from fairscale.internal import torch_version

from typing import Any, Optional

RPC_PORT = 29501
from rtp.module.linear import ColumnParallelLinear, RowParallelLinear
from rtp.module.conv import ColumnParallelConv2d
from torch.nn.parallel import DistributedDataParallel as DDP

def init_random_seed(seed: int):

    torch.manual_seed(seed)
    torch.cuda.manual_seed(seed)
    np.random.seed(seed)

def objects_are_equal(
    a: Any,
    b: Any,
    raise_exception: bool = False,
    dict_key: Optional[str] = None,
    rtol: Optional[float] = None,
    atol: Optional[float] = None,
) -> bool:
    """
    Test that two objects are equal. Tensors are compared to ensure matching
    size, dtype, device and values.
    """
    if type(a) is not type(b):
        if raise_exception:
            raise ValueError(f"type mismatch {type(a)} vs. {type(b)}")
        return False
    if isinstance(a, dict):
        if set(a.keys()) != set(b.keys()):
            if raise_exception:
                raise ValueError(f"keys mismatch {a.keys()} vs. {b.keys()}")
            return False
        for k in a.keys():
            if not objects_are_equal(a[k], b[k], raise_exception, k):
                return False
        return True
    elif isinstance(a, (list, tuple, set)):
        if len(a) != len(b):
            if raise_exception:
                raise ValueError(f"length mismatch {len(a)} vs. {len(b)}")
            return False
        return all(objects_are_equal(x, y, raise_exception) for x, y in zip(a, b))
    elif torch.is_tensor(a):
        try:
            # assert_close doesn't strictly test shape, dtype and device
            shape_dtype_device_match = a.size() == b.size() and a.dtype == b.dtype and a.device == b.device
            if not shape_dtype_device_match:
                if raise_exception:
                    msg = f"sizes: {a.size()} vs. {b.size()}, "
                    msg += f"types: {a.dtype} vs. {b.dtype}, "
                    msg += f"device: {a.device} vs. {b.device}"
                    raise AssertionError(msg)
                else:
                    return False
            # assert_close.
            if torch_version() < (1, 12, 0):
                torch.testing.assert_allclose(a, b, rtol=rtol, atol=atol)
            else:
                torch.testing.assert_close(a, b, rtol=rtol, atol=atol)
            return True
        except (AssertionError, RuntimeError) as e:
            if raise_exception:
                if dict_key and isinstance(e, AssertionError):
                    # Add dict key to the assertion error.
                    msg = e.args[0]
                    new_msg = f"For dict key '{dict_key}': {msg}"
                    raise AssertionError(new_msg) from None
                else:
                    raise e
            else:
                return False
    else:
        return a == b

def _gather(input_: torch.Tensor, dim) -> torch.Tensor:

    # Bypass the function if we are using only 1 GPU.
    if torch.distributed.get_world_size() == 1:
        return input_

    # Size and dimension.
    last_dim = input_.dim() - 1
    rank = torch.distributed.get_rank()
    world_size = torch.distributed.get_world_size()

    tensor_list = [torch.empty_like(input_) for _ in range(world_size)]
    tensor_list[rank] = input_
    torch.distributed.all_gather(tensor_list, input_)

    # Note: torch.cat already creates a contiguous tensor.
    output = torch.cat(tensor_list, dim=dim).contiguous()

    return output

def _reduce(input_: torch.Tensor) -> torch.Tensor:
    """All-reduce the the input tensor across model parallel group."""
    group = torch.distributed.distributed_c10d._get_default_group()

    # Bypass the function if we are using only 1 GPU.
    if torch.distributed.get_world_size(group=group) == 1:
        return input_

    # All-reduce.
    torch.distributed.all_reduce(input_, group=group)

    return input_

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(1, 32, 3, 1)
        self.conv2 = nn.Conv2d(32, 64, 3, 1)
        self.dropout1 = nn.Dropout(0.25)
        self.dropout2 = nn.Dropout(0.5)
        self.fc1 = nn.Linear(9216, 128)
        self.fc2 = nn.Linear(128, 10)

    def forward(self, x):

        x = self.conv1(x)
        x = F.relu(x)
        x = self.conv2(x)
        x = F.relu(x)
        x = F.max_pool2d(x, 2)
        x = self.dropout1(x)
        x = torch.flatten(x, 1)
        x = self.fc1(x)
        x = F.relu(x)
        x = self.dropout2(x)
        x = self.fc2(x)
        output = F.log_softmax(x, dim=1)
        return output

def replace_linear_layers(module, world_size, rank):
    """
    Recursively replace all linear layers in the module with the custom layer.

    Args:
    - module (nn.Module): The module (or model) to modify.
    - custom_layer_class (nn.Module): The custom layer class to replace with.
    """
    for name, child in module.named_children():
        if isinstance(child, nn.Linear):
            in_features = child.in_features
            out_features = child.out_features
            bias = child.bias is not None
            device = child.weight.device
            dtype = child.weight.dtype

            if out_features % world_size == 0:
                r = ColumnParallelLinear(in_features, out_features, bias=bias,
                                        linear_layer=child,
                                        device=device, dtype=dtype)
            else:
                r = RowParallelLinear(in_features, out_features, bias=bias,
                                        linear_layer=child,
                                        device=device, dtype=dtype)
            setattr(module, name, r)

        else:
            replace_linear_layers(child, world_size, rank)

def replace_conv_layers(module, custom_layer_class, world_size, rank):
    """
    Recursively replace all linear layers in the module with the custom layer.

    Args:
    - module (nn.Module): The module (or model) to modify.
    - custom_layer_class (nn.Module): The custom layer class to replace with.
    """
    for name, child in module.named_children():
        if isinstance(child, nn.Conv2d):
            in_channels = child.in_channels
            out_channels = child.out_channels
            kernel_size = child.kernel_size
            stride = child.stride
            padding = child.padding
            dilation = child.dilation
            groups = child.groups
            bias = child.bias is not None
            device = child.weight.device
            dtype = child.weight.dtype

            r = custom_layer_class(in_channels, out_channels, kernel_size, stride, padding, dilation, groups, bias,
                                   Conv2d_layer=child,
                                   device=device, dtype=dtype)

            setattr(module, name, r)
        else:
            replace_conv_layers(child, custom_layer_class, world_size, rank)

class TestIdenticalOutputs(unittest.TestCase):

    def setUp(self):
        pass

    def assert_grad(self, grad1, grad2):
        assert objects_are_equal(grad1, grad2)

    def test_identical_outputs(self):
        # Example input for the models
        num_samples = 4
        in_channels = 1
        input_size = 28
        learning_rate = 0.001

        rank = torch.distributed.get_rank()
        world_size = torch.distributed.get_world_size()
        torch.cuda.set_device(args.local_rank)
        device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")

        init_random_seed(0)

        data_list = [torch.randn(num_samples, in_channels, input_size, input_size).cuda() for _ in range(world_size)]
        data =  data_list[rank]#torch.cat(data_list, dim=0).contiguous()
        labels_list = [torch.randint(0, 2, (num_samples,)).cuda() for _ in range(world_size)]
        labels =  labels_list[rank]#torch.cat(labels_list, dim=0).contiguous()

        model = Net().cuda()
        model.eval()

        Weight_model = copy.deepcopy(model)
        Weight_model.eval()

        model = DDP(model)

        # criterion = nn.CrossEntropyLoss().cuda()
        optimizer = torch.optim.Adadelta(model.parameters(), lr=learning_rate)

        outputs = model(data)
        loss = F.nll_loss(outputs, labels, reduction='sum') #criterion(outputs, labels)

        # Backward pass and optimize
        optimizer.zero_grad()
        loss.backward()

        ref_grads = [p.grad.detach().clone() for p in model.parameters()]
        # for param in model.parameters():
        #     param.grad = None

        optimizer.step()

        sub_sample = num_samples
        # data_list = torch.split(data, sub_sample, dim=0)
        cur_data = data_list[rank]
        #output_list = torch.split(outputs, sub_sample, dim=0)
        cur_output = outputs#output_list[rank]
        # labels_list = torch.split(labels, sub_sample, dim=0)
        cur_label = labels_list[rank]

        replace_linear_layers(Weight_model, world_size, rank)
        replace_conv_layers(Weight_model, ColumnParallelConv2d, world_size, rank)
        Weight_model_output = Weight_model(cur_data)

        Weight_optimizer = torch.optim.Adadelta(Weight_model.parameters(), lr=learning_rate)

        assert objects_are_equal(cur_output, Weight_model_output)

        Weight_optimizer.zero_grad()
        Weight_loss = F.nll_loss(Weight_model_output, cur_label, reduction='sum') / world_size #criterion(Weight_model_output, cur_label) / world_size
        Weight_loss.backward()
        Weight_grads = [p.grad.detach().clone() for p in Weight_model.parameters()]

        Weight_optimizer.step()
        Weight_optimizer.zero_grad()

parser = argparse.ArgumentParser()
parser.add_argument('--local-rank', type=int, default=0)
args, remaining = parser.parse_known_args()
sys.argv[1:] = remaining

# This allows the test to be run if the file is run as a script
if __name__ == '__main__':
    num_devices = torch.cuda.device_count() if torch.cuda.is_available() else 1
    world_size = num_devices
    torch.distributed.init_process_group(
        backend="nccl", rank=args.local_rank, world_size=world_size
    )
    unittest.main()


from tensorflow import keras
import numpy as np
from random import random, randint, sample
import matplotlib.pyplot as plt

class AgentDQL:

    """
    In this class we create a Deep Q-learning Agent,
    running in a stochastic MDP environment.
    It uses Tensorflow Backend for Deep Learning.
    ________________________________________________________________
    Parameters :
    - gamma : float [0,1]
    The discount factor
    - lr : float [0,1]
    The learning rate
    - eps : float [0,1]
    The exploration factor
    - episodes : int
    Number of episodes to run
    - batch_size : int
    Number of samples to take as a batch for training the Q network
    - train_every : int
    Train the Q network every () steps
    - update_every : int
    Update the target Network every () steps
    - env : MDP_environment
    Environment to run the Agent in

    ________________________________________________________________
    Attributes :
    - s : int
    The current state
    - durations : list[int]
    List of each episodes' duration
    - episode_memory_buffer : list[list[int]]
    List of each episodes' states history list
    - episode_memory_buffer_len : int
    Size of the path memory buffer for the display of histories
    - memory : list[tuple(state, action, reward, new_state)]
    Memory buffer
    - memory_size : int (default=2000)
    Size of the memory buffer for the tuples (state, action, reward, new_state)
    - network : Keras model
    Main Q network
    - target_network : Keras model
    Target Q network

    """

    def __init__(self, gamma, lr, eps, episodes, batch_size, train_every, update_every, env):

        # Parameters
        self.episodes = episodes
        self.gamma = gamma
        self.lr = lr
        self.eps = eps
        self.batch_size = batch_size
        self.train_every = train_every
        self.update_every = update_every
        self.env = env

        # Attributes
        self.s = 0  # Initialization : state 0
        self.durations = []
        self.episode_memory_buffer = []
        self.episode_memory_buffer_len = 3

        # Deep Q specific attributes
        self.memory = []
        self.memory_size = 2000
        self.network = self.create_network()
        self.target_network = self.create_network()

    def create_network(self):

        """
        Instantiates the Q-network
        as a two hidden layers dense network.
        """

        model = keras.Sequential([
            keras.layers.Dense(24, input_dim = self.env.getNbStates(), activation="relu"),
            keras.layers.Dense(12, activation = "relu"),
            keras.layers.Dense(self.env.getNbActions())])
        model.compile(loss = "mean_squared_error", optimizer = 'adam')

        return model

    def state_to_input(self, state):

        """
        One-hot encoding function for the states.
        """

        input = [0 for i in range(self.env.getNbStates())]
        input[state] = 1
        return [input]

    def replay(self, i):

        """
        Uses memory replay to update the target and train the model with a batch.
        """

        # Make sure we have enough memory for training

        if len(self.memory) < self.batch_size:
            return

        # Training

        print("==>", "(", i, ")" ," Training network...")

        memory_samples = sample(self.memory, self.batch_size)

        for m in memory_samples:
            state, action, reward, new_state = m

            # update target

            target = self.target_network.predict(self.state_to_input(state))
            if new_state == -1:
                target[0][action] = reward
            else:
                target[0][action] = reward + self.gamma * max(self.target_network.predict(self.state_to_input(new_state))[0])

            # train network

            self.network.fit(
                np.array(self.state_to_input(state)),
                target,
                epochs=1,
                verbose=0)

    def replace_network(self, i):

        """ Updates Target Network. """

        print("==>", "(", i, ")" ," Updating target network...")

        weights = self.network.get_weights()
        target_weights = self.target_network.get_weights()

        for i in range(len(target_weights)):
            target_weights[i] = weights[i]

        self.target_network.set_weights(target_weights)

    def getNextAction(self):

        rand = random()

        # exploitation
        if rand > self.eps:
            a = np.argmax(self.network.predict(self.state_to_input(self.s))[0])
        # exploration
        else:
            a = randint(0, 3)
        return a

    def learning(self):

        """
        Main training loop.
        """

        print("=== USING DEEP Q TRAINING ===")

        # Environment parameters

        dims = self.env.getDims()
        n_actions = self.env.getNbActions()
        n_states = self.env.getNbStates()
        s_final = self.env.get_final_state()

        # Training

        self.rewards_hist = []
        step_counter = 0

        for i in range(self.episodes):

            # Epsilon decay

            if i == self.episodes//4:
                print("dividing by two")
                self.eps /= 2

            if i == self.episodes//2:
                print("dividing by two")
                self.eps /= 2

            if i == 3*self.episodes//4:
                print("dividing by two")
                self.eps /= 2

            if i == 9*self.episodes//10:
                print("removing random exploration")
                self.eps = 0

            # Episode Initialization

            print("=> épisode %i/%i..." % (i+1, self.episodes))

            episode_reward = 0
            episode_hist = [0]

            # State initialization

            self.s = 0

            while self.s != -1:

                # Sample action, get next state

                a = self.getNextAction()

                next_state, reward = self.env.next_step(self.s, a)

                episode_hist.append(next_state)

                episode_reward += reward

                # Write in memory

                if next_state == s_final:
                    next_state = -1

                if len(self.memory) < self.memory_size:
                    self.memory.append([self.s, a, reward, next_state])
                else:
                    self.memory = self.memory[1:] + [[self.s, a, reward, next_state]]

                # Next state update

                self.s = next_state

                # Train the Q-Network every S steps

                if step_counter % self.train_every == 0:
                    self.replay(step_counter)

                if step_counter % self.update_every == 0:
                    self.replace_network(step_counter)

                step_counter += 1

            # End of the episode

            print("===> duration: ", len(episode_hist))
            print("===> reward: ", episode_reward)

            # History update

            self.durations.append(len(episode_hist))
            self.rewards_hist.append(episode_reward)

            if len(self.episode_memory_buffer) == self.episode_memory_buffer_len:
                self.episode_memory_buffer = self.episode_memory_buffer[1:] + [
                    episode_hist]
            else:
                self.episode_memory_buffer += [episode_hist]

        # End of training
        print("=== DEEP-Q TRAINING FINISHED ===")

    def plot_history(self, grid=False):

        # Plot duration and reward history

        plt.subplot(2, 1, 1)
        plt.plot(range(len(self.durations)), self.durations)
        plt.xlabel('episode')
        plt.ylabel('duration (ms)')
        plt.title('Episode duration evolution with training')
        plt.grid(grid)

        plt.subplot(2, 1, 2)
        plt.plot(range(len(self.rewards_hist)), self.rewards_hist)
        plt.xlabel('episode')
        plt.ylabel('reward')
        plt.title('Episode reward evolution with training')
        plt.grid(grid)

        plt.tight_layout()
        plt.show()

# uses the simulated environment with a differnt state
class AgentDCQL(AgentDQL):
    def create_network(self):

        """
        Instantiates the Q-network
        as a convolutional neural network
        """

        model = keras.Sequential([
            keras.layers.Conv2D(24, (2, 2), activation='relu', input_shape=(self.env.n_lin, self.env.n_col, 2)),
            keras.layers.MaxPooling2D((2, 2)),
            keras.layers.Conv2D(18, (2, 2), activation='relu'),
            keras.layers.Flatten(),
            keras.layers.Dense(12, activation = "relu"),
            keras.layers.Dense(self.env.getNbActions())])
        model.compile(loss = "mean_squared_error", optimizer = 'adam')

        return model

    def state_to_input(self, state):
        return np.array([state])

    def learning(self):

        """
        Main training loop.
        """

        print("=== USING DEEP Q TRAINING ===")

        # Environment parameters

        dims = self.env.getDims()
        n_actions = self.env.getNbActions()
        n_states = self.env.getNbStates()
        s_final = self.env.get_final_state()

        # Training

        self.rewards_hist = []
        step_counter = 0

        for i in range(self.episodes):

            if i%50==0 and i > 150:
                self.plot_history()
                self.eps *= 0.93

            # Epsilon decay

            if i == self.episodes // 4:
                print("dividing by two")
                self.eps /= 2

            if i == self.episodes // 2:
                print("dividing by two")
                self.eps /= 2

            if i == 3 * self.episodes // 4:
                print("dividing by two")
                self.eps /= 2

            if i == 9 * self.episodes // 10:
                print("removing random exploration")
                self.eps = 0

            # Episode Initialization

            print("=> épisode %i/%i..." % (i + 1, self.episodes))

            episode_reward = 0
            episode_hist = [0]

            # State initialization

            self.env.regenerate()
            self.s = self.env.to_state([0, 0])

            duration_limit = 100

            while not self.env.is_final(self.s) and duration_limit > 0:

                # Sample action, get next state

                a = self.getNextAction()

                next_state, reward = self.env.next_step(self.s, a)

                def fformat(m):
                    sss = np.argmax(m)
                    return [sss // self.env.n_col, sss % self.env.n_col]

                #print("res:", reward, a, fformat(self.s[:,:,0]), fformat(next_state[:,:,0]), fformat(next_state[:,:,1]), [[-1, 0], [0, 1], [1, 0], [0, -1]][a])

                episode_hist.append(next_state)

                episode_reward += reward

                if len(self.memory) < self.memory_size:
                    self.memory.append([self.s, a, reward, next_state])
                else:
                    self.memory = self.memory[1:] + [[self.s, a, reward, next_state]]

                # Next state update

                self.s = next_state

                # Train the Q-Network every S steps

                if step_counter % self.train_every == 0:
                    self.replay(step_counter)

                if step_counter % self.update_every == 0:
                    self.replace_network(step_counter)

                step_counter += 1
                duration_limit -= 1

            # End of the episode

            print("===> duration: ", len(episode_hist))
            print("===> reward: ", episode_reward)

            # History update

            self.durations.append(len(episode_hist))
            self.rewards_hist.append(episode_reward)

            if len(self.episode_memory_buffer) == self.episode_memory_buffer_len:
                self.episode_memory_buffer = self.episode_memory_buffer[1:] + [
                    episode_hist]
            else:
                self.episode_memory_buffer += [episode_hist]

        # End of training
        print("=== DEEP-Q TRAINING FINISHED ===")

    def replay(self, i):

        """
        Uses memory replay to update the target and train the model with a batch.
        """

        # Make sure we have enough memory for training

        if len(self.memory) < self.batch_size:
            return

        # Training

        print("==>", "(", i, ")" ," Training network...")

        memory_samples = sample(self.memory, self.batch_size)

        state_list = []
        target_list = []

        for m in memory_samples:
            state, action, reward, new_state = m

            # update target

            target = self.target_network.predict(self.state_to_input(state))
            if self.env.is_final(new_state):
                target[0][action] = reward
            else:
                target[0][action] = reward + self.gamma * max(self.target_network.predict(self.state_to_input(new_state))[0])

            # train network

            state_list.append(state)
            target_list.append(target)

            """self.network.fit(
                np.array(self.state_to_input(state)),
                target,
                epochs=1,
                verbose=0)"""
        self.network.fit(
            np.array(state_list),
            np.array(target_list),
            epochs=1,
            verbose=0)


# -*-coding:utf-8-*-
# @auth ivan
# @time 20191204 10:43:07
# @goal test

import sys
import dlib
import cv2 as cv
import numpy as np
import pandas as pd
import warnings
warnings.filterwarnings("ignore")

# print("-"*100, sys.argv[1])

path = "/data/gproj/code/SecurityAI_Round1/Data/images/"
outp = f"{sys.argv[1]}"
# print("Out:", outp)

st, ed = 1, 712
# st, ed = 1, 100
sall = ed-st+1
x, y, z = 112, 112, 3
hig, low = 1, 0
A, B, C = float(sys.argv[2]), float(sys.argv[3]), float(sys.argv[4])

modelv, n, nct = "_68", 19, None

# 人脸分类器
detector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor(f"../Model/shape_predictor{modelv}_face_landmarks.dat")

def whimax(tdata, y, x, tmin=-25, tmax=+25):
    """
        0 mean:   94.24, min:0, max:255
        1 mean:   94.07, min:0, max:255
        2 mean:   94.10, min:0, max:255

        [95, -95, 0] 15 0.15
    """
    base1 = np.random.randint(0, 1, size=tdata.shape)
    base2 = np.random.randint(0, 1, size=tdata.shape)
    base3 = np.random.randint(0, 1, size=tdata.shape)
    # base4 = np.random.randint(0, 1, size=tdata.shape)
    base5 = np.random.randint(0, 1, size=tdata.shape)

    base1[:] = [-94.24, -94.07, -94.10]
    base2[:] = [y, x, 255]
    base3[:] = [255, x, y]
    # base4[:] = [tdata[:,:,0].mean(), tdata[:,:,1].mean(), tdata[:,:,2].mean()]
    base5[:] = [A, B, C]

    rn = []
    for n in range(tmin, tmax+1):
        tdata1 = tdata.copy()
        tdata1 = tdata1 + n
        rn.append([
            n, np.array([
                tdata1,
                # base1,
                # base2,
                # base3,
                # base4,
                base5
            ]).var()
        ])
    rn = pd.DataFrame(rn)
    rn.sort_values(by=1, inplace=True)
    return int(rn.head(1).iloc[0][0])

ndets1, ndets2 = {}, {}
for i in range(st, ed+1):
    pi = f"{path}"+f"00000{i}"[-5:]+".jpg"
    po = f"{outp}/images/"+f"00000{i}"[-5:]+".jpg"
    print(i, pi, po)

    img = cv.imread(pi)
    imgo = img.copy()

    # TODO: //1//
    gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
    det_s = detector(gray, 1)
    ndets1[i] = det_s

    for face in det_s:
        shape = predictor(img, face)
        ni = 0
        for pt in shape.parts():
            ni += 1
            if 0 <= pt.x < x and 0 <= pt.y < y:
                imgo[pt.y-n:pt.y+n, pt.x-n:pt.x+n] = img[pt.y-n:pt.y+n, pt.x-n:pt.x+n] + whimax(img[pt.y-n:pt.y+n, pt.x-n:pt.x+n], pt.y, pt.x)

    def f(n):
        return [
            (y//2-y//n, x//2-x//n),
            (y//2-y//n, x//2),
            (y//2-y//n, x//2+x//n),
            (y//2, x//2-x//n),
            (y//2, x//2+x//n),
            (y//2+y//n, x//2-x//n),
            (y//2+y//n, x//2),
            (y//2+y//n, x//2+x//n),
        ]

    if not det_s:
        for pypx in [(y//2, x//2)]+f(4)+f(8)+f(16):
            (py, px) = pypx
            py, px = int(py), int(px)
            if 0 <= px < x and 0 <= py < y:
                imgo[py-n:py+n, px-n:px+n] = img[py-n:py+n, px-n:px+n] + whimax(img[py-n:py+n, px-n:px+n], py, px)

    cv.imwrite(po, imgo)
    # break

for i in range(st, ed+1):
    po = f"{outp}/images/"+f"00000{i}"[-5:]+".jpg"
    pot = f"{outp}/timages/A"+f"00000{i}"[-5:]+".jpg"
    # print(i, po, pot)
    img = cv.imread(po)
    gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
    det_s = detector(gray, 1)
    ndets2[i] = det_s

    for face in det_s:
        shape = predictor(img, face)
        for pt in shape.parts():
            pt_pos = (pt.x, pt.y)
            cv.circle(img, pt_pos, 2, (0, 255, 0), 1)
        cv.imwrite(pot, img)

lndets1 = sum([0 if i else 1 for i in ndets1.values()])
lndets2 = sum([0 if i else 1 for i in ndets2.values()])
dets12 = sum([1 if i!=j else 0 for i,j in zip(ndets1.values(), ndets2.values())])

result = f"Result {n}\n ndets1: {lndets1}, {lndets1/sall}\n ndets2: {lndets2}, {lndets2/sall}\n dets12: {dets12}/{sall}, {dets12/sall}\n"
with open("../Out/result", "w") as f:
    f.write(result)


import sys
import time
from PIL import Image, ImageDraw
#from models.tiny_yolo import TinyYoloNet
from yolo_utils import *
from darknet import Darknet

import cv2

namesfile=None
def detect(cfgfile, weightfile, imgfolder):
    m = Darknet(cfgfile)

    #m.print_network()
    m.load_weights(weightfile)
    print('Loading weights from %s... Done!' % (weightfile))

    # if m.num_classes == 20:
    #     namesfile = 'data/voc.names'
    # elif m.num_classes == 80:
    #     namesfile = 'data/coco.names'
    # else:
    #     namesfile = 'data/names'

    use_cuda = True
    if use_cuda:
        m.cuda()

    imgfiles = [x for x in os.listdir(imgfolder) if x[-4:] == '.jpg']
    imgfiles.sort()
    for imgname in imgfiles:
        imgfile = os.path.join(imgfolder,imgname)

        img = Image.open(imgfile).convert('RGB')
        sized = img.resize((m.width, m.height))

        #for i in range(2):
        start = time.time()
        boxes = do_detect(m, sized, 0.5, 0.4, use_cuda)
        finish = time.time()
            #if i == 1:
        print('%s: Predicted in %f seconds.' % (imgfile, (finish-start)))

        class_names = load_class_names(namesfile)
        img = plot_boxes(img, boxes, 'result/{}'.format(os.path.basename(imgfile)), class_names)
        img = np.array(img)
        cv2.imshow('{}'.format(os.path.basename(imgfolder)), img)
        cv2.resizeWindow('{}'.format(os.path.basename(imgfolder)), 1000,800)
        cv2.waitKey(1000)

def detect_cv2(cfgfile, weightfile, imgfile):
    import cv2
    m = Darknet(cfgfile)

    m.print_network()
    m.load_weights(weightfile)
    print('Loading weights from %s... Done!' % (weightfile))

    if m.num_classes == 20:
        namesfile = 'data/voc.names'
    elif m.num_classes == 80:
        namesfile = 'data/coco.names'
    else:
        namesfile = 'data/names'

    use_cuda = True
    if use_cuda:
        m.cuda()

    img = cv2.imread(imgfile)
    sized = cv2.resize(img, (m.width, m.height))
    sized = cv2.cvtColor(sized, cv2.COLOR_BGR2RGB)

    for i in range(2):
        start = time.time()
        boxes = do_detect(m, sized, 0.5, 0.4, use_cuda)
        finish = time.time()
        if i == 1:
            print('%s: Predicted in %f seconds.' % (imgfile, (finish-start)))

    class_names = load_class_names(namesfile)
    plot_boxes_cv2(img, boxes, savename='predictions.jpg', class_names=class_names)

def detect_skimage(cfgfile, weightfile, imgfile):
    from skimage import io
    from skimage.transform import resize
    m = Darknet(cfgfile)

    m.print_network()
    m.load_weights(weightfile)
    print('Loading weights from %s... Done!' % (weightfile))

    if m.num_classes == 20:
        namesfile = 'data/voc.names'
    elif m.num_classes == 80:
        namesfile = 'data/coco.names'
    else:
        namesfile = 'data/names'

    use_cuda = True
    if use_cuda:
        m.cuda()

    img = io.imread(imgfile)
    sized = resize(img, (m.width, m.height)) * 255

    for i in range(2):
        start = time.time()
        boxes = do_detect(m, sized, 0.5, 0.4, use_cuda)
        finish = time.time()
        if i == 1:
            print('%s: Predicted in %f seconds.' % (imgfile, (finish-start)))

    class_names = load_class_names(namesfile)
    plot_boxes_cv2(img, boxes, savename='predictions.jpg', class_names=class_names)

if __name__ == '__main__':
    if len(sys.argv) == 5:
        cfgfile = sys.argv[1]
        weightfile = sys.argv[2]
        imgfolder = sys.argv[3]
        cv2.namedWindow('{}'.format(os.path.basename(imgfolder)), cv2.WINDOW_NORMAL )
        cv2.resizeWindow('{}'.format(os.path.basename(imgfolder)), 1000,800)
        globals()["namesfile"] = sys.argv[4]
        detect(cfgfile, weightfile, imgfolder)
        #detect_cv2(cfgfile, weightfile, imgfile)
        #detect_skimage(cfgfile, weightfile, imgfile)
    else:
        print('Usage: ')
        print('  python detect.py cfgfile weightfile imgfolder names')
        #detect('cfg/tiny-yolo-voc.cfg', 'tiny-yolo-voc.weights', 'data/person.jpg', version=1)


import time

from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By

ser_obj = Service("D:\Software\chromedriver.exe")
driver = webdriver.Chrome(service=ser_obj)
driver.get("https://www.myntra.com/myntra-fashion-store?rf=Discount%20Range%3A90.0_100.0_90.0%20TO%20100.0")
driver.maximize_window()
gender_list = driver.find_elements(By.XPATH,
                                   "//ul[@class='gender-list']//label[@class='common-customRadio gender-label']")

# for gender in gender_list:
#     print(gender.text)
#     if gender.text == "Girls":
#         gender.click()
#         time.sleep(15)
#         flag = driver.find_element(By.XPATH, "//ul[@class='gender-list']//label[@class='common-customRadio gender-label undefined']//input[@type='radio']").is_selected()
#         print(flag)
#         assert flag
#         break

gender_list[2].click()
time.sleep(15)
flag = driver.find_element(By.XPATH, "//ul[@class='gender-list']//label[@class='common-customRadio gender-label undefined']//input[@type='radio']").is_selected()
print(flag)
assert flag

driver.close()


import boto3, requests
import difflib

d = difflib.Differ()

r = requests.get("https://checkip.amazonaws.com")

current_ip = r.text.strip()
zoneid = "Z0780024JLJK9BTLLRT5"
hostname = "znetwork.fr."

route53 = boto3.client('route53')

def updatedns(hostname, newdns):
    sets = route53.list_resource_record_sets(HostedZoneId=zoneid)
    for rset in sets['ResourceRecordSets']:
#        print(rset)
        if rset['Name'] == hostname and rset['Type'] == 'A':
            curdnsrecord = rset['ResourceRecords']
            print(curdnsrecord)
            if type(curdnsrecord) in [list, tuple, set]:
                for record in curdnsrecord:
                    curdns = record['Value']
            # print('Current DNS CNAME: %s' % curdns)
            curttl = rset['TTL']
            # print('Current DNS TTL: %s' % curttl)
            if curdns != newdns:
                # UPSERT the record
                print('Updating %s' % hostname)
                route53.change_resource_record_sets(
                    HostedZoneId=zoneid,
                    ChangeBatch={
                    'Changes': [
                        {
                        'Action': 'UPSERT',
                        'ResourceRecordSet': {
                            'Name': hostname,
                            'Type': 'A',
                            'TTL': curttl,
                            'ResourceRecords': [
                            {
                                'Value': newdns
                            }
                            ]
                        }
                        }
                    ]
                    }
                )
try:
    updatedns(hostname, current_ip)
except:
    print('DNS Update failed. Check credentials or IAM roles.')


'''Saul Goodman'''
import json

def better_call_saul():
    '''Saul Goodman'''
    data_in = json.loads(input())
    length = len(data_in)
    num_need, every = int(input()), int(input())
    all_possible = []

    for i in range(length):
        summer = 0
        start_num = i
        end_num = i + (num_need*every)
        data = create_data(data_in, start_num, end_num, every, num_need)
        if num_need > length:
            summer += (num_need // length) * sum(data)
            num_need = num_need % length
            end_num = start_num + (num_need*every)
            for j in range(start_num, end_num, every):
                j %= len(data)
                summer += data[j]
        else:
            summer += sum(data)
        all_possible.append(summer)

    print(max(all_possible))

def create_data(data, start_num, end_num, every, need):
    '''Saul Goodman'''
    new_data = []
    if need > len(data):
        for j in range(len(data)):
            j %= len(data)
            new_data.append(data[j])
    else:
        for j in range(start_num, end_num, every):
            j %= len(data)
            new_data.append(data[j])
    return new_data
better_call_saul()


from typing import List, Dict

from numpy import sin, cos, sqrt
from shapely.geometry import Polygon, LineString, MultiPoint

from simulator.behaviors.behavior import Behavior

class Camera:
    def __init__(self, W, H, fov, view_distance, reverse=False):
        self.W = W
        self.H = H
        self.fov = fov  # field of view in radians
        self.view_distance = view_distance
        self._reverse = reverse

    def real_world_sensor_value(self, x, y, q, world):
        pass

    def robots_visible(self, x, y, q, objects):
        if self._reverse:
            q = -q
        camera_vision = Polygon(
            [
                (x, y),
                (x + cos(q + self.fov / 2) * self.view_distance, (y + sin(q + self.fov / 2) * self.view_distance)),
                (x + cos(q + -self.fov / 2) * self.view_distance, (y + sin(q + -self.fov / 2) * self.view_distance))
            ])

        return any(camera_vision.covers(obj) for obj in objects)

    # either return l - left, m - middle, r - right or None
    def robot_relative_position(self, x, y, q, robots: List[Behavior]) -> Dict[str, Behavior]:
        if self._reverse:
            q = -q
        robot_positions = {
            "l": [],
            "m": [],
            "r": []
        }
        for robot in robots:
            left_camera_view = Polygon(
                [
                    (x, y),
                    (x + cos(q + -self.fov / 2) * self.view_distance, (y + sin(q + -self.fov / 2) * self.view_distance)),
                    (x + cos(q + -self.fov / 3) * self.view_distance, (y + sin(q + -self.fov / 3) * self.view_distance))
                ])
            if left_camera_view.covers(robot.controller.body):
                robot_positions["l"].append(robot)
            middle_camera_view = Polygon(
                [
                    (x, y),
                    (x + cos(q + -self.fov / 3) * self.view_distance, (y + sin(q + -self.fov / 3) * self.view_distance)),
                    (x + cos(q + self.fov / 3) * self.view_distance, (y + sin(q + self.fov / 3) * self.view_distance))
                ])
            if middle_camera_view.covers(robot.controller.body):
                robot_positions["m"].append(robot)
            right_camera_view = Polygon(
                [
                    (x, y),
                    (x + cos(q + self.fov / 3) * self.view_distance, (y + sin(q + self.fov / 3) * self.view_distance)),
                    (x + cos(q + self.fov / 2) * self.view_distance, (y + sin(q + self.fov / 2) * self.view_distance))
                ])
            if right_camera_view.covers(robot.controller.body):
                robot_positions["r"].append(robot)

        selected_robots = self._filter_invisible_robots(x, y, robot_positions)
        return selected_robots

    def _filter_invisible_robots(self, x, y, robots: Dict[str, List[Behavior]]) -> Dict[str, Behavior]:
        return {
            "l": min([(robot, self._distance_to_object(x, y, robot)) for robot in robots["l"]], key=lambda a: a[1])[0] if robots["l"] else None,
            "m": min([(robot, self._distance_to_object(x, y, robot)) for robot in robots["m"]], key=lambda a: a[1])[0] if robots["m"] else None,
            "r": min([(robot, self._distance_to_object(x, y, robot)) for robot in robots["r"]], key=lambda a: a[1])[0] if robots["r"] else None
        }

    def camera_range(self, x, y, q):
        if self._reverse:
            q = -q
        return f"{x + cos(q + self.fov / 2) * self.view_distance}, {x + cos(q + -self.fov / 2) * self.view_distance}, {y + sin(q + self.fov / 2) * self.view_distance}, {y + sin(q + -self.fov / 2) * self.view_distance}"

    # copied for now
    def _distance_to_object(self, x, y, other_robot: Behavior):
        ray = LineString(
            [
                (x, y),
                (other_robot.controller.x, other_robot.controller.y)
            ])  # a line from robot to an other robot
        s = other_robot.controller.body.intersection(ray)
        if s.is_empty:
            return float('inf')
        if type(s) is MultiPoint:
            mult = list(s.geoms)
            closest_side = list(mult[0].coords)[0]
        else:
            closest_side = list(s.coords)[0]
        return sqrt((closest_side[0] - x) ** 2 + (closest_side[1] - y) ** 2)  # distance to wall


# -*- coding: utf-8 -*-
"""
Created on Tue 23 Feb 2018

@author: z5168331
"""
import pyvisa as visa
import pyneMeas.Instruments.Instrument as Instrument

@Instrument.enableOptions
class Keithley2000(Instrument.Instrument):
    # Default options to set/get when the instrument is passed into the sweeper
    defaultOutput = "sourceLevel"
    defaultInput = "senseLevel"

    def __init__(self, address):
        super(Keithley2000, self).__init__()
        self.dev = visa.ResourceManager().open_resource("GPIB0::"+str(address)+"::INSTR")
        print(self.dev.query("*IDN?")) # Probably should query and check we have the right device

        #self.dev.write("*RST")
        # self.dev.write("*CLS")
        self.type ="Keithley2000"  #We cna check each instrument for its type and react accordingly
        self.name = 'myKeithley2000'
        self.scaleFactor = 1.0
        self._setSenseMode(self._getSenseMode())

#    @Instrument.addOptionSetter("beepEnable")
#    def _setBeepEnable(self, enable):
#        self.dev.write(":SYST:BEEP:STAT " + ("ON" if enable else "OFF"))

    @Instrument.addOptionSetter("name")
    def _setName(self,instrumentName):
         self.name = instrumentName
    @Instrument.addOptionGetter("name")
    def _getName(self):
        return self.name

    @Instrument.addOptionSetter("senseMode")
    def _setSenseMode(self,senseMode):
        if senseMode == 'voltage':
            self.dev.write('CONF:VOLT')
        elif senseMode == 'current':
            self.dev.write('CONF:CURR')

    @Instrument.addOptionGetter("senseMode")
    def _getSenseMode(self):
        senseMode = str(self.dev.query('FUNC?'))[1:-5]
        if senseMode == 'VOLT':
            return 'voltage'
        elif senseMode == 'CURR':
            return 'current'

    @Instrument.addOptionSetter("senseRange")   #Voltage sense ranges: 100mV, 1V, 10V,100V, 1000V
    def _setSenseRange(self, senseRange):
        mode = self.get("senseMode", forceCached = False)
        if (mode == "voltage"): #
            if float(senseRange) in (100E-3,1,10,100,1000):
                self.dev.write("SENS:VOLT:RANG "+str(senseRange))
            else:
                raise ValueError(
                    "\"{}\" is not a valid voltage measurement range for the Keithley2000.".format(senseRange) +
                    " Valid voltage sensing ranges are: 100E-3,1,10,100,1000 Volts and equivalent representations."
                )
        elif (mode == "current"): #
            if float(senseRange) in (10E-3,100E-3,1,3):
                self.dev.write("SENS:CURR:RANG "+str(senseRange))

            else:
                raise ValueError(
                    "\"{}\" is not a valid current measurement range for the Keithley200.".format(senseRange) +
                    " Valid current sensing ranges are: 10E-3,100E-3,1,3 Amps and equivalent representations."
                )
#
    @Instrument.addOptionGetter("senseLevel")
    def _getSenseLevel(self):
        tempData = self.dev.query_ascii_values(":READ?")
        if abs(tempData[0])> 1E10:
            res = float('nan')
        else: res = float(tempData[0])/self.scaleFactor
        return res

#    @Instrument.addOptionSetter("compliance")
#    def _setCompliance(self,compliance,currOrVolt=None): #currOrVolt is an optional paramter. you can use it to specify whether you want to set the current or voltage protection. If not give, the function will change the current compliance when in voltage sourcing mode and vice versa.
#        mode = currOrVolt
#        if mode == None:
#            mode = self.get("sourceMode", forceCached = True)
#
#        if (mode == "voltage" and 0.001E-6 <= float(compliance) <= 1.05): # when sourcing a voltage we want current compliance
#            self.dev.write("SENS:CURR:PROT "+str(compliance))
#
#
#        elif (mode == "current" and 0.2E-3 <= compliance <= 21): # when sourcing a current we want voltage compliance
#            self.dev.write("SENS:VOLT:PROT "+str(compliance))
#
#
#        else:
#            raise ValueError(
#                "\"{}\" is not a valid current/voltage protection value (compliance) for the Keithley2401.".format(compliance) +
#                " Current compliance must be between 1.05 Amps - 0.001E-6 Amps (including bounds). /n Voltage protection values must be between 21 V and 0.2mV."
#            )
#
    @Instrument.addOptionGetter("scaleFactor")
    def _getScaleFactor(self):
        return self.scaleFactor

    @Instrument.addOptionSetter("scaleFactor")
    def _setScaleFactor(self,scaleFactor):
        self.scaleFactor = scaleFactor

    def close(self):
        self.dev.close()

    def goTo(self,target,stepsize= 0.001,delay=0.2):
        return


import cv2, subprocess, os, datetime
from kivy.uix.image import Image
from kivy.clock import Clock
from kivy.properties import ObjectProperty, BooleanProperty, StringProperty, NumericProperty
from kivy.graphics.texture import Texture
from kivy.uix.behaviors import DragBehavior
from kivy.graphics import Rectangle, Color
from kivy.weakmethod import WeakMethod
from threading import Thread, Event
from kivy.lang import Builder
from functools import partial
from src.utils import helper, kivyhelper
from src.modules import constants

from ffpyplayer.player import MediaPlayer
import numpy as np

kv = '''
<KivyCameraNo>:
    drag_rectangle: self.x, self.y, self.width, self.height
    drag_timeout: 10000000
    drag_distance: 0
    keep_ratio: True
'''

Builder.load_string(kv)

class KivyCameraNo(DragBehavior, Image):
    capture = ObjectProperty(None)
    url = StringProperty('')
    resource_type = StringProperty('')
    buffer_rate = NumericProperty(0)
    duration_total = NumericProperty(0)
    duration_total_n = NumericProperty(1)
    duration_current = NumericProperty(0)
    duration_fps = NumericProperty(25)
    reconnect = NumericProperty(0)

    event_capture = None
    default_frame = helper._IMAGES_PATH + 'splash.jpg'
    pipe = None
    f_parent = None
    typeOld = StringProperty('')
    category = StringProperty('')
    data_src = None
    url_remove = StringProperty('')
    player = None

    def __init__(self, **kwargs):
        super(KivyCameraNo, self).__init__(**kwargs)
        self.f_height = 720
        self.show_captured_img(self.default_frame)
        self.data_src = {
            "id": "8c31e461881ac85d932bb461b132f32f",
            "name": "image",
            "url": self.default_frame,
            "type": "IMG"
        }
        self._callback_ref = WeakMethod(self._player_callback)

    def on_touch_up(self, touch):
        if self._get_uid('svavoid') in touch.ud:
            return super(DragBehavior, self).on_touch_up(touch)

        if self._drag_touch and self in [x() for x in touch.grab_list]:
            touch.ungrab(self)
            self._drag_touch = None
            ud = touch.ud[self._get_uid()]
            if ud['mode'] == 'unknown':
                super(DragBehavior, self).on_touch_down(touch)
                Clock.schedule_once(partial(self._do_touch_up, touch), .1)
        else:
            if self._drag_touch is not touch:
                super(DragBehavior, self).on_touch_up(touch)
        return self._get_uid() in touch.ud

    def set_data_source(self, input, category):
        if helper._BASE_PATH+'temp' in self.url:
            self.url_remove = self.url
        self.data_src = input
        self.url = input['url']
        self.resource_type = input['type']
        self.category = category
        self.reconnect = 0
        self.buffer_rate = 0
        self.duration_total = 0
        self.duration_current = 0
        self.duration_total_n = 1
        self.duration_fps = 25
        if self.pipe is not None:
            self.pipe.kill()
        if self.capture is not None:
            self.capture.release()
        self.stop_update_capture()

        fps = 25
        try:
            if self.resource_type == "M3U8" or self.resource_type == "VIDEO" or self.resource_type == 'MP4' or self.resource_type == "RTSP":
                timenow = datetime.datetime.timestamp(datetime.datetime.now())
                output = helper._BASE_PATH+'temp/{}.flv'.format(timenow)
                try:
                    if self.resource_type == 'VIDEO' or self.resource_type == 'MP4' or (self.resource_type == "M3U8" and self.category != constants.LIST_TYPE_PRESENTER):
                        _cap = cv2.VideoCapture(self.url)
                        if _cap.isOpened():
                            fps = _cap.get(cv2.CAP_PROP_FPS)
                            if self.resource_type == 'VIDEO' or self.resource_type == 'MP4':
                                if fps >= 25:
                                    self.duration_total_n = _cap.get(cv2.CAP_PROP_FRAME_COUNT)/_cap.get(cv2.CAP_PROP_FPS)*25
                                    self.duration_total = _cap.get(cv2.CAP_PROP_FRAME_COUNT)/_cap.get(cv2.CAP_PROP_FPS)
                                else:
                                    self.duration_total_n = _cap.get(cv2.CAP_PROP_FRAME_COUNT)
                                    self.duration_total = _cap.get(cv2.CAP_PROP_FRAME_COUNT)/25
                    del _cap
                except Exception as e:
                    print("Exception:", e)

                timeout = 2
                command = ["ffmpeg-win/ffmpeg.exe","-y","-nostats","-i",self.url,'-stream_loop','-1',"-i", helper._BASE_PATH+"media/muted2.mp3","-filter_complex","scale=-1:720","-ar","44100","-ab", "128k","-vb",self.f_parent.v_bitrate,"-r","25",output]
                if self.category == constants.LIST_TYPE_PRESENTER:
                    self.url = self.data_src['rtmp']
                    timeout=3
                    command = ["ffmpeg-win/ffmpeg.exe","-y","-nostats","-i", self.url,"-vsync","1","-af","aresample=async=1:min_hard_comp=0.100000:first_pts=0","-preset","medium","-filter_complex","scale=-1:720","-ar","44100","-ab","128k","-vb",self.f_parent.v_bitrate,"-r","25",output]
                elif self.resource_type == "M3U8":
                    timeout=2
                    command = ["ffmpeg-win/ffmpeg.exe","-y","-nostats","-f", "hls","-i", self.url, "-vsync", "1","-af", "aresample=async=1:min_hard_comp=0.100000:first_pts=0","-flags","+global_header","-filter_complex:0","scale=-1:720","-ar","44100", "-ab", "128k","-vb",self.f_parent.v_bitrate,"-r","25",output]
                elif self.resource_type == "RTSP":
                    timeout=2
                    command = ["ffmpeg-win/ffmpeg.exe","-y","-nostats","-rtsp_flags", "prefer_tcp","-i", self.url,"-pix_fmt", "yuv420p", "-flags","+global_header", "-vsync","1","-ar","44100", "-ab", "128k","-af", "aresample=async=1:min_hard_comp=0.100000","-vf","scale=-1:720","-vb",self.f_parent.v_bitrate,"-r","25",output]
                # else:
                #     if fps < 25:
                #         command = ["ffmpeg-win/ffmpeg.exe","-y","-nostats","-i",self.url,'-stream_loop','-1',"-i", helper._BASE_PATH+"media/muted2.mp3","-ar","44100","-ab", "128k","-af", f"atempo={25/fps}","-vf", f"scale=-1:720,setpts={fps/25}*PTS","-vb",self.f_parent.v_bitrate,"-r","25",output]

                si = subprocess.STARTUPINFO()
                si.dwFlags |= subprocess.STARTF_USESHOWWINDOW
                self.pipe = subprocess.Popen(command, startupinfo=si)
                self.url = output
                Clock.schedule_once(self.process_set_data ,timeout)
            else:
                Clock.schedule_once(self.process_set_data , 0)
        except :
            print("Exception:s")
            Clock.schedule_once(self.process_set_data , 0)

    def process_set_data(self, second):
        try:
            # self.init_capture()
            self.player = MediaPlayer(self.url,callback=self._player_callback,
                thread_lib='SDL',
                loglevel='info')
            self.event_capture = Clock.schedule_interval(self.update2, 1.0 / self.duration_fps)
        except Exception:
            pass

    def _player_callback(self, selector, value):
        print("a")
        if self.player is None:
            return

        print(selector)
        # if selector == 'quit':
        #     def close(*args):
        #         self.unload()
        #     Clock.schedule_once(close, 0)

    def update2(self, dt):
        val = ''
        if val != 'eof':
            frame, val = self.player.get_frame()
            if val != 'eof' and frame is not None:
                img, t = frame
                texture = Texture.create(size=(img.get_size()[0], img.get_size()[1]))
                texture.flip_vertical()
                texture.blit_buffer(img.to_bytearray()[0])
                self.texture = texture

    def init_capture(self):
        try:
            if self.capture is not None:
                self.capture.release()
            self.stop_update_capture()

            if self.resource_type == 'IMG' and '.gif' in self.url:
                self.resource_type = 'GIF'
            if self.resource_type == 'CAMERA':
                self.capture = cv2.VideoCapture(int(self.url))
            else:
                self.capture = cv2.VideoCapture(self.url)

            if self.capture is not None and self.capture.isOpened():
                kivyhelper.getApRoot().loadingMini = False
                kivyhelper.getApRoot().mini_display_status(True)
                self.reconnect = 0
                self.event_capture = Clock.schedule_interval(self.update, 1.0 / self.duration_fps)
                if self.f_parent is not None:
                    if self.f_parent.isStream is False:
                        self.remove_file_flv()
                self.typeOld = self.resource_type
            else:
                if self.capture is not None:
                    self.capture.release()
                if self.reconnect >= 20:
                    self.show_captured_img(self.default_frame)
                    kivyhelper.getApRoot().loadingMini = False
                else:
                    self.reconnect += 1
                    Clock.schedule_once(self.process_set_data,1)

        except Exception as e:
            print("Exception init_capture:", e)
            if self.capture is not None:
                self.capture.release()
            if self.reconnect >= 10:
                self.show_captured_img(self.default_frame)
            else:
                self.reconnect += 1
                Clock.schedule_once(self.process_set_data,1)

    def show_captured_img(self, url=None):
        cap = cv2.VideoCapture(url or self.url)
        ret, frame = cap.read()
        if ret:
            self.update_texture_from_frame(frame)
        cap.release()
        del ret, frame, cap

    def stop_update_capture(self):
        if self.event_capture is not None:
            self.event_capture.cancel()

    def update(self, dt):
        try:
            if self.capture.isOpened():
                if not self.capture.grab():
                    kivyhelper.getApRoot().mini_display_status(False)
                    if 'list' in self.data_src and self.data_src['list'] == constants.LIST_TYPE_PRESENTER and kivyhelper.getApRoot().showMiniD is True and kivyhelper.getApRoot().right_content.tab_presenter.ls_presenter.check_is_online(self.data_src['id']) is False:
                        self.f_parent.hide_camera_mini(False)
                else:
                    ret, frame = self.capture.retrieve()
                    if ret:
                        if self.resource_type == 'VIDEO' or self.resource_type == 'MP4' or self.resource_type == 'M3U8':
                            if self.resource_type == 'VIDEO' or self.resource_type == 'MP4':
                                self.buffer_rate = self.capture.get(cv2.CAP_PROP_POS_FRAMES) / self.duration_total_n
                            self.duration_current = self.capture.get(cv2.CAP_PROP_POS_FRAMES)/self.capture.get(cv2.CAP_PROP_FPS)
                        self.update_texture_from_frame(frame)
        except IOError:
            print("Exception update:")

    def update_texture_from_frame(self, frame):
        try:
            if self.resource_type in ["IMG","GIF"]:
                frame = self.resizeFrame(frame)
            texture = Texture.create(size=(frame.shape[1], frame.shape[0]), colorfmt='bgr')
            texture.flip_vertical()
            texture.blit_buffer(frame.tostring(), colorfmt='bgr', bufferfmt='ubyte')
            self.texture = texture
            del frame, texture
        except IOError:
            print("Exception update_texture_from_frame:")

    def release(self):
        if self.pipe is not None:
            self.pipe.kill()
        if self.capture is not None:
            self.capture.release()
        print('MINICAM','Release')

    def resizeFrame(self, frame):
        if frame is None:
            return frame
        if frame.shape[0] >= 720:
            return frame

        h, w, c = frame.shape
        r = w / h
        nH = self.f_height
        nW = int(nH * r)
        return cv2.resize(frame, (nW, nH), interpolation=cv2.INTER_AREA)

    def remove_file_flv(self):
        try:
            if os.path.exists(self.url_remove):
                os.remove(self.url_remove)
        except:
            pass


from collections import OrderedDict
import logging
import urllib

from flask import current_app, session
from requests.exceptions import ConnectionError, Timeout
import slumber
from slumber.exceptions import SlumberBaseException
import json

from cla_common.constants import ELIGIBILITY_STATES
from cla_public.apps.checker.constants import CATEGORIES
from cla_public.libs.api_proxy import on_timeout
from cla_public.libs.utils import get_locale

log = logging.getLogger(__name__)

class ApiError(Exception):
    def __init__(self, *args, **kwargs):
        for key, value in kwargs.iteritems():
            setattr(self, key, value)
        super(ApiError, self).__init__(*args)

class AlreadySavedApiError(Exception):
    pass

def get_api_connection():
    return slumber.API(
        current_app.config["BACKEND_API"]["url"],
        timeout=current_app.config["API_CLIENT_TIMEOUT"],
        extra_headers={"Accept-Language": get_locale()},
    )

def money_interval(amount, interval="per_month"):
    return {"per_interval_value": amount, "interval_period": interval}

def initialise_eligibility_check(check):
    """Initialize the eligibility check API payload so that we will avoid
    getting 'unknown' eligibility all the time
    """

    def set_(dict_, path, value):
        key, _, rest = path.partition(".")
        if rest:
            dict_[key] = dict_.get(key, {})
            set_(dict_[key], rest, value)
        else:
            dict_[key] = dict_.get(key, value)

    set_(check, "you.income.earnings", money_interval(0))
    set_(check, "you.income.benefits", money_interval(0))
    set_(check, "you.income.tax_credits", money_interval(0))
    set_(check, "you.income.child_benefits", money_interval(0))
    set_(check, "you.income.other_income", money_interval(0))
    set_(check, "you.income.self_employment_drawings", money_interval(0))
    set_(check, "you.income.maintenance_received", money_interval(0))
    set_(check, "you.income.pension", money_interval(0))
    set_(check, "you.income.total", 0)
    set_(check, "you.income.self_employed", False)
    set_(check, "you.savings.credit_balance", 0)
    set_(check, "you.savings.investment_balance", 0)
    set_(check, "you.savings.total", 0)
    set_(check, "you.savings.asset_balance", 0)
    set_(check, "you.savings.bank_balance", 0)
    set_(check, "you.deductions.income_tax", money_interval(0))
    set_(check, "you.deductions.mortgage", money_interval(0))
    set_(check, "you.deductions.childcare", money_interval(0))
    set_(check, "you.deductions.rent", money_interval(0))
    set_(check, "you.deductions.maintenance", money_interval(0))
    set_(check, "you.deductions.national_insurance", money_interval(0))
    set_(check, "you.deductions.criminal_legalaid_contributions", 0)
    set_(check, "partner.income.earnings", money_interval(0))
    set_(check, "partner.income.benefits", money_interval(0))
    set_(check, "partner.income.tax_credits", money_interval(0))
    set_(check, "partner.income.child_benefits", money_interval(0))
    set_(check, "partner.income.other_income", money_interval(0))
    set_(check, "partner.income.self_employment_drawings", money_interval(0))
    set_(check, "partner.income.maintenance_received", money_interval(0))
    set_(check, "partner.income.pension", money_interval(0))
    set_(check, "partner.income.total", 0)
    set_(check, "partner.income.self_employed", False)
    set_(check, "partner.savings.credit_balance", 0)
    set_(check, "partner.savings.investment_balance", 0)
    set_(check, "partner.savings.total", 0)
    set_(check, "partner.savings.asset_balance", 0)
    set_(check, "partner.savings.bank_balance", 0)
    set_(check, "partner.deductions.income_tax", money_interval(0))
    set_(check, "partner.deductions.mortgage", money_interval(0))
    set_(check, "partner.deductions.childcare", money_interval(0))
    set_(check, "partner.deductions.rent", money_interval(0))
    set_(check, "partner.deductions.maintenance", money_interval(0))
    set_(check, "partner.deductions.national_insurance", money_interval(0))
    set_(check, "partner.deductions.criminal_legalaid_contributions", 0)
    set_(check, "dependants_young", 0)
    set_(check, "dependants_old", 0)
    set_(check, "on_passported_benefits", False)
    set_(check, "on_nass_benefits", False)
    set_(check, "specific_benefits", {})

    return check

API_MESSAGE_WARNINGS = ["Case with this Eligibility check already exists."]

def log_api_errors_to_sentry(fn):
    def wrapped(*args, **kwargs):
        sentry = getattr(current_app, "sentry", None)
        try:
            return fn(*args, **kwargs)
        except (ConnectionError, Timeout, SlumberBaseException) as e:
            response = getattr(e, "response", None)
            content = getattr(e, "content", "")

            try:
                errors = json.loads(content)
            except ValueError:
                errors = {}

            if errors.get("eligibility_check", None) == API_MESSAGE_WARNINGS:
                raise AlreadySavedApiError(API_MESSAGE_WARNINGS[0])

            if sentry:
                sentry.captureException(data=errors)
            else:
                log.warning("Failed posting to API: {0}, Response: {1}".format(e, content))

            raise ApiError(e, response=response, errors=errors)

    return wrapped

def ignore_api_error(fun):
    def inner(*args, **kwargs):
        try:
            return fun(*args, **kwargs)
        except ApiError:
            return None

    return inner

@log_api_errors_to_sentry
def get_case_ref_from_api(form=None, payload={}):
    backend = get_api_connection()
    reference = session.checker.get("eligibility_check")

    response = backend.eligibility_check(reference).case_ref.get()
    session.checker["case_ref"] = response["reference"]

@log_api_errors_to_sentry
def post_to_eligibility_check_api(form=None, payload={}):
    backend = get_api_connection()
    reference = session.checker.get("eligibility_check")
    payload = form.api_payload() if form else payload

    if reference is None:
        payload = initialise_eligibility_check(payload)
        response = backend.eligibility_check.post(payload)
        session.checker["eligibility_check"] = response["reference"]
    else:
        backend.eligibility_check(reference).patch(payload)

@on_timeout(response=(ELIGIBILITY_STATES.UNKNOWN, []))
def post_to_is_eligible_api():
    backend = get_api_connection()
    reference = session.checker.get("eligibility_check")

    if reference:
        response = backend.eligibility_check(reference).is_eligible().post({})
        return response.get("is_eligible"), response.get("reasons")
    return None, None

def should_attach_eligibility_check():
    return "eligibility_check" in session.checker

def attach_eligibility_check(payload):
    payload["eligibility_check"] = session.checker.get("eligibility_check")

@log_api_errors_to_sentry
def post_to_case_api(form):
    backend = get_api_connection()
    payload = form.api_payload()

    if should_attach_eligibility_check():
        attach_eligibility_check(payload)

    response = backend.case.post(payload)
    session.checker["case_ref"] = response["reference"]

@on_timeout(response="[]")
def get_organisation_list(**kwargs):
    kwargs["page_size"] = 100
    key = "organisation_list_%s" % urllib.urlencode(kwargs)
    organisation_list = current_app.cache.get(key)
    if not organisation_list:
        backend = get_api_connection()
        api_response = backend.organisation.get(**kwargs)
        organisation_list = api_response["results"]

        one_day = 24 * 60 * 60
        current_app.cache.set(key, organisation_list, timeout=one_day)

    return organisation_list

def get_ordered_organisations_by_category(**kwargs):
    organisations = get_organisation_list(**kwargs)
    categories = OrderedDict((name, []) for field, name, description in CATEGORIES)
    for organisation in organisations:
        for cat in organisation["categories"]:
            if cat["name"] in categories:
                categories[cat["name"]].append(organisation)
                break
    return categories

@ignore_api_error
@log_api_errors_to_sentry
def post_reasons_for_contacting(form=None, payload={}):
    backend = get_api_connection()
    payload = form.api_payload() if form else payload
    return backend.reasons_for_contacting.post(payload)

@ignore_api_error
@log_api_errors_to_sentry
def update_reasons_for_contacting(reference, form=None, payload={}):
    backend = get_api_connection()
    payload = form.api_payload() if form else payload
    return backend.reasons_for_contacting(reference).patch(payload)


#
# @lc app=leetcode id=150 lang=python3
#
# [150] Evaluate Reverse Polish Notation
#
# https://leetcode.com/problems/evaluate-reverse-polish-notation/description/
#
# algorithms
# Medium (46.77%)
# Likes:    6180
# Dislikes: 910
# Total Accepted:    703.2K
# Total Submissions: 1.5M
# Testcase Example:  '["2","1","+","3","*"]'
#
# You are given an array of strings tokens that represents an arithmetic
# expression in a Reverse Polish Notation.
#
# Evaluate the expression. Return an integer that represents the value of the
# expression.
#
# Note that:
#
#
# The valid operators are '+', '-', '*', and '/'.
# Each operand may be an integer or another expression.
# The division between two integers always truncates toward zero.
# There will not be any division by zero.
# The input represents a valid arithmetic expression in a reverse polish
# notation.
# The answer and all the intermediate calculations can be represented in a
# 32-bit integer.
#
#
#
# Example 1:
#
#
# Input: tokens = ["2","1","+","3","*"]
# Output: 9
# Explanation: ((2 + 1) * 3) = 9
#
#
# Example 2:
#
#
# Input: tokens = ["4","13","5","/","+"]
# Output: 6
# Explanation: (4 + (13 / 5)) = 6
#
#
# Example 3:
#
#
# Input: tokens = ["10","6","9","3","+","-11","*","/","*","17","+","5","+"]
# Output: 22
# Explanation: ((10 * (6 / ((9 + 3) * -11))) + 17) + 5
# = ((10 * (6 / (12 * -11))) + 17) + 5
# = ((10 * (6 / -132)) + 17) + 5
# = ((10 * 0) + 17) + 5
# = (0 + 17) + 5
# = 17 + 5
# = 22
#
#
#
# Constraints:
#
#
# 1 <= tokens.length <= 10^4
# tokens[i] is either an operator: "+", "-", "*", or "/", or an integer in the
# range [-200, 200].
#
#
#
from collections import deque
from math import trunc
from typing import List

# @lc code=start

class Solution:
    def evalRPN(self, tokens: List[str]) -> int:
        stack = deque()
        ops = {"+", "-", "*", "/"}
        for t in tokens:
            if t in ops:
                r = stack.pop()
                l = stack.pop()
                if t == "+":
                    stack.append(l + r)
                elif t == "-":
                    stack.append(l - r)
                elif t == "*":
                    stack.append(l * r)
                else:
                    stack.append(trunc(l / r))
            else:
                stack.append(int(t))
        return stack.pop()

# @lc code=end