# Copyright (c) 2021, NVIDIA CORPORATION.
"""Base class for Frame types that have an index."""

from __future__ import annotations

import operator
import warnings
from collections import Counter, abc
from typing import Callable, Type, TypeVar
from uuid import uuid4

import cupy as cp
import numpy as np
import pandas as pd
from nvtx import annotate

import cudf
import cudf._lib as libcudf
from cudf._typing import ColumnLike
from cudf.api.types import (
    _is_non_decimal_numeric_dtype,
    is_bool_dtype,
    is_categorical_dtype,
    is_integer_dtype,
    is_list_like,
)
from cudf.core.column import arange, as_column
from cudf.core.column_accessor import ColumnAccessor
from cudf.core.frame import Frame
from cudf.core.index import Index, RangeIndex, _index_from_columns
from cudf.core.multiindex import MultiIndex
from cudf.core.udf.utils import _compile_or_get, _supported_cols_from_frame
from cudf.utils.utils import cached_property

doc_reset_index_template = """
        Reset the index of the {klass}, or a level of it.

        Parameters
        ----------
        level : int, str, tuple, or list, default None
            Only remove the given levels from the index. Removes all levels by
            default.
        drop : bool, default False
            Do not try to insert index into dataframe columns. This resets
            the index to the default integer index.
{argument}
        inplace : bool, default False
            Modify the DataFrame in place (do not create a new object).

        Returns
        -------
        {return_type}
            {klass} with the new index or None if ``inplace=True``.{return_doc}

        Examples
        --------
        {example}
"""

def _indices_from_labels(obj, labels):
    from cudf.core.column import column

    if not isinstance(labels, cudf.MultiIndex):
        labels = column.as_column(labels)

        if is_categorical_dtype(obj.index):
            labels = labels.astype("category")
            codes = labels.codes.astype(obj.index._values.codes.dtype)
            labels = column.build_categorical_column(
                categories=labels.dtype.categories,
                codes=codes,
                ordered=labels.dtype.ordered,
            )
        else:
            labels = labels.astype(obj.index.dtype)

    # join is not guaranteed to maintain the index ordering
    # so we will sort it with its initial ordering which is stored
    # in column "__"
    lhs = cudf.DataFrame({"__": arange(len(labels))}, index=labels)
    rhs = cudf.DataFrame({"_": arange(len(obj))}, index=obj.index)
    return lhs.join(rhs).sort_values("__")["_"]

def _get_label_range_or_mask(index, start, stop, step):
    if (
        not (start is None and stop is None)
        and type(index) is cudf.core.index.DatetimeIndex
        and index.is_monotonic is False
    ):
        start = pd.to_datetime(start)
        stop = pd.to_datetime(stop)
        if start is not None and stop is not None:
            if start > stop:
                return slice(0, 0, None)
            # TODO: Once Index binary ops are updated to support logical_and,
            # can use that instead of using cupy.
            boolean_mask = cp.logical_and((index >= start), (index <= stop))
        elif start is not None:
            boolean_mask = index >= start
        else:
            boolean_mask = index <= stop
        return boolean_mask
    else:
        start, stop = index.find_label_range(start, stop)
        return slice(start, stop, step)

class _FrameIndexer:
    """Parent class for indexers."""

    def __init__(self, frame):
        self._frame = frame

_LocIndexerClass = TypeVar("_LocIndexerClass", bound="_FrameIndexer")
_IlocIndexerClass = TypeVar("_IlocIndexerClass", bound="_FrameIndexer")

T = TypeVar("T", bound="IndexedFrame")

class IndexedFrame(Frame):
    """A frame containing an index.

    This class encodes the common behaviors for core user-facing classes like
    DataFrame and Series that consist of a sequence of columns along with a
    special set of index columns.

    Parameters
    ----------
    data : dict
        An dict mapping column names to Columns
    index : Table
        A Frame representing the (optional) index columns.
    """

    # mypy can't handle bound type variables as class members
    _loc_indexer_type: Type[_LocIndexerClass]  # type: ignore
    _iloc_indexer_type: Type[_IlocIndexerClass]  # type: ignore
    _index: cudf.core.index.BaseIndex

    def __init__(self, data=None, index=None):
        super().__init__(data=data, index=index)

    def to_dict(self, *args, **kwargs):  # noqa: D102
        raise TypeError(
            "cuDF does not support conversion to host memory "
            "via `to_dict()` method. Consider using "
            "`.to_pandas().to_dict()` to construct a Python dictionary."
        )

    @property
    def index(self):
        """Get the labels for the rows."""
        return self._index

    @index.setter
    def index(self, value):
        old_length = len(self)
        new_length = len(value)

        # A DataFrame with 0 columns can have an index of arbitrary length.
        if len(self._data) > 0 and new_length != old_length:
            raise ValueError(
                f"Length mismatch: Expected axis has {old_length} elements, "
                f"new values have {len(value)} elements"
            )
        self._index = Index(value)

    @cached_property
    def loc(self):
        """Select rows and columns by label or boolean mask.

        Examples
        --------
        **Series**

        >>> import cudf
        >>> series = cudf.Series([10, 11, 12], index=['a', 'b', 'c'])
        >>> series
        a    10
        b    11
        c    12
        dtype: int64
        >>> series.loc['b']
        11

        **DataFrame**

        DataFrame with string index.

        >>> df
           a  b
        a  0  5
        b  1  6
        c  2  7
        d  3  8
        e  4  9

        Select a single row by label.

        >>> df.loc['a']
        a    0
        b    5
        Name: a, dtype: int64

        Select multiple rows and a single column.

        >>> df.loc[['a', 'c', 'e'], 'b']
        a    5
        c    7
        e    9
        Name: b, dtype: int64

        Selection by boolean mask.

        >>> df.loc[df.a > 2]
           a  b
        d  3  8
        e  4  9

        Setting values using loc.

        >>> df.loc[['a', 'c', 'e'], 'a'] = 0
        >>> df
           a  b
        a  0  5
        b  1  6
        c  0  7
        d  3  8
        e  0  9

        """
        return self._loc_indexer_type(self)

    @cached_property
    def iloc(self):
        """Select values by position.

        Examples
        --------
        **Series**

        >>> import cudf
        >>> s = cudf.Series([10, 20, 30])
        >>> s
        0    10
        1    20
        2    30
        dtype: int64
        >>> s.iloc[2]
        30

        **DataFrame**

        Selecting rows and column by position.

        Examples
        --------
        >>> df = cudf.DataFrame({'a': range(20),
        ...                      'b': range(20),
        ...                      'c': range(20)})

        Select a single row using an integer index.

        >>> df.iloc[1]
        a    1
        b    1
        c    1
        Name: 1, dtype: int64

        Select multiple rows using a list of integers.

        >>> df.iloc[[0, 2, 9, 18]]
              a    b    c
         0    0    0    0
         2    2    2    2
         9    9    9    9
        18   18   18   18

        Select rows using a slice.

        >>> df.iloc[3:10:2]
             a    b    c
        3    3    3    3
        5    5    5    5
        7    7    7    7
        9    9    9    9

        Select both rows and columns.

        >>> df.iloc[[1, 3, 5, 7], 2]
        1    1
        3    3
        5    5
        7    7
        Name: c, dtype: int64

        Setting values in a column using iloc.

        >>> df.iloc[:4] = 0
        >>> df
           a  b  c
        0  0  0  0
        1  0  0  0
        2  0  0  0
        3  0  0  0
        4  4  4  4
        5  5  5  5
        6  6  6  6
        7  7  7  7
        8  8  8  8
        9  9  9  9
        [10 more rows]

        """
        return self._iloc_indexer_type(self)

    @annotate("SORT_INDEX", color="red", domain="cudf_python")
    def sort_index(
        self,
        axis=0,
        level=None,
        ascending=True,
        inplace=False,
        kind=None,
        na_position="last",
        sort_remaining=True,
        ignore_index=False,
        key=None,
    ):
        """Sort object by labels (along an axis).

        Parameters
        ----------
        axis : {0 or ‘index’, 1 or ‘columns’}, default 0
            The axis along which to sort. The value 0 identifies the rows,
            and 1 identifies the columns.
        level : int or level name or list of ints or list of level names
            If not None, sort on values in specified index level(s).
            This is only useful in the case of MultiIndex.
        ascending : bool, default True
            Sort ascending vs. descending.
        inplace : bool, default False
            If True, perform operation in-place.
        kind : sorting method such as `quick sort` and others.
            Not yet supported.
        na_position : {‘first’, ‘last’}, default ‘last’
            Puts NaNs at the beginning if first; last puts NaNs at the end.
        sort_remaining : bool, default True
            Not yet supported
        ignore_index : bool, default False
            if True, index will be replaced with RangeIndex.
        key : callable, optional
            If not None, apply the key function to the index values before
            sorting. This is similar to the key argument in the builtin
            sorted() function, with the notable difference that this key
            function should be vectorized. It should expect an Index and return
            an Index of the same shape. For MultiIndex inputs, the key is
            applied per level.

        Returns
        -------
        Frame or None

        Notes
        -----
        Difference from pandas:
          * Not supporting: kind, sort_remaining=False

        Examples
        --------
        **Series**
        >>> import cudf
        >>> series = cudf.Series(['a', 'b', 'c', 'd'], index=[3, 2, 1, 4])
        >>> series
        3    a
        2    b
        1    c
        4    d
        dtype: object
        >>> series.sort_index()
        1    c
        2    b
        3    a
        4    d
        dtype: object

        Sort Descending

        >>> series.sort_index(ascending=False)
        4    d
        3    a
        2    b
        1    c
        dtype: object

        **DataFrame**
        >>> df = cudf.DataFrame(
        ... {"b":[3, 2, 1], "a":[2, 1, 3]}, index=[1, 3, 2])
        >>> df.sort_index(axis=0)
           b  a
        1  3  2
        2  1  3
        3  2  1
        >>> df.sort_index(axis=1)
           a  b
        1  2  3
        3  1  2
        2  3  1
        """
        if kind is not None:
            raise NotImplementedError("kind is not yet supported")

        if not sort_remaining:
            raise NotImplementedError(
                "sort_remaining == False is not yet supported"
            )

        if key is not None:
            raise NotImplementedError("key is not yet supported.")

        if na_position not in {"first", "last"}:
            raise ValueError(f"invalid na_position: {na_position}")

        if axis in (0, "index"):
            idx = self.index
            if isinstance(idx, MultiIndex):
                if level is not None:
                    # Pandas doesn't handle na_position in case of MultiIndex.
                    na_position = "first" if ascending is True else "last"
                    labels = [
                        idx._get_level_label(lvl)
                        for lvl in (level if is_list_like(level) else (level,))
                    ]
                    # Explicitly construct a Frame rather than using type(self)
                    # to avoid constructing a SingleColumnFrame (e.g. Series).
                    idx = Frame._from_data(idx._data.select_by_label(labels))

                inds = idx._get_sorted_inds(
                    ascending=ascending, na_position=na_position
                )
                out = self._gather(inds)
                # TODO: frame factory function should handle multilevel column
                # names
                if isinstance(
                    self, cudf.core.dataframe.DataFrame
                ) and isinstance(
                    self.columns, pd.core.indexes.multi.MultiIndex
                ):
                    out.columns = self.columns
            elif (ascending and idx.is_monotonic_increasing) or (
                not ascending and idx.is_monotonic_decreasing
            ):
                out = self.copy()
            else:
                inds = idx.argsort(
                    ascending=ascending, na_position=na_position
                )
                out = self._gather(inds)
                if isinstance(
                    self, cudf.core.dataframe.DataFrame
                ) and isinstance(
                    self.columns, pd.core.indexes.multi.MultiIndex
                ):
                    out.columns = self.columns
        else:
            labels = sorted(self._data.names, reverse=not ascending)
            out = self[labels]

        if ignore_index is True:
            out = out.reset_index(drop=True)
        return self._mimic_inplace(out, inplace=inplace)

    def hash_values(self, method="murmur3"):
        """Compute the hash of values in this column.

        Parameters
        ----------
        method : {'murmur3', 'md5'}, default 'murmur3'
            Hash function to use:
            * murmur3: MurmurHash3 hash function.
            * md5: MD5 hash function.

        Returns
        -------
        Series
            A Series with hash values.

        Examples
        --------
        **Series**

        >>> import cudf
        >>> series = cudf.Series([10, 120, 30])
        >>> series
        0     10
        1    120
        2     30
        dtype: int64
        >>> series.hash_values(method="murmur3")
        0   -1930516747
        1     422619251
        2    -941520876
        dtype: int32
        >>> series.hash_values(method="md5")
        0    7be4bbacbfdb05fb3044e36c22b41e8b
        1    947ca8d2c5f0f27437f156cfbfab0969
        2    d0580ef52d27c043c8e341fd5039b166
        dtype: object

        **DataFrame**

        >>> import cudf
        >>> df = cudf.DataFrame({"a": [10, 120, 30], "b": [0.0, 0.25, 0.50]})
        >>> df
             a     b
        0   10  0.00
        1  120  0.25
        2   30  0.50
        >>> df.hash_values(method="murmur3")
        0    -330519225
        1    -397962448
        2   -1345834934
        dtype: int32
        >>> df.hash_values(method="md5")
        0    57ce879751b5169c525907d5c563fae1
        1    948d6221a7c4963d4be411bcead7e32b
        2    fe061786ea286a515b772d91b0dfcd70
        dtype: object
        """
        # Note that both Series and DataFrame return Series objects from this
        # calculation, necessitating the unfortunate circular reference to the
        # child class here.
        return cudf.Series._from_data(
            {None: libcudf.hash.hash(self, method)}, index=self.index
        )

    def _gather(
        self, gather_map, keep_index=True, nullify=False, check_bounds=True
    ):
        """Gather rows of frame specified by indices in `gather_map`.

        Skip bounds checking if check_bounds is False.
        Set rows to null for all out of bound indices if nullify is `True`.
        """
        gather_map = cudf.core.column.as_column(gather_map)

        # TODO: For performance, the check and conversion of gather map should
        # be done by the caller. This check will be removed in future release.
        if not is_integer_dtype(gather_map.dtype):
            gather_map = gather_map.astype("int32")

        if not libcudf.copying._gather_map_is_valid(
            gather_map, len(self), check_bounds, nullify
        ):
            raise IndexError("Gather map index is out of bounds.")

        return self._from_columns_like_self(
            libcudf.copying.gather(
                list(self._index._columns + self._columns)
                if keep_index
                else list(self._columns),
                gather_map,
                nullify=nullify,
            ),
            self._column_names,
            self._index.names if keep_index else None,
        )

    def _positions_from_column_names(
        self, column_names, offset_by_index_columns=False
    ):
        """Map each column name into their positions in the frame.

        Return positions of the provided column names, offset by the number of
        index columns if `offset_by_index_columns` is True. The order of
        indices returned corresponds to the column order in this Frame.
        """
        num_index_columns = (
            len(self._index._data) if offset_by_index_columns else 0
        )
        return [
            i + num_index_columns
            for i, name in enumerate(self._column_names)
            if name in set(column_names)
        ]

    def drop_duplicates(
        self,
        subset=None,
        keep="first",
        nulls_are_equal=True,
        ignore_index=False,
    ):
        """
        Drop duplicate rows in frame.

        subset : list, optional
            List of columns to consider when dropping rows.
        keep : ["first", "last", False]
            "first" will keep the first duplicate entry, "last" will keep the
            last duplicate entry, and False will drop all duplicates.
        nulls_are_equal: bool, default True
            Null elements are considered equal to other null elements.
        ignore_index: bool, default False
            If True, the resulting axis will be labeled 0, 1, ..., n - 1.
        """
        if subset is None:
            subset = self._column_names
        elif (
            not np.iterable(subset)
            or isinstance(subset, str)
            or isinstance(subset, tuple)
            and subset in self._data.names
        ):
            subset = (subset,)
        diff = set(subset) - set(self._data)
        if len(diff) != 0:
            raise KeyError(f"columns {diff} do not exist")
        subset_cols = [name for name in self._column_names if name in subset]
        if len(subset_cols) == 0:
            return self.copy(deep=True)

        keys = self._positions_from_column_names(
            subset, offset_by_index_columns=not ignore_index
        )
        return self._from_columns_like_self(
            libcudf.stream_compaction.drop_duplicates(
                list(self._columns)
                if ignore_index
                else list(self._index._columns + self._columns),
                keys=keys,
                keep=keep,
                nulls_are_equal=nulls_are_equal,
            ),
            self._column_names,
            self._index.names if not ignore_index else None,
        )

    def add_prefix(self, prefix):
        """
        Prefix labels with string `prefix`.

        For Series, the row labels are prefixed.
        For DataFrame, the column labels are prefixed.

        Parameters
        ----------
        prefix : str
            The string to add before each label.

        Returns
        -------
        Series or DataFrame
            New Series with updated labels or DataFrame with updated labels.

        See Also
        --------
        Series.add_suffix: Suffix row labels with string 'suffix'.
        DataFrame.add_suffix: Suffix column labels with string 'suffix'.

        Examples
        --------
        **Series**
        >>> s = cudf.Series([1, 2, 3, 4])
        >>> s
        0    1
        1    2
        2    3
        3    4
        dtype: int64
        >>> s.add_prefix('item_')
        item_0    1
        item_1    2
        item_2    3
        item_3    4
        dtype: int64

        **DataFrame**
        >>> df = cudf.DataFrame({'A': [1, 2, 3, 4], 'B': [3, 4, 5, 6]})
        >>> df
           A  B
        0  1  3
        1  2  4
        2  3  5
        3  4  6
        >>> df.add_prefix('col_')
             col_A  col_B
        0       1       3
        1       2       4
        2       3       5
        3       4       6
        """
        raise NotImplementedError(
            "`IndexedFrame.add_prefix` not currently implemented. \
                Use `Series.add_prefix` or `DataFrame.add_prefix`"
        )

    def add_suffix(self, suffix):
        """
        Suffix labels with string `suffix`.

        For Series, the row labels are suffixed.
        For DataFrame, the column labels are suffixed.

        Parameters
        ----------
        prefix : str
            The string to add after each label.

        Returns
        -------
        Series or DataFrame
            New Series with updated labels or DataFrame with updated labels.

        See Also
        --------
        Series.add_prefix: prefix row labels with string 'prefix'.
        DataFrame.add_prefix: Prefix column labels with string 'prefix'.

        Examples
        --------
        **Series**
        >>> s = cudf.Series([1, 2, 3, 4])
        >>> s
        0    1
        1    2
        2    3
        3    4
        dtype: int64
        >>> s.add_suffix('_item')
        0_item    1
        1_item    2
        2_item    3
        3_item    4
        dtype: int64

        **DataFrame**
        >>> df = cudf.DataFrame({'A': [1, 2, 3, 4], 'B': [3, 4, 5, 6]})
        >>> df
           A  B
        0  1  3
        1  2  4
        2  3  5
        3  4  6
        >>> df.add_suffix('_col')
             A_col  B_col
        0       1       3
        1       2       4
        2       3       5
        3       4       6
        """
        raise NotImplementedError(
            "`IndexedFrame.add_suffix` not currently implemented. \
                Use `Series.add_suffix` or `DataFrame.add_suffix`"
        )

    @annotate("APPLY", color="purple", domain="cudf_python")
    def _apply(self, func, kernel_getter, *args, **kwargs):
        """Apply `func` across the rows of the frame."""
        if kwargs:
            raise ValueError("UDFs using **kwargs are not yet supported.")

        try:
            kernel, retty = _compile_or_get(
                self, func, args, kernel_getter=kernel_getter
            )
        except Exception as e:
            raise ValueError(
                "user defined function compilation failed."
            ) from e

        # Mask and data column preallocated
        ans_col = cp.empty(len(self), dtype=retty)
        ans_mask = cudf.core.column.column_empty(len(self), dtype="bool")
        launch_args = [(ans_col, ans_mask), len(self)]
        offsets = []

        # if _compile_or_get succeeds, it is safe to create a kernel that only
        # consumes the columns that are of supported dtype
        for col in _supported_cols_from_frame(self).values():
            data = col.data
            mask = col.mask
            if mask is None:
                launch_args.append(data)
            else:
                launch_args.append((data, mask))
            offsets.append(col.offset)
        launch_args += offsets
        launch_args += list(args)

        try:
            kernel.forall(len(self))(*launch_args)
        except Exception as e:
            raise RuntimeError("UDF kernel execution failed.") from e

        col = as_column(ans_col)
        col.set_base_mask(libcudf.transform.bools_to_mask(ans_mask))
        result = cudf.Series._from_data({None: col}, self._index)

        return result

    def sort_values(
        self,
        by,
        axis=0,
        ascending=True,
        inplace=False,
        kind="quicksort",
        na_position="last",
        ignore_index=False,
    ):
        """Sort by the values along either axis.

        Parameters
        ----------
        by : str or list of str
            Name or list of names to sort by.
        ascending : bool or list of bool, default True
            Sort ascending vs. descending. Specify list for multiple sort
            orders. If this is a list of bools, must match the length of the
            by.
        na_position : {‘first’, ‘last’}, default ‘last’
            'first' puts nulls at the beginning, 'last' puts nulls at the end
        ignore_index : bool, default False
            If True, index will not be sorted.

        Returns
        -------
        Frame : Frame with sorted values.

        Notes
        -----
        Difference from pandas:
          * Support axis='index' only.
          * Not supporting: inplace, kind

        Examples
        --------
        >>> import cudf
        >>> df = cudf.DataFrame()
        >>> df['a'] = [0, 1, 2]
        >>> df['b'] = [-3, 2, 0]
        >>> df.sort_values('b')
           a  b
        0  0 -3
        2  2  0
        1  1  2
        """
        if na_position not in {"first", "last"}:
            raise ValueError(f"invalid na_position: {na_position}")
        if inplace:
            raise NotImplementedError("`inplace` not currently implemented.")
        if kind != "quicksort":
            if kind not in {"mergesort", "heapsort", "stable"}:
                raise AttributeError(
                    f"{kind} is not a valid sorting algorithm for "
                    f"'DataFrame' object"
                )
            warnings.warn(
                f"GPU-accelerated {kind} is currently not supported, "
                f"defaulting to quicksort."
            )
        if axis != 0:
            raise NotImplementedError("`axis` not currently implemented.")

        if len(self) == 0:
            return self

        # argsort the `by` column
        out = self._gather(
            self._get_columns_by_label(by)._get_sorted_inds(
                ascending=ascending, na_position=na_position
            ),
            keep_index=not ignore_index,
        )
        if isinstance(self, cudf.core.dataframe.DataFrame) and isinstance(
            self.columns, pd.core.indexes.multi.MultiIndex
        ):
            out.columns = self.columns
        return out

    def _n_largest_or_smallest(self, largest, n, columns, keep):
        # Get column to operate on
        if isinstance(columns, str):
            columns = [columns]

        if len(self) == 0:
            return self

        if keep == "first":
            if n < 0:
                n = 0

            # argsort the `by` column
            return self._gather(
                self._get_columns_by_label(columns)._get_sorted_inds(
                    ascending=not largest
                )[:n],
                keep_index=True,
                check_bounds=False,
            )
        elif keep == "last":
            indices = self._get_columns_by_label(columns)._get_sorted_inds(
                ascending=largest
            )

            if n <= 0:
                # Empty slice.
                indices = indices[0:0]
            else:
                indices = indices[: -n - 1 : -1]
            return self._gather(indices, keep_index=True, check_bounds=False)
        else:
            raise ValueError('keep must be either "first", "last"')

    def _align_to_index(
        self: T,
        index: ColumnLike,
        how: str = "outer",
        sort: bool = True,
        allow_non_unique: bool = False,
    ) -> T:
        index = cudf.core.index.as_index(index)

        if self.index.equals(index):
            return self
        if not allow_non_unique:
            if not self.index.is_unique or not index.is_unique:
                raise ValueError("Cannot align indices with non-unique values")

        lhs = cudf.DataFrame._from_data(self._data, index=self.index)
        rhs = cudf.DataFrame._from_data({}, index=index)

        # create a temporary column that we will later sort by
        # to recover ordering after index alignment.
        sort_col_id = str(uuid4())
        if how == "left":
            lhs[sort_col_id] = arange(len(lhs))
        elif how == "right":
            rhs[sort_col_id] = arange(len(rhs))

        result = lhs.join(rhs, how=how, sort=sort)
        if how in ("left", "right"):
            result = result.sort_values(sort_col_id)
            del result[sort_col_id]

        result = self.__class__._from_data(result._data, index=result.index)
        result._data.multiindex = self._data.multiindex
        result._data._level_names = self._data._level_names
        result.index.names = self.index.names

        return result

    def round(self, decimals=0, how="half_even"):
        """
        Round to a variable number of decimal places.

        Parameters
        ----------
        decimals : int, dict, Series
            Number of decimal places to round each column to. This parameter
            must be an int for a Series. For a DataFrame, a dict or a Series
            are also valid inputs. If an int is given, round each column to the
            same number of places. Otherwise dict and Series round to variable
            numbers of places. Column names should be in the keys if
            `decimals` is a dict-like, or in the index if `decimals` is a
            Series. Any columns not included in `decimals` will be left as is.
            Elements of `decimals` which are not columns of the input will be
            ignored.
        how : str, optional
            Type of rounding. Can be either "half_even" (default)
            or "half_up" rounding.

        Returns
        -------
        Series or DataFrame
            A Series or DataFrame with the affected columns rounded to the
            specified number of decimal places.

        Examples
        --------
        **Series**

        >>> s = cudf.Series([0.1, 1.4, 2.9])
        >>> s.round()
        0    0.0
        1    1.0
        2    3.0
        dtype: float64

        **DataFrame**

        >>> df = cudf.DataFrame(
        ...     [(.21, .32), (.01, .67), (.66, .03), (.21, .18)],
        ...     columns=['dogs', 'cats'],
        ... )
        >>> df
           dogs  cats
        0  0.21  0.32
        1  0.01  0.67
        2  0.66  0.03
        3  0.21  0.18

        By providing an integer each column is rounded to the same number
        of decimal places.

        >>> df.round(1)
           dogs  cats
        0   0.2   0.3
        1   0.0   0.7
        2   0.7   0.0
        3   0.2   0.2

        With a dict, the number of places for specific columns can be
        specified with the column names as keys and the number of decimal
        places as values.

        >>> df.round({'dogs': 1, 'cats': 0})
           dogs  cats
        0   0.2   0.0
        1   0.0   1.0
        2   0.7   0.0
        3   0.2   0.0

        Using a Series, the number of places for specific columns can be
        specified with the column names as the index and the number of
        decimal places as the values.

        >>> decimals = cudf.Series([0, 1], index=['cats', 'dogs'])
        >>> df.round(decimals)
           dogs  cats
        0   0.2   0.0
        1   0.0   1.0
        2   0.7   0.0
        3   0.2   0.0
        """
        if isinstance(decimals, cudf.Series):
            decimals = decimals.to_pandas()

        if isinstance(decimals, pd.Series):
            if not decimals.index.is_unique:
                raise ValueError("Index of decimals must be unique")
            decimals = decimals.to_dict()
        elif isinstance(decimals, int):
            decimals = {name: decimals for name in self._column_names}
        elif not isinstance(decimals, abc.Mapping):
            raise TypeError(
                "decimals must be an integer, a dict-like or a Series"
            )

        cols = {
            name: col.round(decimals[name], how=how)
            if (name in decimals and _is_non_decimal_numeric_dtype(col.dtype))
            else col.copy(deep=True)
            for name, col in self._data.items()
        }

        return self.__class__._from_data(
            data=cudf.core.column_accessor.ColumnAccessor(
                cols,
                multiindex=self._data.multiindex,
                level_names=self._data.level_names,
            ),
            index=self._index,
        )

    def resample(
        self,
        rule,
        axis=0,
        closed=None,
        label=None,
        convention="start",
        kind=None,
        loffset=None,
        base=None,
        on=None,
        level=None,
        origin="start_day",
        offset=None,
    ):
        """
        Convert the frequency of ("resample") the given time series data.

        Parameters
        ----------
        rule: str
            The offset string representing the frequency to use.
            Note that DateOffset objects are not yet supported.
        closed: {"right", "left"}, default None
            Which side of bin interval is closed. The default is
            "left" for all frequency offsets except for "M" and "W",
            which have a default of "right".
        label: {"right", "left"}, default None
            Which bin edge label to label bucket with. The default is
            "left" for all frequency offsets except for "M" and "W",
            which have a default of "right".
        on: str, optional
            For a DataFrame, column to use instead of the index for
            resampling.  Column must be a datetime-like.
        level: str or int, optional
            For a MultiIndex, level to use instead of the index for
            resampling.  The level must be a datetime-like.

        Returns
        -------
        A Resampler object

        Examples
        --------
        First, we create a time series with 1 minute intervals:

        >>> index = cudf.date_range(start="2001-01-01", periods=10, freq="1T")
        >>> sr = cudf.Series(range(10), index=index)
        >>> sr
        2001-01-01 00:00:00    0
        2001-01-01 00:01:00    1
        2001-01-01 00:02:00    2
        2001-01-01 00:03:00    3
        2001-01-01 00:04:00    4
        2001-01-01 00:05:00    5
        2001-01-01 00:06:00    6
        2001-01-01 00:07:00    7
        2001-01-01 00:08:00    8
        2001-01-01 00:09:00    9
        dtype: int64

        Downsampling to 3 minute intervals, followed by a "sum" aggregation:

        >>> sr.resample("3T").sum()
        2001-01-01 00:00:00     3
        2001-01-01 00:03:00    12
        2001-01-01 00:06:00    21
        2001-01-01 00:09:00     9
        dtype: int64

        Use the right side of each interval to label the bins:

        >>> sr.resample("3T", label="right").sum()
        2001-01-01 00:03:00     3
        2001-01-01 00:06:00    12
        2001-01-01 00:09:00    21
        2001-01-01 00:12:00     9
        dtype: int64

        Close the right side of the interval instead of the left:

        >>> sr.resample("3T", closed="right").sum()
        2000-12-31 23:57:00     0
        2001-01-01 00:00:00     6
        2001-01-01 00:03:00    15
        2001-01-01 00:06:00    24
        dtype: int64

        Upsampling to 30 second intervals:

        >>> sr.resample("30s").asfreq()[:5]  # show the first 5 rows
        2001-01-01 00:00:00       0
        2001-01-01 00:00:30    <NA>
        2001-01-01 00:01:00       1
        2001-01-01 00:01:30    <NA>
        2001-01-01 00:02:00       2
        dtype: int64

        Upsample and fill nulls using the "bfill" method:

        >>> sr.resample("30s").bfill()[:5]
        2001-01-01 00:00:00    0
        2001-01-01 00:00:30    1
        2001-01-01 00:01:00    1
        2001-01-01 00:01:30    2
        2001-01-01 00:02:00    2
        dtype: int64

        Resampling by a specified column of a Dataframe:

        >>> df = cudf.DataFrame({
        ...     "price": [10, 11, 9, 13, 14, 18, 17, 19],
        ...     "volume": [50, 60, 40, 100, 50, 100, 40, 50],
        ...     "week_starting": cudf.date_range(
        ...         "2018-01-01", periods=8, freq="7D"
        ...     )
        ... })
        >>> df
        price  volume week_starting
        0     10      50    2018-01-01
        1     11      60    2018-01-08
        2      9      40    2018-01-15
        3     13     100    2018-01-22
        4     14      50    2018-01-29
        5     18     100    2018-02-05
        6     17      40    2018-02-12
        7     19      50    2018-02-19
        >>> df.resample("M", on="week_starting").mean()
                       price     volume
        week_starting
        2018-01-31      11.4  60.000000
        2018-02-28      18.0  63.333333

        Notes
        -----
        Note that the dtype of the index (or the 'on' column if using
        'on=') in the result will be of a frequency closest to the
        resampled frequency.  For example, if resampling from
        nanoseconds to milliseconds, the index will be of dtype
        'datetime64[ms]'.
        """
        import cudf.core.resample

        if (axis, convention, kind, loffset, base, origin, offset) != (
            0,
            "start",
            None,
            None,
            None,
            "start_day",
            None,
        ):
            raise NotImplementedError(
                "The following arguments are not "
                "currently supported by resample:\n\n"
                "- axis\n"
                "- convention\n"
                "- kind\n"
                "- loffset\n"
                "- base\n"
                "- origin\n"
                "- offset"
            )
        by = cudf.Grouper(
            key=on, freq=rule, closed=closed, label=label, level=level
        )
        return (
            cudf.core.resample.SeriesResampler(self, by=by)
            if isinstance(self, cudf.Series)
            else cudf.core.resample.DataFrameResampler(self, by=by)
        )

    def dropna(
        self, axis=0, how="any", thresh=None, subset=None, inplace=False
    ):
        """
        Drop rows (or columns) containing nulls from a Column.

        Parameters
        ----------
        axis : {0, 1}, optional
            Whether to drop rows (axis=0, default) or columns (axis=1)
            containing nulls.
        how : {"any", "all"}, optional
            Specifies how to decide whether to drop a row (or column).
            any (default) drops rows (or columns) containing at least
            one null value. all drops only rows (or columns) containing
            *all* null values.
        thresh: int, optional
            If specified, then drops every row (or column) containing
            less than `thresh` non-null values
        subset : list, optional
            List of columns to consider when dropping rows (all columns
            are considered by default). Alternatively, when dropping
            columns, subset is a list of rows to consider.
        inplace : bool, default False
            If True, do operation inplace and return None.

        Returns
        -------
        Copy of the DataFrame with rows/columns containing nulls dropped.

        See also
        --------
        cudf.DataFrame.isna
            Indicate null values.

        cudf.DataFrame.notna
            Indicate non-null values.

        cudf.DataFrame.fillna
            Replace null values.

        cudf.Series.dropna
            Drop null values.

        cudf.Index.dropna
            Drop null indices.

        Examples
        --------
        >>> import cudf
        >>> df = cudf.DataFrame({"name": ['Alfred', 'Batman', 'Catwoman'],
        ...                    "toy": ['Batmobile', None, 'Bullwhip'],
        ...                    "born": [np.datetime64("1940-04-25"),
        ...                             np.datetime64("NaT"),
        ...                             np.datetime64("NaT")]})
        >>> df
               name        toy                 born
        0    Alfred  Batmobile  1940-04-25 00:00:00
        1    Batman       <NA>                 <NA>
        2  Catwoman   Bullwhip                 <NA>

        Drop the rows where at least one element is null.

        >>> df.dropna()
             name        toy       born
        0  Alfred  Batmobile 1940-04-25

        Drop the columns where at least one element is null.

        >>> df.dropna(axis='columns')
               name
        0    Alfred
        1    Batman
        2  Catwoman

        Drop the rows where all elements are null.

        >>> df.dropna(how='all')
               name        toy                 born
        0    Alfred  Batmobile  1940-04-25 00:00:00
        1    Batman       <NA>                 <NA>
        2  Catwoman   Bullwhip                 <NA>

        Keep only the rows with at least 2 non-null values.

        >>> df.dropna(thresh=2)
               name        toy                 born
        0    Alfred  Batmobile  1940-04-25 00:00:00
        2  Catwoman   Bullwhip                 <NA>

        Define in which columns to look for null values.

        >>> df.dropna(subset=['name', 'born'])
             name        toy       born
        0  Alfred  Batmobile 1940-04-25

        Keep the DataFrame with valid entries in the same variable.

        >>> df.dropna(inplace=True)
        >>> df
             name        toy       born
        0  Alfred  Batmobile 1940-04-25
        """
        if axis == 0:
            result = self._drop_na_rows(how=how, subset=subset, thresh=thresh)
        else:
            result = self._drop_na_columns(
                how=how, subset=subset, thresh=thresh
            )

        return self._mimic_inplace(result, inplace=inplace)

    def _drop_na_rows(self, how="any", subset=None, thresh=None):
        """
        Drop null rows from `self`.

        how : {"any", "all"}, optional
            Specifies how to decide whether to drop a row.
            any (default) drops rows containing at least
            one null value. all drops only rows containing
            *all* null values.
        subset : list, optional
            List of columns to consider when dropping rows.
        thresh : int, optional
            If specified, then drops every row containing
            less than `thresh` non-null values.
        """
        if subset is None:
            subset = self._column_names
        elif (
            not np.iterable(subset)
            or isinstance(subset, str)
            or isinstance(subset, tuple)
            and subset in self._data.names
        ):
            subset = (subset,)
        diff = set(subset) - set(self._data)
        if len(diff) != 0:
            raise KeyError(f"columns {diff} do not exist")

        if len(subset) == 0:
            return self.copy(deep=True)

        data_columns = [
            col.nans_to_nulls()
            if isinstance(col, cudf.core.column.NumericalColumn)
            else col
            for col in self._columns
        ]

        return self._from_columns_like_self(
            libcudf.stream_compaction.drop_nulls(
                [*self._index._data.columns, *data_columns],
                how=how,
                keys=self._positions_from_column_names(
                    subset, offset_by_index_columns=True
                ),
                thresh=thresh,
            ),
            self._column_names,
            self._index.names,
        )

    def _apply_boolean_mask(self, boolean_mask):
        """Apply boolean mask to each row of `self`.

        Rows corresponding to `False` is dropped.
        """
        boolean_mask = cudf.core.column.as_column(boolean_mask)
        if not is_bool_dtype(boolean_mask.dtype):
            raise ValueError("boolean_mask is not boolean type.")

        return self._from_columns_like_self(
            libcudf.stream_compaction.apply_boolean_mask(
                list(self._index._columns + self._columns), boolean_mask
            ),
            column_names=self._column_names,
            index_names=self._index.names,
        )

    def take(self, indices, axis=0):
        """Return a new frame containing the rows specified by *indices*.

        Parameters
        ----------
        indices : array-like
            Array of ints indicating which positions to take.
        axis : Unsupported

        Returns
        -------
        out : Series or DataFrame
            New object with desired subset of rows.

        Examples
        --------
        **Series**
        >>> s = cudf.Series(['a', 'b', 'c', 'd', 'e'])
        >>> s.take([2, 0, 4, 3])
        2    c
        0    a
        4    e
        3    d
        dtype: object

        **DataFrame**

        >>> a = cudf.DataFrame({'a': [1.0, 2.0, 3.0],
        ...                    'b': cudf.Series(['a', 'b', 'c'])})
        >>> a.take([0, 2, 2])
             a  b
        0  1.0  a
        2  3.0  c
        2  3.0  c
        >>> a.take([True, False, True])
             a  b
        0  1.0  a
        2  3.0  c
        """
        if self._get_axis_from_axis_arg(axis) != 0:
            raise NotImplementedError("Only axis=0 is supported.")

        return self._gather(indices)

    def _reset_index(self, level, drop, col_level=0, col_fill=""):
        """Shared path for DataFrame.reset_index and Series.reset_index."""
        if level is not None and not isinstance(level, (tuple, list)):
            level = (level,)
        _check_duplicate_level_names(level, self._index.names)

        # Split the columns in the index into data and index columns
        (
            data_columns,
            index_columns,
            data_names,
            index_names,
        ) = self._index._split_columns_by_levels(level)
        if index_columns:
            index = _index_from_columns(index_columns, name=self._index.name,)
            if isinstance(index, MultiIndex):
                index.names = index_names
            else:
                index.name = index_names[0]
        else:
            index = RangeIndex(len(self))

        if drop:
            return self._data, index

        new_column_data = {}
        for name, col in zip(data_names, data_columns):
            if name == "index" and "index" in self._data:
                name = "level_0"
            name = (
                tuple(
                    name if i == col_level else col_fill
                    for i in range(self._data.nlevels)
                )
                if self._data.multiindex
                else name
            )
            new_column_data[name] = col
        # This is to match pandas where the new data columns are always
        # inserted to the left of existing data columns.
        return (
            ColumnAccessor(
                {**new_column_data, **self._data}, self._data.multiindex
            ),
            index,
        )

    def _first_or_last(
        self, offset, idx: int, op: Callable, side: str, slice_func: Callable
    ) -> "IndexedFrame":
        """Shared code path for ``first`` and ``last``."""
        if not isinstance(self._index, cudf.core.index.DatetimeIndex):
            raise TypeError("'first' only supports a DatetimeIndex index.")
        if not isinstance(offset, str):
            raise NotImplementedError(
                f"Unsupported offset type {type(offset)}."
            )

        if len(self) == 0:
            return self.copy()

        pd_offset = pd.tseries.frequencies.to_offset(offset)
        to_search = op(pd.Timestamp(self._index._column[idx]), pd_offset)
        if (
            idx == 0
            and not isinstance(pd_offset, pd.tseries.offsets.Tick)
            and pd_offset.is_on_offset(pd.Timestamp(self._index[0]))
        ):
            # Special handle is required when the start time of the index
            # is on the end of the offset. See pandas gh29623 for detail.
            to_search = to_search - pd_offset.base
            return self.loc[:to_search]
        end_point = int(
            self._index._column.searchsorted(to_search, side=side)[0]
        )
        return slice_func(end_point)

    def first(self, offset):
        """Select initial periods of time series data based on a date offset.

        When having a DataFrame with **sorted** dates as index, this function
        can select the first few rows based on a date offset.

        Parameters
        ----------
        offset: str
            The offset length of the data that will be selected. For intance,
            '1M' will display all rows having their index within the first
            month.

        Returns
        -------
        Series or DataFrame
            A subset of the caller.

        Raises
        ------
        TypeError
            If the index is not a ``DatetimeIndex``

        Examples
        --------
        >>> i = cudf.date_range('2018-04-09', periods=4, freq='2D')
        >>> ts = cudf.DataFrame({'A': [1, 2, 3, 4]}, index=i)
        >>> ts
                    A
        2018-04-09  1
        2018-04-11  2
        2018-04-13  3
        2018-04-15  4
        >>> ts.first('3D')
                    A
        2018-04-09  1
        2018-04-11  2
        """
        return self._first_or_last(
            offset,
            idx=0,
            op=operator.__add__,
            side="left",
            slice_func=lambda i: self.iloc[:i],
        )

    def last(self, offset):
        """Select final periods of time series data based on a date offset.

        When having a DataFrame with **sorted** dates as index, this function
        can select the last few rows based on a date offset.

        Parameters
        ----------
        offset: str
            The offset length of the data that will be selected. For instance,
            '3D' will display all rows having their index within the last 3
            days.

        Returns
        -------
        Series or DataFrame
            A subset of the caller.

        Raises
        ------
        TypeError
            If the index is not a ``DatetimeIndex``

        Examples
        --------
        >>> i = cudf.date_range('2018-04-09', periods=4, freq='2D')
        >>> ts = cudf.DataFrame({'A': [1, 2, 3, 4]}, index=i)
        >>> ts
                    A
        2018-04-09  1
        2018-04-11  2
        2018-04-13  3
        2018-04-15  4
        >>> ts.last('3D')
                    A
        2018-04-13  3
        2018-04-15  4
        """
        return self._first_or_last(
            offset,
            idx=-1,
            op=operator.__sub__,
            side="right",
            slice_func=lambda i: self.iloc[i:],
        )

def _check_duplicate_level_names(specified, level_names):
    """Raise if any of `specified` has duplicates in `level_names`."""
    if specified is None:
        return
    if len(set(level_names)) == len(level_names):
        return
    duplicates = {key for key, val in Counter(level_names).items() if val > 1}

    duplicates_specified = [spec for spec in specified if spec in duplicates]
    if not len(duplicates_specified) == 0:
        # Note: pandas raises first encountered duplicates, cuDF raises all.
        raise ValueError(
            f"The names {duplicates_specified} occurs multiple times, use a"
            " level number"
        )


"""Test for letsencrypt_plesk.challenge."""
import unittest
import mock

from letsencrypt import errors
from letsencrypt_plesk import challenge
from letsencrypt_plesk.tests import api_mock

class PleskChallengeTest(unittest.TestCase):
    def setUp(self):
        super(PleskChallengeTest, self).setUp()
        self.challenge = challenge.PleskChallenge("example.com",
                                                  api_mock.PleskApiMock())

    def test_perform(self):
        # pylint: disable=protected-access
        self.challenge._init_domain_props = mock.MagicMock()
        self.challenge._exists = mock.MagicMock(return_value=True)
        self.challenge._create_file = mock.MagicMock()
        self.challenge.www_root = '/www'
        self.challenge.perform(self._mock_achall())
        self.assertEqual(self.challenge.verify_path, '/www/.well-known')
        self.challenge._create_file.assert_has_calls([
            mock.call('/www/.well-known/.htaccess', mock.ANY),
            mock.call('/www/.well-known/abc', '123')])

    def test_cleanup(self):
        # pylint: disable=protected-access
        self.challenge._remove_file = mock.MagicMock()
        self.challenge.verify_path = '/www/.well-known'
        self.challenge.www_root = '/www'
        self.challenge.ftp_login = 'ftp_user'
        self.challenge.cleanup(self._mock_achall())
        self.challenge._remove_file.assert_has_calls([
            mock.call('/www/.well-known/.htaccess'),
            mock.call('/www/.well-known/abc')])

    def test_cleanup_error(self):
        # pylint: disable=protected-access
        self.challenge._remove_file = mock.MagicMock()
        self.challenge._remove_file.side_effect = api_mock.api_client.PleskApiException
        self.challenge.verify_path = '/www/.well-known'
        self.challenge.www_root = '/www'
        self.challenge.ftp_login = 'ftp_user'
        self.challenge.cleanup(self._mock_achall())

    @staticmethod
    def _mock_achall():
        achall = mock.MagicMock(URI_ROOT_PATH='.well-known')
        achall.chall.encode.return_value = 'abc'
        response = mock.MagicMock()
        validation = mock.MagicMock()
        validation.encode.return_value = '123'
        achall.response_and_validation.return_value = (response, validation)
        return achall

    def test_init_domain_props(self):
        # pylint: disable=protected-access
        self.challenge.plesk_api_client.expects_request(
            'request_site_get_one')
        self.challenge.plesk_api_client.will_response(
            'response_site_get_one_ok')

        self.challenge._init_domain_props()
        self.challenge.plesk_api_client.assert_called()
        self.assertEqual(self.challenge.ftp_login, 'username')
        self.assertEqual(self.challenge.www_root, '/var/www/vhosts/example.com/httpdocs')

    def test_init_domain_props_error(self):
        # pylint: disable=protected-access
        self.challenge.plesk_api_client.expects_request(
            'request_site_get_one')
        self.challenge.plesk_api_client.will_response(
            'response_site_get_one_error')

        self.assertRaises(errors.DvAuthError, self.challenge._init_domain_props)
        self.challenge.plesk_api_client.assert_called()

    def test_init_domain_props_without_hosting(self):
        # pylint: disable=protected-access
        self.challenge.plesk_api_client.expects_request(
            'request_site_get_one')
        self.challenge.plesk_api_client.will_response(
            'response_site_get_one_without_hosting')

        self.assertRaises(errors.DvAuthError, self.challenge._init_domain_props)
        self.challenge.plesk_api_client.assert_called()

    def test_filemng(self):
        # pylint: disable=protected-access
        plesk_api_mock = self.challenge.plesk_api_client
        plesk_api_mock.BIN_PATH = '/bin'
        self.challenge.ftp_login = 'ftp_user'
        self.challenge._filemng('test')
        plesk_api_mock.execute.assert_called_once_with('/bin/filemng',
                                                       arguments=['ftp_user', 'test'])

    def test_filemng_with_user(self):
        # pylint: disable=protected-access
        plesk_api_mock = self.challenge.plesk_api_client
        plesk_api_mock.BIN_PATH = '/bin'
        self.challenge._filemng('test', user='psaadm')
        plesk_api_mock.execute.assert_called_once_with('/bin/filemng',
                                                       arguments=['psaadm', 'test'])

    def test_ls(self):
        # pylint: disable=protected-access
        self.challenge._filemng = mock.MagicMock()
        self.challenge._filemng.return_value = """
..	1439971863	4096	root	root	755
favicon.ico	1214575941	2862	root	root	644
index.html	1364580529	2241	root	root	644
.	1364580529	4096	root	root	755
"""
        self.assertEqual(['favicon.ico', 'index.html'], self.challenge._ls('/www'))
        self.challenge._filemng.assert_called_once_with('list', 'both', '/www', stdout=True)

    def test_is_sub_path(self):
        # pylint: disable=protected-access
        self.assertTrue(self.challenge._is_sub_path('/a/b/c/d', '/a/b'))
        self.assertFalse(self.challenge._is_sub_path('/a/b/c/d', '/c/d'))
        self.assertFalse(self.challenge._is_sub_path('/a/b', '/a/b'))

    def test_remove_file(self):
        # pylint: disable=protected-access
        self.challenge.verify_path = '/www/.well-known'
        self.challenge.www_root = '/www'
        self.challenge.ftp_login = 'ftp_user'
        self.challenge._filemng = mock.MagicMock()
        self.challenge._exists = mock.MagicMock(return_value=True)
        self.challenge._ls = mock.MagicMock(return_value=[])

        self.challenge._remove_file('/www/.well-known/abc')
        self.challenge._filemng.assert_has_calls([
            mock.call('rm', '/www/.well-known/abc'),
            mock.call('rmdir', '/www/.well-known')])

    def test_remove_file_not_empty_dir(self):
        # pylint: disable=protected-access
        self.challenge.verify_path = '/www/.well-known'
        self.challenge.www_root = '/www'
        self.challenge.ftp_login = 'ftp_user'
        self.challenge._filemng = mock.MagicMock()
        self.challenge._exists = mock.MagicMock(return_value=True)
        self.challenge._ls = mock.MagicMock(return_value=['zxc', 'asd', 'qwe'])

        self.challenge._remove_file('/www/.well-known/abc')
        self.challenge._filemng.assert_called_once_with('rm', '/www/.well-known/abc')

    def test_exists(self):
        # pylint: disable=protected-access
        self.challenge._filemng = mock.MagicMock(return_value='0')
        self.assertTrue(self.challenge._exists('/www/test-exists'))
        self.challenge._filemng.assert_called_once_with('file_exists', '/www/test-exists',
                                                        stdout=True)

    def test_not_exists(self):
        # pylint: disable=protected-access
        self.challenge._filemng = mock.MagicMock(return_value='1')
        self.assertFalse(self.challenge._exists('/www/test-not-exists'))
        self.challenge._filemng.assert_called_once_with('file_exists', '/www/test-not-exists',
                                                        stdout=True)

    def test_create_file(self):
        # pylint: disable=protected-access
        self.challenge._filemng = mock.MagicMock()
        self.challenge._exists = mock.MagicMock(return_value=False)
        self.challenge.verify_path = '/www/.well-known'
        self.challenge._create_file('/www/.well-known/abc', '123')
        self.challenge._filemng.assert_has_calls([
            mock.call("mkdir", "-p", "/www/.well-known"),
            mock.call("cp2perm", mock.ANY, "/www/.well-known/abc", "0644")])

if __name__ == "__main__":
    unittest.main()  # pragma: no cover


import sys

def main():
	with open(sys.argv[1]) as f:
		for line in f:
			data = line.strip('\n').split(",")
			digits, words = [], []
			for elem in data:
				if elem.isdigit():
					digits.append(elem)
				else:
					words.append(elem)
			result = ""
			if words:
				result = ",".join(words)
			if digits and words:
				result += "|"
			if digits:
				result += ",".join(digits)
			print(result)

if __name__ == '__main__':
	main()


import paho.mqtt.client as mqtt
import time
import grovepi
import math
from grove_rgb_lcd import *

def on_connect(client, userdata, flags, rc):
    print("Connected to server (i.e., broker) with result code "+str(rc))

    #subscribe to topics of interest here
    client.subscribe("victoryi/alarm")
    client.message_callback_add("victoryi/alarm", alarm_callback)

    client.subscribe("victoryi/alert")
    client.message_callback_add("victoryi/alert", alert_callback)

def on_message(client, userdata, msg):
    print("on_message: " + msg.topic + " " + str(msg.playload, "utf-8"))

#callback for the alarm
def alarm_callback(client, userdata, msg):
    message = str(msg.payload, "utf-8")
    if(message == "Sound Alarm"):
        r_led = 8 #red led d8
        b_led = 7 #blue led d7
        buzz = 2 #buzzer d2
        grovepi.pinMode(r_led, "OUTPUT")
        grovepi.pinMode(b_led, "OUTPUT")
        for i in range(15):
            #BEEEEEEEEEE
            grovepi.digitalWrite(r_led,1)
            grovepi.digitalWrite(b_led,0)
            grovepi.digitalWrite(buzz,1)
            time.sleep(0.5)
            #BOOOOOOOOOO
            grovepi.digitalWrite(r_led,0)
            grovepi.digitalWrite(b_led,1)
            grovepi.digitalWrite(buzz,0)
            time.sleep(0.5)
        grovepi.digitalWrite(r_led,1)
        #alaram lights stay on after alarm

#callback for the alert
def alert_callback(client, userdata, msg):
    setRGB(255,0,0)
    message = str(msg.payload, "utf-8")

    setText_norefresh(message)

if __name__ == '__main__':
    #this section is covered in publisher_and_subscriber_example.py
    client = mqtt.Client()
    client.on_message = on_message
    client.on_connect = on_connect
    client.connect(host="eclipse.usc.edu", port=11000, keepalive=60)
    client.loop_start()

    bus.write_byte_data(DISPLAY_TEXT_ADDR,0x80,0x01)
    setRGB(70,140,140) #random color

    button = 3 #port d3 used for the button
    ths = 4 #port d4 used for the temperature and humidity sensor
    blue = 0

    grovepi.pinMode(button, "INPUT")

    print("Beginning environmental detection")

    while True:

        [temp,humidity] = grovepi.dht(ths,blue)
        if math.isnan(temp) == False and math.isnan(humidity) == False:
            print("Current temp = %.02f C Current humidity =%.02f%%"%(temp, humidity))

        #Ive added a button trigger to that we dont need to test it out with real fire
        if(grovepi.digitalRead(button) == 1) or ((temp > 40.00) and (humidity < 5.00)):
                client.publish("victoryi/alert", "RPI is hot, press to sound alarm")
        time.sleep(2) #2 second interval


from flask import Flask, request, render_template
import os,json
import datetime

app = Flask(__name__)
current_dir = os.getcwd()
templates_dir = os.path.join(current_dir,'templates/')

@app.route('/')
def index():
    return "Hello, append filename in routes for perform - GET,POST,PUT,DELETE"

@app.before_request
def log_headers():
    print(f'\nHeaders : \n{request.headers}')

@app.route('/<file_name>',methods = ['POST','GET', 'PUT','DELETE'])
def http_server(file_name):

    file_path = templates_dir + file_name

    if request.method == 'GET':
        if os.path.isfile(file_path):
            if request.headers.get('If-Modified-Since'):
                date = datetime.datetime.strptime(request.headers["If-Modified-Since"], "%Y-%m-%dT%H:%M:%S.%f%Z")
                check_timestamp = datetime.datetime.timestamp(date)
                last_modified = os.path.getmtime(file_path)

                if int(check_timestamp) > int(last_modified):
                    return f'Not Modified Since, {request.headers["If-Modified-Since"]}', 304
            return render_template(file_name),200
        else:
            return render_template('404.html'),404

    elif request.method == 'POST':
        content =request.get_json()["content"]
        try:
            with open(file_path, 'a') as file:
                file.write(content)
            return {"status" : f"File saved in {templates_dir}"},200
        except Exception as err:
            return {"status" : "Failed saving","Error" : err},404

    elif request.method == 'PUT':
        content = request.get_json()["content"]
        try:
            with open(file_path, 'w') as file:
                file.write('\n' + content)
            return {"status" : f"File updated and saved in {templates_dir}!"},200
        except Exception as err:
            return {"status" :"Failed to update", "Error" : err},404

    elif request.method == "DELETE":
        if os.path.isfile(file_path):
            os.remove(file_path)
            return {"status" : "File deleted successfully"},200
        else:
            return render_template('404.html') ,404

@app.after_request
def set_headers(response):
    response.headers['Server'] = 'rodriguez/1.0'
    response.headers['Connection'] = 'Closed'
    return response

@app.route('/check', methods=['POST'])
def check():
    data = request.json
    return f"Test success, {data.get('content')}"

if __name__ == '__main__':
    app.run(debug=True)


def make_album(artist_name, album_title):
    """Return the artist name and album title."""
    album_info = f"{artist_name}, {album_title}"
    return album_info.title()
while True:
    print("\nPlease tell me your favorite artist and album:")
    print("(enter 'q' at any time to quit)")

    a_name = input("Artist name: ")
    if a_name == 'q':
        break
    a_title = input("Album title: ")
    if a_title == 'q':
        break
    music_info = make_album(a_name, a_title)
    print(f"\nMy favorite artist and album is {music_info}")


#Gets the summary of every article on the feed

import feedparser, urllib2, re

url = "http://rss.nytimes.com/services/xml/rss/nyt/Science.xml"
stuff = feedparser.parse(url)

text = ""
for entry in stuff['entries']:
    text += entry['summary_detail']['value']

#Clean out all the tags and etc
stripper = re.compile('<.*?>')
final = re.sub(stripper,'',text)
final = final.replace('.',' ')
final = final.strip()

#corpus-ify
words = final.split(" ")
words.sort()
corpus = []
for word in words:
    if not word in corpus:
        corpus.append(word.encode('utf-8'))
corpus.sort()


"""
#---------------------------------------------------------------------------
# Projeto 2 - Cinemática de colisões relativísticas (Aniquilação elétron-pósitron)
# Aluno: Gabriel Moreira e Victor Augustus
# Professoras: Helena Malbouisson e Clemencia Mora
# Disciplina: Introdução a Python - UERJ 2018.2
#---------------------------------------------------------------------------

Script desenvolvido para simulações de colisões relativísticas com foco em aniquilação elétron-pósitron.
Com a colisão de um pósitron de momentum randômico com um elétron em repouso no referencial do laboratório foi possível calcular a energia final dos fótons resultandos
	pelo princípio de conservação de energia com base em um distribuição de ângulos finais de um dos fótons.

Source: https://arxiv.org/pdf/1011.1943.pdf

Obs:
O script tem dependências com os packages numpy, pandas e openpyxl.
Caso o script não funcione executando no terminal com --python3 projeto2.py é preciso instalar esses packages pelo terminal da seguinte maneira:

sudo apt install python3-pip
pip3 install pandas
pip3 install openpyxl
"""

# Módulos utilizados
from math 					import *
from particula 				import *
from random 				import *
import matplotlib.pyplot	as plt
import numpy 				as np
import pandas 				as pd
from pandas 				import ExcelWriter

# Definindo as funções
def aniquilacao_ep(Ep,Ee,theta):
	k = sqrt((Ep - Ee)/(Ep + Ee))
	Ef = Ee/(1-k*cos(radians(theta)))
	return Ef

def plot(dict,x_label,y_label,title):
	for i in range(1,n_colisoes+1):
		data_x = np.array(dict['Colisão ' + str(i)][0])
		data_y = np.array(dict['Colisão ' + str(i)][1])
		plt.plot(data_x,data_y, label = 'Colisão ' + str(i))
		plt.xlabel(x_label, fontsize = 12)
		plt.ylabel(y_label + '1 (MeV)', fontsize = 12)

	plt.title(title, fontsize = 14)
	plt.legend(bbox_to_anchor = (1.04,1), loc = 'upper left')
	plt.tight_layout(rect = [0,0,0.75,1])
	plt.savefig('foton1.png')
	plt.show()
	plt.close()

	for i in range(1,n_colisoes+1):
		data_x = np.array(dict['Colisão ' + str(i)][0])
		data_y = np.array(dict['Colisão ' + str(i)][2])
		plt.plot(data_x,data_y, label = 'Colisão ' + str(i))
		plt.xlabel(x_label, fontsize = 12)
		plt.ylabel(y_label + '2 (MeV)', fontsize = 12)

	plt.title(title, fontsize = 14)
	plt.legend(bbox_to_anchor = (1.04,1), loc = "upper left")
	plt.tight_layout(rect = [0,0,0.75,1])
	plt.savefig('foton2.png')
	plt.show()
	plt.close()

# Apresentando o programa ao usuário e coletando o número de colisões
print('\nProjeto de Colisões Relativísticas, com foco em Aniquilação Elétron-Pósitron \n Foi utilizado um sistema de unidades naturais onde c = 1')
print('\bO programa irá calcular a energia final dos fótons resultantes da colisão de um elétron com momentum p = 0 e um pósitron com momentum p randômico entre 1.0 e 100.0.')
print('No final do processo, iniciará a plotagem dos gráficos da distribuição do ângulo resultante do fóton 1 pela distribuição da energa final dos fótons 1 e 2, além disso, o script também irá armazenar os dados.')
n_colisoes = int(input('\nDigite o número de colisões: '))
while n_colisoes <= 0:
	n_colisoes = int(input('Essa resposta não é válida! Digite um número > 0: '))

# Definindo o dicionário que irá armazenar os dados
dist = {}

for i in range(1,n_colisoes+1):
	dist['Colisão ' + str(i)] = [[] for i in range(6)]

# Criando um loop com a variável loop_question para reiniciar o programa depedendo da resposta do usuário
loop_question = 'sim'
while loop_question == 'sim':

	# Loop
	for i in range(1,n_colisoes+1):
		p = uniform(1.0,100.0)
		e = Particula(0.511)
		pos = Particula(0.511,p)

		theta = 0
		while theta <= 360:
			Ef = aniquilacao_ep(pos.energia(),e.energia(),theta)
			fot1 = Particula(0,Ef)
			fot2 = Particula(0,(pos.energia()+e.energia())-Ef)

			dist['Colisão ' + str(i)][0].append(theta)
			dist['Colisão ' + str(i)][1].append(fot1.energia())
			dist['Colisão ' + str(i)][2].append(fot2.energia())
			dist['Colisão ' + str(i)][3].append(e.energia())
			dist['Colisão ' + str(i)][4].append(pos.energia())
			dist['Colisão ' + str(i)][5].append(p)

			theta += 1

	# Gráfico
	plot(dist,'\u03B8 (\u00B0)', 'Energia \u03B3_', 'Aniquilação Elétron-Pósitron')

	# Criando um arquivo .txt e armazenando os dados calculados
	arq = open('dados_aniquilação_ep.txt', 'w')
	for i in range(1,n_colisoes+1):
		arq.write('\n############### Colisão ' + str(i) + ' ###############')
		arq.write('\np_pos(MeV/c)		\u03B8 (\u00B0)		E_e(MeV)		E_pos(MeV)		E_e+p(MeV)		E_\u03B3_1(MeV)		E_\u03B3_2(MeV)		E_\u03B3_1+\u03B3_2(MeV)')
		arq.write('\n')
		for j in range(0,len(dist['Colisão 1'][0])):
			arq.write(str("%.4f" % dist['Colisão ' + str(i)][5][j]))
			arq.write('			')
			arq.write(str("%.4f" % dist['Colisão ' + str(i)][0][j]))
			arq.write('			')
			arq.write(str("%.4f" % dist['Colisão ' + str(i)][3][j]))
			arq.write('			')
			arq.write(str("%.4f" % dist['Colisão ' + str(i)][4][j]))
			arq.write('			')
			arq.write(str("%.4f" % (dist['Colisão ' + str(i)][3][j] + dist['Colisão ' + str(i)][4][j])))
			arq.write('			')
			arq.write(str("%.4f" % dist['Colisão ' + str(i)][1][j]))
			arq.write('			')
			arq.write(str("%.4f" % dist['Colisão ' + str(i)][2][j]))
			arq.write('			')
			arq.write(str("%.4f" % (dist['Colisão ' + str(i)][1][j] + dist['Colisão ' + str(i)][2][j])))
			arq.write('\n')
	arq.close()

	# Criando um arquivo .xlsx e armazenando os dados cada sheet do excel
	writer = ExcelWriter("dados_aniquilação_ep.xlsx")

	Df_List = []

	for i in range(1, n_colisoes + 1):

		theta 		= np.array(dist['Colisão ' + str(i)][0])
		E_fot1_ene	= np.array(dist['Colisão ' + str(i)][1])
		E_fot2_ene 	= np.array(dist['Colisão ' + str(i)][2])
		E_ene		= np.array(dist['Colisão ' + str(i)][3])
		P_ene		= np.array(dist['Colisão ' + str(i)][4])
		P			= np.array(dist['Colisão ' + str(i)][5])

		My_data_frame = pd.DataFrame({ "p_pos(MeV/c)": P,
										"\u03B8 (\u00B0)": theta,
										"E_e(MeV)": E_ene,
										"E_pos(MeV)": P_ene,
										"E_e+p(MeV)": E_ene + P_ene,
										"E_\u03B3_1(MeV)": E_fot1_ene,
										"E_\u03B3_2(MeV)": E_fot2_ene,
										"E_\u03B3_1+\u03B3_2(MeV)": E_fot1_ene + E_fot2_ene
									})

		Df_List.append(My_data_frame)

	for i in range(len(Df_List)):
		#print (Df_List)
		col_string = 'Sheet_' + str(i+1)
		Df_List[i].to_excel(writer, col_string, index=False)
		writer.save()

	# Update da loop_question para saber se o usuário quer realizar outras simulações
	loop_question = input('\nDeseja simular outras colisões? [sim/não] ')
	while loop_question not in {'sim', 'não'}:
		loop_question = input('Essa não é uma resposta válida! Responda [sim] ou [não]. ')

	# Atualizando a condição inicial do número de colisões para o caso que o usuário deseja realizar outras simulações
	if loop_question == 'sim':
		n_colisoes = int(input('Digite o número de colisões: '))
		while n_colisoes <= 0:
			n_colisoes = int(input('Essa resposta não é válida! Digite um número > 0: '))

	# Limpando o dicionário que irá armazenar os dados para o caso que o usuário deseja realizar outras simulações
		dist = {}

		for i in range(1,n_colisoes+1):
			dist['Colisão ' + str(i)] = [[] for i in range(6)]

print('\nEncerrando o programa!')


import os
import importlib.util
import logging
import Framework.Utility as Utility
import Framework.JlscaBruteForceCalculation as JlscaBruteForceCalculation
from python_log_indenter import IndentedLoggerAdapter

logger = IndentedLoggerAdapter(logging.getLogger())

def cw_to_trs(root_dir, cw_dir, cw_date_string, trs_file, num_traces_to_test):
    cmd = "julia "
    cmd += os.path.join(root_dir, 'Framework', 'cw_to_trs.jl') + " "
    cmd += "--cw_date_string {} ".format(cw_date_string)
    cmd += "--cw_trace_location {} ".format(cw_dir)
    cmd += "--num_traces {} ".format(num_traces_to_test)
    cmd += trs_file
    Utility.execute(cmd)

def attack_inc_cpa(root_dir, trs_file, attack_params, jlsca_log_file):
    cmd = "julia" + " "
    cmd += os.path.join(root_dir, 'Framework', 'inc_cpa.jl') + " "
    for key, value in attack_params.items():
        cmd += str(key)+ " "
        cmd += str(value) + " "
    cmd += trs_file
    Utility.execute(cmd, jlsca_log_file)

def jlsca_analysis(root_dir, ProjectDir, local_setup):
    logger = IndentedLoggerAdapter(logging.getLogger())
    logger.add()
    # Load LocalSetup from user input
    spec = importlib.util.spec_from_file_location(local_setup, os.path.join(os.getcwd(), local_setup))
    LocalSetup = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(LocalSetup)
    logger.info("Using parameters from LocalSetup:")
    logger.add()
    for key, value in LocalSetup.AttackParameters.items():
        logger.info(key + " " + str(value))
    logger.sub()
    # Prepare cw files to trs files if not already converted
    for num_traces_to_test in LocalSetup.TraceNumList:
        # Trace file to be generated and used in attack
        trs_file = os.path.join(
            LocalSetup.TraceDir,
            LocalSetup.num_samples + "_samples_" + num_traces_to_test + "_traces_data.trs")

        # Log file to be generated from attack and used in brute force calculation
        jlsca_log_file = os.path.join(
            LocalSetup.ProjectDir,
            '_log',
            'jlsca_' + num_traces_to_test + '_traces.log'
        )
        logger.info("Jlsca attack using {:>6} traces:".format(num_traces_to_test))
        logger.add()
        if not os.path.isfile(trs_file):
            logger.info("Converting CW project to JlSca trs format")
            cw_to_trs(root_dir, LocalSetup.CWFilesDir, LocalSetup.date_string, trs_file, num_traces_to_test)
            logger.info("CW files converted into .trs file")

        logger.info("Running attack Incremental Correlation Power Analysis using Julia")
        attack_inc_cpa(root_dir, trs_file, LocalSetup.AttackParameters, jlsca_log_file)
        logger.info("Attack Incremental Correlation Power Analysis completed")

        logger.info("Running calculation on number of guesses needed based on brute force algorithm")
        num_of_guesses = JlscaBruteForceCalculation.calculate(jlsca_log_file,
                                                              LocalSetup.CWKnownKey,
                                                              "--show-plots" in LocalSetup.AttackParameters)

        logger.info("Number of guesses calculation completed")
        logger.info("Number of guesses needed using {:>6} traces: {}".format(num_traces_to_test, num_of_guesses))
        logger.sub()
    logger.sub()


from my_lib import *
from metrics import *
from keras import initializers
import datetime
import pickle
from keras.callbacks import ModelCheckpoint, EarlyStopping, TensorBoard
from keras.preprocessing.image import ImageDataGenerator
import tensorflow.keras as keras

input_size = (176, 176, 4)
metrics = [dice, dice_en_metric, dice_core_metric, dice_whole_metric, 'accuracy']

train_ind, val_ind, _ = create_train_test_split()

batch_size = 16
lr = 1e-4
epochs = 100
total_num_slices = 1.5e4
seed = 1
num_batches_in_epoch = int(total_num_slices // batch_size)

# Initialize and load weights of pretrained networks
input_size_small = (176, 176, 1)
unet_t1 = unet_dong_single_mod(input_size=input_size_small, num_classes=4, lr=lr, loss='categorical_crossentropy', metrics=metrics)
unet_t1ce = unet_dong_single_mod(input_size=input_size_small, num_classes=4, lr=lr, loss='categorical_crossentropy', metrics=metrics)
unet_t2 = unet_dong_single_mod(input_size=input_size_small, num_classes=4, lr=lr, loss='categorical_crossentropy', metrics=metrics)
unet_flair = unet_dong_single_mod(input_size=input_size_small, num_classes=4, lr=lr, loss='categorical_crossentropy', metrics=metrics)

'''
t1_weights = '20200107-separately_trained_weights_sensor_fusion/20200107-102802-t1-lr-0.0001-n-79-dong_small.hdf5'
t1ce_weights = "20200107-separately_trained_weights_sensor_fusion/20200107-102838-t1ce-lr-0.0001-n-79-dong_small.hdf5"
t2_weights = "20200107-separately_trained_weights_sensor_fusion/20200107-102913-t2-lr-0.0001-n-79-dong_small.hdf5"
flair_weights = "20200107-separately_trained_weights_sensor_fusion/20200107-102947-flair-lr-0.0001-n-79-dong_small.hdf5"
'''
t1_weights = '20200107-202317-t1-lr-0.0001-n-15539-dong_small.hdf5'
t1ce_weights = "20200107-123438-t1ce-lr-0.0001-n-15539-dong_small.hdf5"
t2_weights = "20200108-110818-t2-lr-0.0001-n-15539-dong_small.hdf5"
flair_weights = "20200108-060927-flair-lr-0.0001-n-15539-dong_small.hdf5"

unet_t1.load_weights(t1_weights)
unet_t1ce.load_weights(t1ce_weights)
unet_t2.load_weights(t2_weights)
unet_flair.load_weights(flair_weights)

# Setup the model
unet = multi_stream_unet_trained_sequentially(unet_t1, unet_t1ce, unet_t2, unet_flair, lr=lr, loss='categorical_crossentropy', metrics=metrics)

X_train, Y_train = load_patients_numpy("data_numpy_separate_patients_original_size", train_ind, cropping=True)
X_val, Y_val = load_patients_numpy("data_numpy_separate_patients_original_size", val_ind, cropping=True)

train_datagen = ImageDataGenerator(
    rotation_range=20,
    horizontal_flip=True,
    vertical_flip=True,
    width_shift_range=0.1,
    height_shift_range=0.1,
    shear_range=0.2,
    zoom_range=0.1)

input_generator = train_datagen.flow(
        X_train,
        batch_size=batch_size,
        seed=seed
)

label_generator = train_datagen.flow(
        Y_train,
        batch_size=batch_size,
        seed=seed
)

# Where to save logs and weights
weights_path = datetime.datetime.now().strftime("%Y%m%d-%H%M%S") + '-all-lr-' + str(lr)\
               + '-n-' + str(X_train.shape[0]) + "-weights_data_aug_fit_gen.hdf5"
log_dir = "logs/" + datetime.datetime.now().strftime("%Y%m%d-%H%M%S") + "-all" \
          + '-lr-' + str(lr) + '-n-' + str(X_train.shape[0]) + '_data_aug_fit_gen'

cp = ModelCheckpoint(weights_path, save_best_only=True, monitor='val_loss', mode='auto', verbose=1, period=1)
es = EarlyStopping(monitor='val_loss', mode='auto', verbose=1, patience=10)
tbc = TensorBoard(log_dir=log_dir)

tot_generator = zip(input_generator, label_generator)

unet.fit_generator(
        tot_generator,
        steps_per_epoch=num_batches_in_epoch,
        epochs=epochs,
        validation_data=(X_val, Y_val),
        callbacks=[cp, es, tbc]
)


# encoding: utf-8
"""
Given a sorted array, remove the duplicates in place such that each element appear only once and return the new length.

Do not allocate extra space for another array, you must do this in place with constant memory.

For example,
Given input array nums = [1,1,2],

Your function should return length = 2, with the first two elements of nums being 1 and 2 respectively. It doesn't
matter what you leave beyond the new length.
"""

class Solution(object):
    def removeDuplicates(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        # 如果是可以有两个重复的话,改成 new_len < 2 or num > nums[new_len - 2]就行了
        new_len = 0
        for num in nums:
            if new_len < 1 or num > nums[new_len - 1]:
                nums[new_len] = num
                new_len += 1
        return new_len


import json
import requests
import traceback

import sample as sample
import sendOMF as sendOMF
import omfHelper as omfHelper
import auth as auth

config = {}

def suppressError(call):
    try:
        call()
    except Exception as e:
        print(f'Encountered Error: {e}')

def checkData():
    if config['destinationOCS']:
        checkLastOCSVal()
    # don't have to check others as they are sync and we get instant feedback on success from the app itself

def checkLastOCSVal():
    msg_headers = {
        "Authorization": auth.getAuthHeader()
    }
    url = config['omfURL'].split('/omf')[0] + \
        '/streams/Tank1Measurements/data/last'
    response = requests.get(
        url,
        headers=msg_headers,
        verify=config['verify']
    )

    # response code in 200s if the request was successful!
    if response.status_code < 200 or response.status_code >= 300:
        print(msg_headers)
        response.close()
        print(
            f'Response from was bad.  message: {response.status_code} {response.text}.')
        print()
        raise Exception(
            f'OMF message was unsuccessful,  {response.status_code}:{response.text}')

def sendTypeDelete(_type):
    sendOMF.sendCall(json.dumps(_type), 'type', 'delete')

def sendContainerDelete(container):
    sendOMF.sendCall(json.dumps(container), 'container', 'delete')

def test_main():
    global config
    # Tests to make sure the sample runs as expected

    try:
        config = sample.main(True, ['2,3', 'n'])
        checkData()

    except Exception as ex:
        print(f'Encountered Error: {ex}.')
        print
        traceback.print_exc()
        print
        success = False
        raise ex

    finally:
        print('Deletes')
        print

        suppressError(lambda: sendContainerDelete(omfHelper.getContainer()))
        suppressError(lambda: sendTypeDelete(omfHelper.getType()))

if __name__ == '__main__':
    test_main()


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Sat Jul 13 17:05:38 2019

@author: kata
"""

import sys

import numpy as np
import scipy as sp
import matplotlib as mpl
import scipy.misc

from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
import matplotlib.pyplot as plt

from PyQt5 import QtWidgets, uic, QtCore, QtGui
from PyQt5.QtWidgets import QDialog, QApplication, QPushButton, QVBoxLayout
from mpl_toolkits.axisartist.axislines import SubplotZero

class sin2(QtWidgets.QDialog):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.ui = uic.loadUi("sin.ui", self)

        self.figure = plt.figure()

        self.canvas = FigureCanvas(self.figure)

        self.toolbar = NavigationToolbar(self.canvas, self)

        self.button = QPushButton('Plot')
        self.button.clicked.connect(self.plot)

        layout = QVBoxLayout()
        layout.addWidget(self.toolbar)
        layout.addWidget(self.canvas)
        layout.addWidget(self.button)

        layout.addWidget(self.lblStrecken)
        layout.addWidget(self.hSlider)

        layout.addWidget(self.lblStau)
        layout.addWidget(self.hSlider_2)

        layout.addWidget(self.lbla)
        layout.addWidget(self.a)
        layout.addWidget(self.lblb)
        layout.addWidget(self.b)
        self.setLayout(layout)

        self.ui.hSlider.setMinimum(0)
        self.ui.hSlider.setMaximum(10)
        self.ui.hSlider.setSingleStep(0.2)
        self.ui.hSlider.valueChanged.connect(self.sliderValueChanged)

        self.ui.hSlider_2.setMinimum(0)
        self.ui.hSlider_2.setMaximum(10)
        self.ui.hSlider_2.setSingleStep(0.2)
        self.ui.hSlider_2.valueChanged.connect(self.sliderValueChanged2)

        self.ui.a.textChanged.connect(self.setA)
        self.ui.b.textChanged.connect(self.setB)

    def sliderValueChanged(self, value):
        global k
        k = float(value)
        print(k)

    def sliderValueChanged2(self, value):
        global k
        k = 1/float(value)
        print(k)

    def setA(self, value):
        global a
        a = float(value)
        print("a: ", a)

    def setB(self, value):
        global b
        b = float(value)
        print("b: ", b)

    def plot(self):
        self.figure.clear()

        print("a: ", a)
        print("b: ", b)
        print("k: ", k)

        x = np.linspace(0, 4*np.pi, 100)
        #x = np.linspace(0, 2*np.pi, 100)
        #x = np.arange(0.0, (2*np.pi), 0.01)

      #  x = np.linspace(-5, 10*np.pi, 100)
        y = np.linspace(-5, 5, 100)
        y = a + np.sin((k*x)+b)

        f, ax = plt.subplots()
        ax = self.figure.add_subplot(111)

        tex = r'$f(x) = a + sin(k * x) + b$'
        tex_y = r'$sin(x)$'
        tex_x = r'$x$'

        ax.set_title(tex, fontsize=20)
        ax.set(xlabel=tex_x, ylabel=tex_y)
        ax.grid()

        ax.plot(x, y)
        plt.axis('equal')
        plt.show()
        self.canvas.draw()

app = QtWidgets.QApplication(sys.argv)
dialog = sin2()
dialog.show()
sys.exit(app.exec_())