import operator
import warnings

import numpy

try:
    import scipy.sparse
    _scipy_available = True
except ImportError:
    _scipy_available = False

import cupy
from cupy._core import _accelerator
from cupy.cuda import cub
from cupy.cuda import runtime
from cupyx.scipy.sparse import _base
from cupyx.scipy.sparse import _compressed
from cupyx.scipy.sparse import _csc
from cupyx.scipy.sparse import SparseEfficiencyWarning
from cupyx.scipy.sparse import _util

class csr_matrix(_compressed._compressed_sparse_matrix):

    """Compressed Sparse Row matrix.

    This can be instantiated in several ways.

    ``csr_matrix(D)``
        ``D`` is a rank-2 :class:`cupy.ndarray`.
    ``csr_matrix(S)``
        ``S`` is another sparse matrix. It is equivalent to ``S.tocsr()``.
    ``csr_matrix((M, N), [dtype])``
        It constructs an empty matrix whose shape is ``(M, N)``. Default dtype
        is float64.
    ``csr_matrix((data, (row, col)))``
        All ``data``, ``row`` and ``col`` are one-dimenaional
        :class:`cupy.ndarray`.
    ``csr_matrix((data, indices, indptr))``
        All ``data``, ``indices`` and ``indptr`` are one-dimenaional
        :class:`cupy.ndarray`.

    Args:
        arg1: Arguments for the initializer.
        shape (tuple): Shape of a matrix. Its length must be two.
        dtype: Data type. It must be an argument of :class:`numpy.dtype`.
        copy (bool): If ``True``, copies of given arrays are always used.

    .. seealso::
        :class:`scipy.sparse.csr_matrix`

    """

    format = 'csr'

    def get(self, stream=None):
        """Returns a copy of the array on host memory.

        Args:
            stream (cupy.cuda.Stream): CUDA stream object. If it is given, the
                copy runs asynchronously. Otherwise, the copy is synchronous.

        Returns:
            scipy.sparse.csr_matrix: Copy of the array on host memory.

        """
        if not _scipy_available:
            raise RuntimeError('scipy is not available')
        data = self.data.get(stream)
        indices = self.indices.get(stream)
        indptr = self.indptr.get(stream)
        return scipy.sparse.csr_matrix(
            (data, indices, indptr), shape=self._shape)

    def _convert_dense(self, x):
        m = dense2csr(x)
        return m.data, m.indices, m.indptr

    def _swap(self, x, y):
        return (x, y)

    def _add_sparse(self, other, alpha, beta):
        from cupyx import cusparse

        self.sum_duplicates()
        other = other.tocsr()
        other.sum_duplicates()
        if cusparse.check_availability('csrgeam2'):
            csrgeam = cusparse.csrgeam2
        elif cusparse.check_availability('csrgeam'):
            csrgeam = cusparse.csrgeam
        else:
            raise NotImplementedError
        return csrgeam(self, other, alpha, beta)

    def _comparison(self, other, op, op_name):
        if _util.isscalarlike(other):
            data = cupy.asarray(other, dtype=self.dtype).reshape(1)
            if numpy.isnan(data[0]):
                if op_name == '_ne_':
                    return csr_matrix(cupy.ones(self.shape, dtype=numpy.bool_))
                else:
                    return csr_matrix(self.shape, dtype=numpy.bool_)
            indices = cupy.zeros((1,), dtype=numpy.int32)
            indptr = cupy.arange(2, dtype=numpy.int32)
            other = csr_matrix((data, indices, indptr), shape=(1, 1))
            return binopt_csr(self, other, op_name)
        elif _util.isdense(other):
            return op(self.todense(), other)
        elif isspmatrix_csr(other):
            self.sum_duplicates()
            other.sum_duplicates()
            if op_name in ('_ne_', '_lt_', '_gt_'):
                return binopt_csr(self, other, op_name)

            warnings.warn(
                "Comparing sparse matrices using ==, <=, and >= is "
                "inefficient, try using !=, <, or > instead.",
                SparseEfficiencyWarning)
            if op_name == '_eq_':
                opposite_op_name = '_ne_'
            elif op_name == '_le_':
                opposite_op_name = '_gt_'
            elif op_name == '_ge_':
                opposite_op_name = '_lt_'
            res = binopt_csr(self, other, opposite_op_name)
            out = cupy.logical_not(res.toarray())
            return csr_matrix(out)
        raise NotImplementedError

    def __eq__(self, other):
        return self._comparison(other, operator.eq, '_eq_')

    def __ne__(self, other):
        return self._comparison(other, operator.ne, '_ne_')

    def __lt__(self, other):
        return self._comparison(other, operator.lt, '_lt_')

    def __gt__(self, other):
        return self._comparison(other, operator.gt, '_gt_')

    def __le__(self, other):
        return self._comparison(other, operator.le, '_le_')

    def __ge__(self, other):
        return self._comparison(other, operator.ge, '_ge_')

    def __mul__(self, other):
        from cupyx import cusparse

        if cupy.isscalar(other):
            self.sum_duplicates()
            return self._with_data(self.data * other)
        elif isspmatrix_csr(other):
            self.sum_duplicates()
            other.sum_duplicates()
            if cusparse.check_availability('spgemm'):
                return cusparse.spgemm(self, other)
            elif cusparse.check_availability('csrgemm2'):
                return cusparse.csrgemm2(self, other)
            elif cusparse.check_availability('csrgemm'):
                return cusparse.csrgemm(self, other)
            else:
                raise AssertionError
        elif _csc.isspmatrix_csc(other):
            self.sum_duplicates()
            other.sum_duplicates()
            if cusparse.check_availability('csrgemm') and not runtime.is_hip:
                # trans=True is still buggy as of ROCm 4.2.0
                return cusparse.csrgemm(self, other.T, transb=True)
            elif cusparse.check_availability('spgemm'):
                b = other.tocsr()
                b.sum_duplicates()
                return cusparse.spgemm(self, b)
            elif cusparse.check_availability('csrgemm2'):
                b = other.tocsr()
                b.sum_duplicates()
                return cusparse.csrgemm2(self, b)
            else:
                raise AssertionError
        elif _base.isspmatrix(other):
            return self * other.tocsr()
        elif _base.isdense(other):
            if other.ndim == 0:
                self.sum_duplicates()
                return self._with_data(self.data * other)
            elif other.ndim == 1:
                self.sum_duplicates()
                other = cupy.asfortranarray(other)
                # need extra padding to ensure not stepping on the CUB bug,
                # see cupy/cupy#3679 for discussion
                is_cub_safe = (self.indptr.data.mem.size
                               > self.indptr.size * self.indptr.dtype.itemsize)
                # CUB spmv is buggy since CUDA 11.0, see
                # https://github.com/cupy/cupy/issues/3822#issuecomment-782607637
                is_cub_safe &= (cub._get_cuda_build_version() < 11000)
                for accelerator in _accelerator.get_routine_accelerators():
                    if (accelerator == _accelerator.ACCELERATOR_CUB
                            and not runtime.is_hip
                            and is_cub_safe and other.flags.c_contiguous):
                        return cub.device_csrmv(
                            self.shape[0], self.shape[1], self.nnz,
                            self.data, self.indptr, self.indices, other)
                if (cusparse.check_availability('csrmvEx') and self.nnz > 0 and
                        cusparse.csrmvExIsAligned(self, other)):
                    # csrmvEx does not work if nnz == 0
                    csrmv = cusparse.csrmvEx
                elif cusparse.check_availability('csrmv'):
                    csrmv = cusparse.csrmv
                elif cusparse.check_availability('spmv'):
                    csrmv = cusparse.spmv
                else:
                    raise AssertionError
                return csrmv(self, other)
            elif other.ndim == 2:
                self.sum_duplicates()
                if cusparse.check_availability('csrmm2'):
                    csrmm = cusparse.csrmm2
                elif cusparse.check_availability('spmm'):
                    csrmm = cusparse.spmm
                else:
                    raise AssertionError
                return csrmm(self, cupy.asfortranarray(other))
            else:
                raise ValueError('could not interpret dimensions')
        else:
            return NotImplemented

    def __div__(self, other):
        raise NotImplementedError

    def __rdiv__(self, other):
        raise NotImplementedError

    def __truediv__(self, other):
        """Point-wise division by another matrix, vector or scalar"""
        if _util.isscalarlike(other):
            dtype = self.dtype
            if dtype == numpy.float32:
                # Note: This is a work-around to make the output dtype the same
                # as SciPy. It might be SciPy version dependent.
                dtype = numpy.float64
            dtype = cupy.result_type(dtype, other)
            d = cupy.reciprocal(other, dtype=dtype)
            return multiply_by_scalar(self, d)
        elif _util.isdense(other):
            other = cupy.atleast_2d(other)
            other = cupy.broadcast_to(other, self.shape)
            check_shape_for_pointwise_op(self.shape, other.shape)
            ret = self.tocoo()
            ret.data = _cupy_divide_by_dense()(
                ret.data, ret.row, ret.col, ret.shape[1], other)
            return ret
        elif _base.isspmatrix(other):
            # Note: If broadcasting is needed, an exception is raised here for
            # compatibility with SciPy, as SciPy does not support broadcasting
            # in the "sparse / sparse" case.
            check_shape_for_pointwise_op(self.shape, other.shape,
                                         allow_broadcasting=False)
            dtype = numpy.promote_types(self.dtype, other.dtype)
            if dtype.char not in 'FD':
                dtype = numpy.promote_types(numpy.float64, dtype)
            # Note: The following implementation converts two sparse matrices
            # into dense matrices and then performs a point-wise division,
            # which can use lots of memory.
            self_dense = self.todense().astype(dtype, copy=False)
            return self_dense / other.todense()
        return NotImplemented

    def __rtruediv__(self, other):
        return NotImplemented

    # TODO(unno): Implement check_format

    def diagonal(self, k=0):
        rows, cols = self.shape
        ylen = min(rows + min(k, 0), cols - max(k, 0))
        if ylen <= 0:
            return cupy.empty(0, dtype=self.dtype)
        self.sum_duplicates()
        y = cupy.empty(ylen, dtype=self.dtype)
        _cupy_csr_diagonal()(k, rows, cols, self.data, self.indptr,
                             self.indices, y)
        return y

    def eliminate_zeros(self):
        """Removes zero entories in place."""
        from cupyx import cusparse

        compress = cusparse.csr2csr_compress(self, 0)
        self.data = compress.data
        self.indices = compress.indices
        self.indptr = compress.indptr

    def _maximum_minimum(self, other, cupy_op, op_name, dense_check):
        if _util.isscalarlike(other):
            other = cupy.asarray(other, dtype=self.dtype)
            if dense_check(other):
                dtype = self.dtype
                # Note: This is a work-around to make the output dtype the same
                # as SciPy. It might be SciPy version dependent.
                if dtype == numpy.float32:
                    dtype = numpy.float64
                elif dtype == numpy.complex64:
                    dtype = numpy.complex128
                dtype = cupy.result_type(dtype, other)
                other = other.astype(dtype, copy=False)
                # Note: The computation steps below are different from SciPy.
                new_array = cupy_op(self.todense(), other)
                return csr_matrix(new_array)
            else:
                self.sum_duplicates()
                new_data = cupy_op(self.data, other)
                return csr_matrix((new_data, self.indices, self.indptr),
                                  shape=self.shape, dtype=self.dtype)
        elif _util.isdense(other):
            self.sum_duplicates()
            other = cupy.atleast_2d(other)
            return cupy_op(self.todense(), other)
        elif isspmatrix_csr(other):
            self.sum_duplicates()
            other.sum_duplicates()
            return binopt_csr(self, other, op_name)
        raise NotImplementedError

    def maximum(self, other):
        return self._maximum_minimum(other, cupy.maximum, '_maximum_',
                                     lambda x: x > 0)

    def minimum(self, other):
        return self._maximum_minimum(other, cupy.minimum, '_minimum_',
                                     lambda x: x < 0)

    def multiply(self, other):
        """Point-wise multiplication by another matrix, vector or scalar"""
        if cupy.isscalar(other):
            return multiply_by_scalar(self, other)
        elif _util.isdense(other):
            self.sum_duplicates()
            other = cupy.atleast_2d(other)
            return multiply_by_dense(self, other)
        elif isspmatrix_csr(other):
            self.sum_duplicates()
            other.sum_duplicates()
            return multiply_by_csr(self, other)
        else:
            msg = 'expected scalar, dense matrix/vector or csr matrix'
            raise TypeError(msg)

    # TODO(unno): Implement prune

    def setdiag(self, values, k=0):
        """Set diagonal or off-diagonal elements of the array."""
        rows, cols = self.shape
        row_st, col_st = max(0, -k), max(0, k)
        x_len = min(rows - row_st, cols - col_st)
        if x_len <= 0:
            raise ValueError('k exceeds matrix dimensions')
        values = values.astype(self.dtype)
        if values.ndim == 0:
            # broadcast
            x_data = cupy.full((x_len,), values, dtype=self.dtype)
        else:
            x_len = min(x_len, values.size)
            x_data = values[:x_len]
        x_indices = cupy.arange(col_st, col_st + x_len, dtype='i')
        x_indptr = cupy.zeros((rows + 1,), dtype='i')
        x_indptr[row_st:row_st+x_len+1] = cupy.arange(x_len+1, dtype='i')
        x_indptr[row_st+x_len+1:] = x_len
        x_data -= self.diagonal(k=k)[:x_len]
        y = self + csr_matrix((x_data, x_indices, x_indptr), shape=self.shape)
        self.data = y.data
        self.indices = y.indices
        self.indptr = y.indptr

    def sort_indices(self):
        """Sorts the indices of this matrix *in place*.

        .. warning::
            Calling this function might synchronize the device.

        """
        from cupyx import cusparse

        if not self.has_sorted_indices:
            cusparse.csrsort(self)
            self.has_sorted_indices = True

    def toarray(self, order=None, out=None):
        """Returns a dense matrix representing the same value.

        Args:
            order ({'C', 'F', None}): Whether to store data in C (row-major)
                order or F (column-major) order. Default is C-order.
            out: Not supported.

        Returns:
            cupy.ndarray: Dense array representing the same matrix.

        .. seealso:: :meth:`scipy.sparse.csr_matrix.toarray`

        """
        from cupyx import cusparse

        order = 'C' if order is None else order.upper()
        if self.nnz == 0:
            return cupy.zeros(shape=self.shape, dtype=self.dtype, order=order)

        if self.dtype.char not in 'fdFD':
            return csr2dense(self, order)

        x = self.copy()
        x.has_canonical_format = False  # need to enforce sum_duplicates
        x.sum_duplicates()
        if (cusparse.check_availability('sparseToDense')
                and (not runtime.is_hip or (x.nnz > 0))):
            # On HIP, nnz=0 is problematic as of ROCm 4.2.0
            y = cusparse.sparseToDense(x)
            if order == 'F':
                return y
            elif order == 'C':
                return cupy.ascontiguousarray(y)
            else:
                raise ValueError('order not understood')
        else:
            # csr2dense returns F-contiguous array.
            if order == 'C':
                # To return C-contiguous array, it uses transpose.
                return cusparse.csc2dense(x.T).T
            elif order == 'F':
                return cusparse.csr2dense(x)
            else:
                raise ValueError('order not understood')

    def tobsr(self, blocksize=None, copy=False):
        # TODO(unno): Implement tobsr
        raise NotImplementedError

    def tocoo(self, copy=False):
        """Converts the matrix to COOdinate format.

        Args:
            copy (bool): If ``False``, it shares data arrays as much as
                possible.

        Returns:
            cupyx.scipy.sparse.coo_matrix: Converted matrix.

        """
        from cupyx import cusparse

        if copy:
            data = self.data.copy()
            indices = self.indices.copy()
        else:
            data = self.data
            indices = self.indices

        return cusparse.csr2coo(self, data, indices)

    def tocsc(self, copy=False):
        """Converts the matrix to Compressed Sparse Column format.

        Args:
            copy (bool): If ``False``, it shares data arrays as much as
                possible. Actually this option is ignored because all
                arrays in a matrix cannot be shared in csr to csc conversion.

        Returns:
            cupyx.scipy.sparse.csc_matrix: Converted matrix.

        """
        from cupyx import cusparse

        # copy is ignored
        if cusparse.check_availability('csr2csc'):
            csr2csc = cusparse.csr2csc
        elif cusparse.check_availability('csr2cscEx2'):
            csr2csc = cusparse.csr2cscEx2
        else:
            raise NotImplementedError
        # don't touch has_sorted_indices, as cuSPARSE made no guarantee
        return csr2csc(self)

    def tocsr(self, copy=False):
        """Converts the matrix to Compressed Sparse Row format.

        Args:
            copy (bool): If ``False``, the method returns itself.
                Otherwise it makes a copy of the matrix.

        Returns:
            cupyx.scipy.sparse.csr_matrix: Converted matrix.

        """
        if copy:
            return self.copy()
        else:
            return self

    def _tocsx(self):
        """Inverts the format.
        """
        return self.tocsc()

    def todia(self, copy=False):
        # TODO(unno): Implement todia
        raise NotImplementedError

    def todok(self, copy=False):
        # TODO(unno): Implement todok
        raise NotImplementedError

    def tolil(self, copy=False):
        # TODO(unno): Implement tolil
        raise NotImplementedError

    def transpose(self, axes=None, copy=False):
        """Returns a transpose matrix.

        Args:
            axes: This option is not supported.
            copy (bool): If ``True``, a returned matrix shares no data.
                Otherwise, it shared data arrays as much as possible.

        Returns:
            cupyx.scipy.sparse.csc_matrix: `self` with the dimensions reversed.

        """
        if axes is not None:
            raise ValueError(
                'Sparse matrices do not support an \'axes\' parameter because '
                'swapping dimensions is the only logical permutation.')

        shape = self.shape[1], self.shape[0]
        trans = _csc.csc_matrix(
            (self.data, self.indices, self.indptr), shape=shape, copy=copy)
        trans.has_canonical_format = self.has_canonical_format
        return trans

    def getrow(self, i):
        """Returns a copy of row i of the matrix, as a (1 x n)
        CSR matrix (row vector).

        Args:
            i (integer): Row

        Returns:
            cupyx.scipy.sparse.csr_matrix: Sparse matrix with single row
        """
        return self._major_slice(slice(i, i + 1), copy=True)

    def getcol(self, i):
        """Returns a copy of column i of the matrix, as a (m x 1)
        CSR matrix (column vector).

        Args:
            i (integer): Column

        Returns:
            cupyx.scipy.sparse.csr_matrix: Sparse matrix with single column
        """
        return self._minor_slice(slice(i, i + 1), copy=True)

    def _get_intXarray(self, row, col):
        row = slice(row, row + 1)
        return self._major_slice(row)._minor_index_fancy(col)

    def _get_intXslice(self, row, col):
        row = slice(row, row + 1)
        return self._major_slice(row)._minor_slice(col, copy=True)

    def _get_sliceXint(self, row, col):
        col = slice(col, col + 1)
        copy = row.step in (1, None)
        return self._major_slice(row)._minor_slice(col, copy=copy)

    def _get_sliceXarray(self, row, col):
        return self._major_slice(row)._minor_index_fancy(col)

    def _get_arrayXint(self, row, col):
        col = slice(col, col + 1)
        return self._major_index_fancy(row)._minor_slice(col)

    def _get_arrayXslice(self, row, col):
        if col.step not in (1, None):
            start, stop, step = col.indices(self.shape[1])
            cols = cupy.arange(start, stop, step, self.indices.dtype)
            return self._get_arrayXarray(row, cols)
        return self._major_index_fancy(row)._minor_slice(col)

def isspmatrix_csr(x):
    """Checks if a given matrix is of CSR format.

    Returns:
        bool: Returns if ``x`` is :class:`cupyx.scipy.sparse.csr_matrix`.

    """
    return isinstance(x, csr_matrix)

def check_shape_for_pointwise_op(a_shape, b_shape, allow_broadcasting=True):
    if allow_broadcasting:
        a_m, a_n = a_shape
        b_m, b_n = b_shape
        if not (a_m == b_m or a_m == 1 or b_m == 1):
            raise ValueError('inconsistent shape')
        if not (a_n == b_n or a_n == 1 or b_n == 1):
            raise ValueError('inconsistent shape')
    else:
        if a_shape != b_shape:
            raise ValueError('inconsistent shape')

def multiply_by_scalar(sp, a):
    data = sp.data * a
    indices = sp.indices.copy()
    indptr = sp.indptr.copy()
    return csr_matrix((data, indices, indptr), shape=sp.shape)

def multiply_by_dense(sp, dn):
    check_shape_for_pointwise_op(sp.shape, dn.shape)
    sp_m, sp_n = sp.shape
    dn_m, dn_n = dn.shape
    m, n = max(sp_m, dn_m), max(sp_n, dn_n)
    nnz = sp.nnz * (m // sp_m) * (n // sp_n)
    dtype = numpy.promote_types(sp.dtype, dn.dtype)
    data = cupy.empty(nnz, dtype=dtype)
    indices = cupy.empty(nnz, dtype=sp.indices.dtype)
    if m > sp_m:
        if n > sp_n:
            indptr = cupy.arange(0, nnz+1, n, dtype=sp.indptr.dtype)
        else:
            indptr = cupy.arange(0, nnz+1, sp.nnz, dtype=sp.indptr.dtype)
    else:
        indptr = sp.indptr.copy()
        if n > sp_n:
            indptr *= n

    # out = sp * dn
    cupy_multiply_by_dense()(sp.data, sp.indptr, sp.indices, sp_m, sp_n,
                             dn, dn_m, dn_n, indptr, m, n, data, indices)

    return csr_matrix((data, indices, indptr), shape=(m, n))

_GET_ROW_ID_ = '''
__device__ inline int get_row_id(int i, int min, int max, const int *indptr) {
    int row = (min + max) / 2;
    while (min < max) {
        if (i < indptr[row]) {
            max = row - 1;
        } else if (i >= indptr[row + 1]) {
            min = row + 1;
        } else {
            break;
        }
        row = (min + max) / 2;
    }
    return row;
}
'''

_FIND_INDEX_HOLDING_COL_IN_ROW_ = '''
__device__ inline int find_index_holding_col_in_row(
        int row, int col, const int *indptr, const int *indices) {
    int j_min = indptr[row];
    int j_max = indptr[row+1] - 1;
    while (j_min <= j_max) {
        int j = (j_min + j_max) / 2;
        int j_col = indices[j];
        if (j_col == col) {
            return j;
        } else if (j_col < col) {
            j_min = j + 1;
        } else {
            j_max = j - 1;
        }
    }
    return -1;
}
'''

@cupy._util.memoize(for_each_device=True)
def cupy_multiply_by_dense():
    return cupy.ElementwiseKernel(
        '''
        raw S SP_DATA, raw I SP_INDPTR, raw I SP_INDICES,
        int32 SP_M, int32 SP_N,
        raw D DN_DATA, int32 DN_M, int32 DN_N,
        raw I OUT_INDPTR, int32 OUT_M, int32 OUT_N
        ''',
        'O OUT_DATA, I OUT_INDICES',
        '''
        int i_out = i;
        int m_out = get_row_id(i_out, 0, OUT_M - 1, &(OUT_INDPTR[0]));
        int i_sp = i_out;
        if (OUT_M > SP_M && SP_M == 1) {
            i_sp -= OUT_INDPTR[m_out];
        }
        if (OUT_N > SP_N && SP_N == 1) {
            i_sp /= OUT_N;
        }
        int n_out = SP_INDICES[i_sp];
        if (OUT_N > SP_N && SP_N == 1) {
            n_out = i_out - OUT_INDPTR[m_out];
        }
        int m_dn = m_out;
        if (OUT_M > DN_M && DN_M == 1) {
            m_dn = 0;
        }
        int n_dn = n_out;
        if (OUT_N > DN_N && DN_N == 1) {
            n_dn = 0;
        }
        OUT_DATA = (O)(SP_DATA[i_sp] * DN_DATA[n_dn + (DN_N * m_dn)]);
        OUT_INDICES = n_out;
        ''',
        'cupyx_scipy_sparse_csr_multiply_by_dense',
        preamble=_GET_ROW_ID_
    )

@cupy._util.memoize(for_each_device=True)
def _cupy_divide_by_dense():
    return cupy.ElementwiseKernel(
        'T data, I row, I col, I width, raw T other',
        'T res',
        '''
        res = data / other[row * width + col]
        ''',
        'cupyx_scipy_sparse_coo_divide_dense',
    )

def multiply_by_csr(a, b):
    check_shape_for_pointwise_op(a.shape, b.shape)
    a_m, a_n = a.shape
    b_m, b_n = b.shape
    m, n = max(a_m, b_m), max(a_n, b_n)
    a_nnz = a.nnz * (m // a_m) * (n // a_n)
    b_nnz = b.nnz * (m // b_m) * (n // b_n)
    if a_nnz > b_nnz:
        return multiply_by_csr(b, a)
    c_nnz = a_nnz
    dtype = numpy.promote_types(a.dtype, b.dtype)
    c_data = cupy.empty(c_nnz, dtype=dtype)
    c_indices = cupy.empty(c_nnz, dtype=a.indices.dtype)
    if m > a_m:
        if n > a_n:
            c_indptr = cupy.arange(0, c_nnz+1, n, dtype=a.indptr.dtype)
        else:
            c_indptr = cupy.arange(0, c_nnz+1, a.nnz, dtype=a.indptr.dtype)
    else:
        c_indptr = a.indptr.copy()
        if n > a_n:
            c_indptr *= n
    flags = cupy.zeros(c_nnz+1, dtype=a.indices.dtype)
    nnz_each_row = cupy.zeros(m+1, dtype=a.indptr.dtype)

    # compute c = a * b where necessary and get sparsity pattern of matrix d
    cupy_multiply_by_csr_step1()(
        a.data, a.indptr, a.indices, a_m, a_n,
        b.data, b.indptr, b.indices, b_m, b_n,
        c_indptr, m, n, c_data, c_indices, flags, nnz_each_row)

    flags = cupy.cumsum(flags, dtype=a.indptr.dtype)
    d_indptr = cupy.cumsum(nnz_each_row, dtype=a.indptr.dtype)
    d_nnz = int(d_indptr[-1])
    d_data = cupy.empty(d_nnz, dtype=dtype)
    d_indices = cupy.empty(d_nnz, dtype=a.indices.dtype)

    # remove zero elements in matric c
    cupy_multiply_by_csr_step2()(c_data, c_indices, flags, d_data, d_indices)

    return csr_matrix((d_data, d_indices, d_indptr), shape=(m, n))

@cupy._util.memoize(for_each_device=True)
def cupy_multiply_by_csr_step1():
    return cupy.ElementwiseKernel(
        '''
        raw A A_DATA, raw I A_INDPTR, raw I A_INDICES, int32 A_M, int32 A_N,
        raw B B_DATA, raw I B_INDPTR, raw I B_INDICES, int32 B_M, int32 B_N,
        raw I C_INDPTR, int32 C_M, int32 C_N
        ''',
        'C C_DATA, I C_INDICES, raw I FLAGS, raw I NNZ_EACH_ROW',
        '''
        int i_c = i;
        int m_c = get_row_id(i_c, 0, C_M - 1, &(C_INDPTR[0]));

        int i_a = i;
        if (C_M > A_M && A_M == 1) {
            i_a -= C_INDPTR[m_c];
        }
        if (C_N > A_N && A_N == 1) {
            i_a /= C_N;
        }
        int n_c = A_INDICES[i_a];
        if (C_N > A_N && A_N == 1) {
            n_c = i % C_N;
        }
        int m_b = m_c;
        if (C_M > B_M && B_M == 1) {
            m_b = 0;
        }
        int n_b = n_c;
        if (C_N > B_N && B_N == 1) {
            n_b = 0;
        }
        int i_b = find_index_holding_col_in_row(m_b, n_b,
            &(B_INDPTR[0]), &(B_INDICES[0]));
        if (i_b >= 0) {
            atomicAdd(&(NNZ_EACH_ROW[m_c+1]), 1);
            FLAGS[i+1] = 1;
            C_DATA = (C)(A_DATA[i_a] * B_DATA[i_b]);
            C_INDICES = n_c;
        }
        ''',
        'cupyx_scipy_sparse_csr_multiply_by_csr_step1',
        preamble=_GET_ROW_ID_ + _FIND_INDEX_HOLDING_COL_IN_ROW_
    )

@cupy._util.memoize(for_each_device=True)
def cupy_multiply_by_csr_step2():
    return cupy.ElementwiseKernel(
        'T C_DATA, I C_INDICES, raw I FLAGS',
        'raw D D_DATA, raw I D_INDICES',
        '''
        int j = FLAGS[i];
        if (j < FLAGS[i+1]) {
            D_DATA[j] = (D)(C_DATA);
            D_INDICES[j] = C_INDICES;
        }
        ''',
        'cupyx_scipy_sparse_csr_multiply_by_csr_step2'
    )

_BINOPT_MAX_ = '''
__device__ inline O binopt(T in1, T in2) {
    return max(in1, in2);
}
'''
_BINOPT_MIN_ = '''
__device__ inline O binopt(T in1, T in2) {
    return min(in1, in2);
}
'''
_BINOPT_EQ_ = '''
__device__ inline O binopt(T in1, T in2) {
    return (in1 == in2);
}
'''
_BINOPT_NE_ = '''
__device__ inline O binopt(T in1, T in2) {
    return (in1 != in2);
}
'''
_BINOPT_LT_ = '''
__device__ inline O binopt(T in1, T in2) {
    return (in1 < in2);
}
'''
_BINOPT_GT_ = '''
__device__ inline O binopt(T in1, T in2) {
    return (in1 > in2);
}
'''
_BINOPT_LE_ = '''
__device__ inline O binopt(T in1, T in2) {
    return (in1 <= in2);
}
'''
_BINOPT_GE_ = '''
__device__ inline O binopt(T in1, T in2) {
    return (in1 >= in2);
}
'''

def binopt_csr(a, b, op_name):
    check_shape_for_pointwise_op(a.shape, b.shape)
    a_m, a_n = a.shape
    b_m, b_n = b.shape
    m, n = max(a_m, b_m), max(a_n, b_n)
    a_nnz = a.nnz * (m // a_m) * (n // a_n)
    b_nnz = b.nnz * (m // b_m) * (n // b_n)

    a_info = cupy.zeros(a_nnz + 1, dtype=a.indices.dtype)
    b_info = cupy.zeros(b_nnz + 1, dtype=b.indices.dtype)
    a_valid = cupy.zeros(a_nnz, dtype=numpy.int8)
    b_valid = cupy.zeros(b_nnz, dtype=numpy.int8)
    c_indptr = cupy.zeros(m + 1, dtype=a.indptr.dtype)
    in_dtype = numpy.promote_types(a.dtype, b.dtype)
    a_data = a.data.astype(in_dtype, copy=False)
    b_data = b.data.astype(in_dtype, copy=False)
    funcs = _GET_ROW_ID_
    if op_name == '_maximum_':
        funcs += _BINOPT_MAX_
        out_dtype = in_dtype
    elif op_name == '_minimum_':
        funcs += _BINOPT_MIN_
        out_dtype = in_dtype
    elif op_name == '_eq_':
        funcs += _BINOPT_EQ_
        out_dtype = numpy.bool_
    elif op_name == '_ne_':
        funcs += _BINOPT_NE_
        out_dtype = numpy.bool_
    elif op_name == '_lt_':
        funcs += _BINOPT_LT_
        out_dtype = numpy.bool_
    elif op_name == '_gt_':
        funcs += _BINOPT_GT_
        out_dtype = numpy.bool_
    elif op_name == '_le_':
        funcs += _BINOPT_LE_
        out_dtype = numpy.bool_
    elif op_name == '_ge_':
        funcs += _BINOPT_GE_
        out_dtype = numpy.bool_
    else:
        raise ValueError('invalid op_name: {}'.format(op_name))
    a_tmp_data = cupy.empty(a_nnz, dtype=out_dtype)
    b_tmp_data = cupy.empty(b_nnz, dtype=out_dtype)
    a_tmp_indices = cupy.empty(a_nnz, dtype=a.indices.dtype)
    b_tmp_indices = cupy.empty(b_nnz, dtype=b.indices.dtype)
    _size = a_nnz + b_nnz
    cupy_binopt_csr_step1(op_name, preamble=funcs)(
        m, n,
        a.indptr, a.indices, a_data, a_m, a_n, a.nnz, a_nnz,
        b.indptr, b.indices, b_data, b_m, b_n, b.nnz, b_nnz,
        a_info, a_valid, a_tmp_indices, a_tmp_data,
        b_info, b_valid, b_tmp_indices, b_tmp_data,
        c_indptr, size=_size)
    a_info = cupy.cumsum(a_info, dtype=a_info.dtype)
    b_info = cupy.cumsum(b_info, dtype=b_info.dtype)
    c_indptr = cupy.cumsum(c_indptr, dtype=c_indptr.dtype)
    c_nnz = int(c_indptr[-1])
    c_indices = cupy.empty(c_nnz, dtype=a.indices.dtype)
    c_data = cupy.empty(c_nnz, dtype=out_dtype)
    cupy_binopt_csr_step2(op_name)(
        a_info, a_valid, a_tmp_indices, a_tmp_data, a_nnz,
        b_info, b_valid, b_tmp_indices, b_tmp_data, b_nnz,
        c_indices, c_data, size=_size)
    return csr_matrix((c_data, c_indices, c_indptr), shape=(m, n))

@cupy._util.memoize(for_each_device=True)
def cupy_binopt_csr_step1(op_name, preamble=''):
    name = 'cupyx_scipy_sparse_csr_binopt_' + op_name + 'step1'
    return cupy.ElementwiseKernel(
        '''
        int32 M, int32 N,
        raw I A_INDPTR, raw I A_INDICES, raw T A_DATA,
        int32 A_M, int32 A_N, int32 A_NNZ_ACT, int32 A_NNZ,
        raw I B_INDPTR, raw I B_INDICES, raw T B_DATA,
        int32 B_M, int32 B_N, int32 B_NNZ_ACT, int32 B_NNZ
        ''',
        '''
        raw I A_INFO, raw B A_VALID, raw I A_TMP_INDICES, raw O A_TMP_DATA,
        raw I B_INFO, raw B B_VALID, raw I B_TMP_INDICES, raw O B_TMP_DATA,
        raw I C_INFO
        ''',
        '''
        if (i >= A_NNZ + B_NNZ) return;

        const int *MY_INDPTR, *MY_INDICES;  int *MY_INFO;  const T *MY_DATA;
        const int *OP_INDPTR, *OP_INDICES;  int *OP_INFO;  const T *OP_DATA;
        int MY_M, MY_N, MY_NNZ_ACT, MY_NNZ;
        int OP_M, OP_N, OP_NNZ_ACT, OP_NNZ;
        signed char *MY_VALID;  I *MY_TMP_INDICES;  O *MY_TMP_DATA;

        int my_j;
        if (i < A_NNZ) {
            // in charge of one of non-zero element of sparse matrix A
            my_j = i;
            MY_INDPTR  = &(A_INDPTR[0]);   OP_INDPTR  = &(B_INDPTR[0]);
            MY_INDICES = &(A_INDICES[0]);  OP_INDICES = &(B_INDICES[0]);
            MY_INFO    = &(A_INFO[0]);     OP_INFO    = &(B_INFO[0]);
            MY_DATA    = &(A_DATA[0]);     OP_DATA    = &(B_DATA[0]);
            MY_M       = A_M;              OP_M       = B_M;
            MY_N       = A_N;              OP_N       = B_N;
            MY_NNZ_ACT = A_NNZ_ACT;        OP_NNZ_ACT = B_NNZ_ACT;
            MY_NNZ     = A_NNZ;            OP_NNZ     = B_NNZ;
            MY_VALID   = &(A_VALID[0]);
            MY_TMP_DATA= &(A_TMP_DATA[0]);
            MY_TMP_INDICES = &(A_TMP_INDICES[0]);
        } else {
            // in charge of one of non-zero element of sparse matrix B
            my_j = i - A_NNZ;
            MY_INDPTR  = &(B_INDPTR[0]);   OP_INDPTR  = &(A_INDPTR[0]);
            MY_INDICES = &(B_INDICES[0]);  OP_INDICES = &(A_INDICES[0]);
            MY_INFO    = &(B_INFO[0]);     OP_INFO    = &(A_INFO[0]);
            MY_DATA    = &(B_DATA[0]);     OP_DATA    = &(A_DATA[0]);
            MY_M       = B_M;              OP_M       = A_M;
            MY_N       = B_N;              OP_N       = A_N;
            MY_NNZ_ACT = B_NNZ_ACT;        OP_NNZ_ACT = A_NNZ_ACT;
            MY_NNZ     = B_NNZ;            OP_NNZ     = A_NNZ;
            MY_VALID   = &(B_VALID[0]);
            MY_TMP_DATA= &(B_TMP_DATA[0]);
            MY_TMP_INDICES = &(B_TMP_INDICES[0]);
        }
        int _min, _max, _mid;

        // get column location
        int my_col;
        int my_j_act = my_j;
        if (MY_M == 1 && MY_M < M) {
            if (MY_N == 1 && MY_N < N) my_j_act = 0;
            else                       my_j_act = my_j % MY_NNZ_ACT;
        } else {
            if (MY_N == 1 && MY_N < N) my_j_act = my_j / N;
        }
        my_col = MY_INDICES[my_j_act];
        if (MY_N == 1 && MY_N < N) {
            my_col = my_j % N;
        }

        // get row location
        int my_row = get_row_id(my_j_act, 0, MY_M - 1, &(MY_INDPTR[0]));
        if (MY_M == 1 && MY_M < M) {
            if (MY_N == 1 && MY_N < N) my_row = my_j / N;
            else                       my_row = my_j / MY_NNZ_ACT;
        }

        int op_row = my_row;
        int op_row_act = op_row;
        if (OP_M == 1 && OP_M < M) {
            op_row_act = 0;
        }

        int op_col = 0;
        _min = OP_INDPTR[op_row_act];
        _max = OP_INDPTR[op_row_act + 1] - 1;
        int op_j_act = _min;
        bool op_nz = false;
        if (_min <= _max) {
            if (OP_N == 1 && OP_N < N) {
                op_col = my_col;
                op_nz = true;
            }
            else {
                _mid = (_min + _max) / 2;
                op_col = OP_INDICES[_mid];
                while (_min < _max) {
                    if (op_col < my_col) {
                        _min = _mid + 1;
                    } else if (op_col > my_col) {
                        _max = _mid;
                    } else {
                        break;
                    }
                    _mid = (_min + _max) / 2;
                    op_col = OP_INDICES[_mid];
                }
                op_j_act = _mid;
                if (op_col == my_col) {
                    op_nz = true;
                } else if (op_col < my_col) {
                    op_col = N;
                    op_j_act += 1;
                }
            }
        }

        int op_j = op_j_act;
        if (OP_M == 1 && OP_M < M) {
            if (OP_N == 1 && OP_N < N) {
                op_j = (op_col + N * op_row) * OP_NNZ_ACT;
            } else {
                op_j = op_j_act + OP_NNZ_ACT * op_row;
            }
        } else {
            if (OP_N == 1 && OP_N < N) {
                op_j = op_col + N * op_j_act;
            }
        }

        if (i < A_NNZ || !op_nz) {
            T my_data = MY_DATA[my_j_act];
            T op_data = 0;
            if (op_nz) op_data = OP_DATA[op_j_act];
            O out;
            if (i < A_NNZ) out = binopt(my_data, op_data);
            else           out = binopt(op_data, my_data);
            if (out != static_cast<O>(0)) {
                MY_VALID[my_j] = 1;
                MY_TMP_DATA[my_j] = out;
                MY_TMP_INDICES[my_j] = my_col;
                atomicAdd( &(C_INFO[my_row + 1]), 1 );
                atomicAdd( &(MY_INFO[my_j + 1]), 1 );
                atomicAdd( &(OP_INFO[op_j]), 1 );
            }
        }
        ''',
        name, preamble=preamble,
    )

@cupy._util.memoize(for_each_device=True)
def cupy_binopt_csr_step2(op_name):
    name = 'cupyx_scipy_sparse_csr_binopt' + op_name + 'step2'
    return cupy.ElementwiseKernel(
        '''
        raw I A_INFO, raw B A_VALID, raw I A_TMP_INDICES, raw O A_TMP_DATA,
        int32 A_NNZ,
        raw I B_INFO, raw B B_VALID, raw I B_TMP_INDICES, raw O B_TMP_DATA,
        int32 B_NNZ
        ''',
        'raw I C_INDICES, raw O C_DATA',
        '''
        if (i < A_NNZ) {
            int j = i;
            if (A_VALID[j]) {
                C_INDICES[A_INFO[j]] = A_TMP_INDICES[j];
                C_DATA[A_INFO[j]]    = A_TMP_DATA[j];
            }
        } else if (i < A_NNZ + B_NNZ) {
            int j = i - A_NNZ;
            if (B_VALID[j]) {
                C_INDICES[B_INFO[j]] = B_TMP_INDICES[j];
                C_DATA[B_INFO[j]]    = B_TMP_DATA[j];
            }
        }
        ''',
        name,
    )

def csr2dense(a, order):
    out = cupy.zeros(a.shape, dtype=a.dtype, order=order)
    m, n = a.shape
    kern = _cupy_csr2dense(a.dtype)
    kern(m, n, a.indptr, a.indices, a.data, (order == 'C'), out)
    return out

@cupy._util.memoize(for_each_device=True)
def _cupy_csr2dense(dtype):
    if dtype == '?':
        op = "if (DATA) OUT[index] = true;"
    else:
        op = "atomicAdd(&OUT[index], DATA);"

    return cupy.ElementwiseKernel(
        'int32 M, int32 N, raw I INDPTR, I INDICES, T DATA, bool C_ORDER',
        'raw T OUT',
        '''
        int row = get_row_id(i, 0, M - 1, &(INDPTR[0]));
        int col = INDICES;
        int index = C_ORDER ? col + N * row : row + M * col;
        ''' + op,
        'cupyx_scipy_sparse_csr2dense',
        preamble=_GET_ROW_ID_
    )

def dense2csr(a):
    from cupyx import cusparse

    if a.dtype.char in 'fdFD':
        if cusparse.check_availability('denseToSparse'):
            return cusparse.denseToSparse(a, format='csr')
        else:
            return cusparse.dense2csr(a)
    m, n = a.shape
    a = cupy.ascontiguousarray(a)
    indptr = cupy.zeros(m + 1, dtype=numpy.int32)
    info = cupy.zeros(m * n + 1, dtype=numpy.int32)
    cupy_dense2csr_step1()(m, n, a, indptr, info)
    indptr = cupy.cumsum(indptr, dtype=numpy.int32)
    info = cupy.cumsum(info, dtype=numpy.int32)
    nnz = int(indptr[-1])
    indices = cupy.empty(nnz, dtype=numpy.int32)
    data = cupy.empty(nnz, dtype=a.dtype)
    cupy_dense2csr_step2()(m, n, a, info, indices, data)
    return csr_matrix((data, indices, indptr), shape=(m, n))

@cupy._util.memoize(for_each_device=True)
def cupy_dense2csr_step1():
    return cupy.ElementwiseKernel(
        'int32 M, int32 N, T A',
        'raw I INDPTR, raw I INFO',
        '''
        int row = i / N;
        int col = i % N;
        if (A != static_cast<T>(0)) {
            atomicAdd( &(INDPTR[row + 1]), 1 );
            INFO[i + 1] = 1;
        }
        ''',
        'cupyx_scipy_sparse_dense2csr_step1')

@cupy._util.memoize(for_each_device=True)
def cupy_dense2csr_step2():
    return cupy.ElementwiseKernel(
        'int32 M, int32 N, T A, raw I INFO',
        'raw I INDICES, raw T DATA',
        '''
        int row = i / N;
        int col = i % N;
        if (A != static_cast<T>(0)) {
            int idx = INFO[i];
            INDICES[idx] = col;
            DATA[idx] = A;
        }
        ''',
        'cupyx_scipy_sparse_dense2csr_step2')

@cupy._util.memoize(for_each_device=True)
def _cupy_csr_diagonal():
    return cupy.ElementwiseKernel(
        'int32 k, int32 rows, int32 cols, '
        'raw T data, raw I indptr, raw I indices',
        'T y',
        '''
        int row = i;
        int col = i;
        if (k < 0) row -= k;
        if (k > 0) col += k;
        if (row >= rows || col >= cols) return;
        int j = find_index_holding_col_in_row(row, col,
            &(indptr[0]), &(indices[0]));
        if (j >= 0) {
            y = data[j];
        } else {
            y = static_cast<T>(0);
        }
        ''',
        'cupyx_scipy_sparse_csr_diagonal',
        preamble=_FIND_INDEX_HOLDING_COL_IN_ROW_
    )


import asyncio
import logging

from bleak import BleakClient, BleakScanner

DEVICE_NAME = "iBobber"
IBOBBER_ADDR = "34:14:B5:4B:B9:15"
ACCEL_SERVICE_UUID = "1791FFA0-3853-11E3-AA6E-0800200C9A66"
CUSTOM_SERVICE_UUID = "1791FF90-3853-11E3-AA6E-0800200C9A66"
BATT_SERVICE_UUID = "0000180F-0000-1000-8000-00805F9B34FB"
DEVICE_SERVICE_UUID = "0000180A-0000-1000-8000-00805F9B34FB"
OAD_SERVICE_UUID = "F000FFC0-0451-4000-B000-000000000000"

ALL_SERVICES = [
    ACCEL_SERVICE_UUID,
    CUSTOM_SERVICE_UUID,
    BATT_SERVICE_UUID,
    DEVICE_SERVICE_UUID,
    OAD_SERVICE_UUID,
]

log = logging.getLogger("iBobber")
log.setLevel(logging.DEBUG)

async def main():
    logging.basicConfig(
        level=logging.INFO,
        # level=logging.DEBUG,
        format="%(asctime)s %(levelname)-8s %(name)-15s %(message)s",
        # datefmt="%Y-%m-%d %H:%M:%S",
    )
    log.debug("meow")
    for device, advert in sorted(
        (await BleakScanner.discover(return_adv=True)).values(),
        key=lambda d: -d[1].rssi,
    ):
        if not (device.name == DEVICE_NAME or advert.local_name == DEVICE_NAME):
            continue
        log.debug("Got a bobber at %s", device.address)
        log.debug("services be like %s", advert.service_uuids)

        async with BleakClient(
            device.address,
            # services=ALL_SERVICES,
            timeout=9999,
        ) as client:
            log.info("Connected!")
            for service in client.services:
                log.debug("%s: %s", service.handle, service.description)
                for characteristic in service.characteristics:
                    log.debug(
                        "  %s: %s",
                        characteristic.handle,
                        characteristic.description,
                    )

asyncio.run(main())


from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import unittest

import apache_beam as beam
from apache_beam.runners.direct import direct_runner
from apache_beam.runners.interactive import cache_manager as cache
from apache_beam.runners.interactive import pipeline_analyzer

def to_stable_runner_api(p):
  """The extra round trip ensures a stable pipeline proto.
  """
  return (
      beam.pipeline.Pipeline.from_runner_api(
          p.to_runner_api(use_fake_coders=True), p.runner,
          p._options).to_runner_api(use_fake_coders=True))

class PipelineAnalyzerTest(unittest.TestCase):
  def setUp(self):
    self.runner = direct_runner.DirectRunner()
    self.cache_manager = cache.FileBasedCacheManager()

  def tearDown(self):
    self.cache_manager.cleanup()

  def assertPipelineEqual(self, pipeline_proto1, pipeline_proto2):
    """A naive check for Pipeline proto equality.
    """
    components1 = pipeline_proto1.components
    components2 = pipeline_proto2.components

    self.assertEqual(len(components1.transforms), len(components2.transforms))
    self.assertEqual(
        len(components1.pcollections), len(components2.pcollections))

    # GreatEqual instead of Equal because the pipeline_proto_to_execute could
    # include more windowing_stratagies and coders than necessary.
    self.assertGreaterEqual(
        len(components1.windowing_strategies),
        len(components2.windowing_strategies))
    self.assertGreaterEqual(len(components1.coders), len(components2.coders))
    self.assertTransformEqual(
        pipeline_proto1,
        pipeline_proto1.root_transform_ids[0],
        pipeline_proto2,
        pipeline_proto2.root_transform_ids[0])

  def assertTransformEqual(
      self, pipeline_proto1, transform_id1, pipeline_proto2, transform_id2):
    """A naive check for Transform proto equality.
    """
    transform_proto1 = pipeline_proto1.components.transforms[transform_id1]
    transform_proto2 = pipeline_proto2.components.transforms[transform_id2]
    self.assertEqual(transform_proto1.spec.urn, transform_proto2.spec.urn)
    # Skipping payload checking because PTransforms of the same functionality
    # could generate different payloads.
    self.assertEqual(
        len(transform_proto1.subtransforms),
        len(transform_proto2.subtransforms))
    self.assertSetEqual(
        set(transform_proto1.inputs), set(transform_proto2.inputs))
    self.assertSetEqual(
        set(transform_proto1.outputs), set(transform_proto2.outputs))

  def test_basic(self):
    p = beam.Pipeline(runner=self.runner)

    # The cold run.
    pcoll = (
        p
        | 'Create' >> beam.Create([1, 2, 3])
        | 'Double' >> beam.Map(lambda x: x * 2)
        | 'Square' >> beam.Map(lambda x: x**2))
    analyzer = pipeline_analyzer.PipelineAnalyzer(
        self.cache_manager, to_stable_runner_api(p), self.runner)
    pipeline_to_execute = beam.pipeline.Pipeline.from_runner_api(
        analyzer.pipeline_proto_to_execute(), p.runner, p._options)
    pipeline_to_execute.run().wait_until_finish()
    self.assertEqual(
        len(analyzer.tl_required_trans_ids()),
        7  # Create, Double, Square, CacheSample * 3, CacheFull
    )
    self.assertEqual(len(analyzer.tl_referenced_pcoll_ids()), 3)
    self.assertEqual(len(analyzer.read_cache_ids()), 0)
    self.assertEqual(len(analyzer.write_cache_ids()), 4)

    # The second run.
    _ = (
        pcoll
        | 'Triple' >> beam.Map(lambda x: x * 3)
        | 'Cube' >> beam.Map(lambda x: x**3))
    analyzer = pipeline_analyzer.PipelineAnalyzer(
        self.cache_manager, to_stable_runner_api(p), self.runner)
    pipeline_to_execute = beam.pipeline.Pipeline.from_runner_api(
        analyzer.pipeline_proto_to_execute(), p.runner, p._options)
    self.assertEqual(
        len(analyzer.tl_required_trans_ids()),
        6  # Read, Triple, Cube, CacheSample * 2, CacheFull
    )
    self.assertEqual(len(analyzer.tl_referenced_pcoll_ids()), 3)
    self.assertEqual(len(analyzer.read_cache_ids()), 1)
    self.assertEqual(len(analyzer.write_cache_ids()), 3)

    # No need to actually execute the second run.

  def test_word_count(self):
    p = beam.Pipeline(runner=self.runner)

    class WordExtractingDoFn(beam.DoFn):
      def process(self, element):
        text_line = element.strip()
        words = text_line.split()
        return words

    # Count the occurrences of each word.
    pcoll1 = p | beam.Create(['to be or not to be that is the question'])
    pcoll2 = pcoll1 | 'Split' >> beam.ParDo(WordExtractingDoFn())
    pcoll3 = pcoll2 | 'Pair with One' >> beam.Map(lambda x: (x, 1))
    pcoll4 = pcoll3 | 'Group' >> beam.GroupByKey()
    pcoll5 = pcoll4 | 'Count' >> beam.Map(lambda item: (item[0], sum(item[1])))

    analyzer = pipeline_analyzer.PipelineAnalyzer(
        self.cache_manager, to_stable_runner_api(p), self.runner)

    cache_label1 = 'PColl-1111111'
    cache_label2 = 'PColl-2222222'
    cache_label3 = 'PColl-3333333'
    cache_label4 = 'PColl-4444444'
    cache_label5 = 'PColl-5555555'

    # pylint: disable=expression-not-assigned
    pcoll1 | 'CacheSample%s' % cache_label1 >> cache.WriteCache(
        self.cache_manager, cache_label1, sample=True, sample_size=10)
    pcoll2 | 'CacheSample%s' % cache_label2 >> cache.WriteCache(
        self.cache_manager, cache_label2, sample=True, sample_size=10)
    pcoll3 | 'CacheSample%s' % cache_label3 >> cache.WriteCache(
        self.cache_manager, cache_label3, sample=True, sample_size=10)
    pcoll4 | 'CacheSample%s' % cache_label4 >> cache.WriteCache(
        self.cache_manager, cache_label3, sample=True, sample_size=10)
    pcoll5 | 'CacheSample%s' % cache_label5 >> cache.WriteCache(
        self.cache_manager, cache_label3, sample=True, sample_size=10)
    pcoll5 | 'CacheFull%s' % cache_label5 >> cache.WriteCache(
        self.cache_manager, cache_label3)
    expected_pipeline_proto = to_stable_runner_api(p)

    self.assertPipelineEqual(
        analyzer.pipeline_proto_to_execute(), expected_pipeline_proto)

    pipeline_to_execute = beam.pipeline.Pipeline.from_runner_api(
        analyzer.pipeline_proto_to_execute(), p.runner, p._options)
    pipeline_to_execute.run().wait_until_finish()

  def test_write_cache_expansion(self):
    p = beam.Pipeline(runner=self.runner)

    pcoll1 = p | 'Create' >> beam.Create([1, 2, 3])
    pcoll2 = pcoll1 | 'Double' >> beam.Map(lambda x: x * 2)
    pcoll3 = pcoll2 | 'Square' >> beam.Map(lambda x: x**2)
    analyzer = pipeline_analyzer.PipelineAnalyzer(
        self.cache_manager, to_stable_runner_api(p), self.runner)

    cache_label1 = 'PColl-1234567'
    cache_label2 = 'PColl-7654321'
    cache_label3 = 'PColl-3141593'

    # pylint: disable=expression-not-assigned
    pcoll1 | 'CacheSample%s' % cache_label1 >> cache.WriteCache(
        self.cache_manager, cache_label1, sample=True, sample_size=10)
    pcoll2 | 'CacheSample%s' % cache_label2 >> cache.WriteCache(
        self.cache_manager, cache_label2, sample=True, sample_size=10)
    pcoll3 | 'CacheSample%s' % cache_label3 >> cache.WriteCache(
        self.cache_manager, cache_label3, sample=True, sample_size=10)
    pcoll3 | 'CacheFull%s' % cache_label3 >> cache.WriteCache(
        self.cache_manager, cache_label3)
    expected_pipeline_proto = to_stable_runner_api(p)

    # Since WriteCache expansion leads to more than 50 PTransform protos in the
    # pipeline, a simple check of proto map size is enough.
    self.assertPipelineEqual(
        analyzer.pipeline_proto_to_execute(), expected_pipeline_proto)

  def test_read_cache_expansion(self):
    p = beam.Pipeline(runner=self.runner)

    # The cold run.
    pcoll = (
        p
        | 'Create' >> beam.Create([1, 2, 3])
        | 'Double' >> beam.Map(lambda x: x * 2)
        | 'Square' >> beam.Map(lambda x: x**2))
    pipeline_proto = to_stable_runner_api(p)

    pipeline_info = pipeline_analyzer.PipelineInfo(pipeline_proto.components)
    pcoll_id = 'ref_PCollection_PCollection_12'  # Output PCollection of Square
    cache_label1 = pipeline_info.cache_label(pcoll_id)

    analyzer = pipeline_analyzer.PipelineAnalyzer(
        self.cache_manager, pipeline_proto, self.runner)
    pipeline_to_execute = beam.pipeline.Pipeline.from_runner_api(
        analyzer.pipeline_proto_to_execute(), p.runner, p._options)
    pipeline_to_execute.run().wait_until_finish()

    # The second run.
    _ = (
        pcoll
        | 'Triple' >> beam.Map(lambda x: x * 3)
        | 'Cube' >> beam.Map(lambda x: x**3))
    analyzer = pipeline_analyzer.PipelineAnalyzer(
        self.cache_manager, to_stable_runner_api(p), self.runner)

    expected_pipeline = beam.Pipeline(runner=self.runner)
    pcoll1 = (
        expected_pipeline
        | 'Load%s' % cache_label1 >> cache.ReadCache(
            self.cache_manager, cache_label1))
    pcoll2 = pcoll1 | 'Triple' >> beam.Map(lambda x: x * 3)
    pcoll3 = pcoll2 | 'Cube' >> beam.Map(lambda x: x**3)

    cache_label2 = 'PColl-7654321'
    cache_label3 = 'PColl-3141593'

    # pylint: disable=expression-not-assigned
    pcoll2 | 'CacheSample%s' % cache_label2 >> cache.WriteCache(
        self.cache_manager, cache_label2, sample=True, sample_size=10)
    pcoll3 | 'CacheSample%s' % cache_label3 >> cache.WriteCache(
        self.cache_manager, cache_label3, sample=True, sample_size=10)
    pcoll3 | 'CacheFull%s' % cache_label3 >> cache.WriteCache(
        self.cache_manager, cache_label3)

    # Since ReadCache & WriteCache expansion leads to more than 50 PTransform
    # protos in the pipeline, a simple check of proto map size is enough.
    self.assertPipelineEqual(
        analyzer.pipeline_proto_to_execute(),
        to_stable_runner_api(expected_pipeline))

class PipelineInfoTest(unittest.TestCase):
  def setUp(self):
    self.runner = direct_runner.DirectRunner()

  def test_passthrough(self):
    """
    Test that PTransforms which pass through their input PCollection can be
    used with PipelineInfo.
    """
    class Passthrough(beam.PTransform):
      def expand(self, pcoll):
        return pcoll

    p = beam.Pipeline(runner=self.runner)
    p | beam.Impulse() | Passthrough()  # pylint: disable=expression-not-assigned
    proto = to_stable_runner_api(p).components
    info = pipeline_analyzer.PipelineInfo(proto)
    for pcoll_id in info.all_pcollections():
      # FIXME: If PipelineInfo does not support passthrough PTransforms, this
      #        will only fail some of the time, depending on the ordering of
      #        transforms in the Pipeline proto.

      # Should not throw exception
      info.cache_label(pcoll_id)

if __name__ == '__main__':
  unittest.main()


import os
os.environ["XLA_FLAGS"] = "--xla_force_host_platform_device_count=4"

import sys
from typing import Literal
import pickle

sys.path.append(snakemake.config['workdir'])

import jax
from jax.config import config

config.update("jax_enable_x64", True)
print(f"Jax device count: {jax.local_device_count()}")

import torch

torch.set_default_dtype(torch.float64)

from arviz.data.inference_data import InferenceDataT

from src.utils.experiment_storage import ExperimentStorage, experiment_id_ctx
from src.napsu_mq.napsu_mq import NapsuMQModel, NapsuMQResult
from src.utils.string_utils import epsilon_float_to_str
from src.utils.timer import Timer
from src.utils.data_utils import transform_for_modeling
from src.utils.job_parameters import JobParameters
from src.utils.seed_utils import set_seed, get_seed
from src.utils.data_generator import create_dummy_dataset

"""
Create model with increasing number of variables in dataset and profiling enabled. Try to estimate the exponent for the runtime
using the number of canonical queries and the tree width.
"""

if __name__ == '__main__':

    parameter_combination_pickle_path = snakemake.input[0]
    parameter_combination_pickle_file = open(parameter_combination_pickle_path, "rb")
    parameter_combinations: JobParameters = pickle.load(parameter_combination_pickle_file)
    parameter_combination_pickle_file.close()

    epsilon = parameter_combinations.epsilon
    epsilon_str = epsilon_float_to_str(epsilon)
    experiment_id = parameter_combinations.experiment_id
    experiment_id_ctx.set(experiment_id)
    dataset_name = parameter_combinations.dataset
    query_list = parameter_combinations.query_list
    query_str = parameter_combinations.query_string
    laplace_approximation = parameter_combinations.laplace_approximation
    laplace_approximation_algorithm = parameter_combinations.laplace_approximation_algorithm
    algo = parameter_combinations.algo
    repeat_index = parameter_combinations.repeat_index

    seed = snakemake.config['seed']
    unique_seed = get_seed(seed, repeat_index)
    rng = set_seed(unique_seed)

    target_file = str(snakemake.output[0])

    storage_file_path = f"logs/napsu_linear_regression_test_storage_{experiment_id}.pickle"
    mode: Literal["append"] = "append"
    timer_file_path = "logs/napsu_linear_regression_test_timer.csv"

    storage = ExperimentStorage(file_path=storage_file_path, mode=mode)
    timer = Timer(file_path=timer_file_path, mode=mode)

    n_categories = int(dataset_name.split("x")[1])

    dataframe = create_dummy_dataset(n_columns=5, n_rows=10000, n_categories=n_categories)
    dataframe = transform_for_modeling(dataset_name, dataframe)

    n, d = dataframe.shape
    query = []
    delta = (n ** (-2))

    timer_meta = {
        "experiment_id": experiment_id,
        "dataset_name": dataset_name,
        "query": query,
        "epsilon": epsilon,
        "delta": delta,
        "MCMC_algo": algo,
        "laplace_approximation": laplace_approximation,
        "laplace_approximation_algorithm": laplace_approximation_algorithm,
        "repeat_index": repeat_index,
        "original_seed": unique_seed
    }

    pid = timer.start(f"Main run", **timer_meta)

    print(
        f"PARAMS: \n\tdataset name {dataset_name}\n\tcliques {query_str}\n\tMCMC algo {algo}\n\tepsilon {epsilon_str}\n\tdelta: {delta}\n\tLaplace approximation {laplace_approximation}")

    print("Initializing NapsuMQModel")
    model = NapsuMQModel()

    result: NapsuMQResult
    inf_data: InferenceDataT

    result, inf_data = model.fit(
        data=dataframe,
        dataset_name=dataset_name,
        rng=rng,
        epsilon=epsilon,
        delta=delta,
        column_feature_set=query,
        MCMC_algo=algo,
        use_laplace_approximation=laplace_approximation,
        return_inference_data=True,
        enable_profiling=False,
        laplace_approximation_algorithm=laplace_approximation_algorithm,
        laplace_approximation_forward_mode=True
    )

    timer.stop(pid)

    print("Writing model to file")
    result.store(target_file)

    inf_data.to_netcdf(f"logs/inf_data_linear_regression_{dataset_name}_{epsilon_str}e_{repeat_index}_repeat.nc")

    # Save storage and timer results every iteration
    storage.save_as_pickle(file_path=storage_file_path, experiment_id=experiment_id)
    timer.save(file_path=timer_file_path, mode=mode, index=False)


"""
H4xton fake typing chat exploit
"""
import requests,os,sys,time,random

class Discord():

    def TypeExploit(token,channelid):
        url = f"https://canary.discord.com/api/v9/channels/{channelid}/typing"
        headers = {
        'authority': 'canary.discord.com',
        'content-length': '0',
        'sec-ch-ua': '',
        'accept-language': 'da,en-US;q=0.9',
        'sec-ch-ua-mobile': '?1',
        'authorization': token,
        'user-agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.164 Mobile Safari/537.36',
        'x-discord-locale': 'da',
        'accept': '*/*',
        'origin': 'https://canary.discord.com',
        'sec-fetch-site': 'same-origin',
        'sec-fetch-mode': 'cors',
        'sec-fetch-dest': 'empty'
        }
        while True:
            response = requests.request("POST", url, headers=headers, data=None)
            if response.status_code == 204:
                print("[+] Succesfuly send a Heartbeat")
            else:
                exit("[-] Failed to send a Heartbeat -> ".format(response.text))

if __name__ == "__main__":
    token = input("[+] Enter your token: ")
    channelid = input("[+] Channel ID: ")
    while True:
        Discord.TypeExploit(token,channelid)


__author__ = 'Xing'
# Given a linked list, remove the nth node from the end of list and return its head.
#
# For example,
#
#    Given linked list: 1->2->3->4->5, and n = 2.
#
#    After removing the second node from the end, the linked list becomes 1->2->3->5.
class ListNode(object):
    def __init__(self, x):
        self.val = x
        self.next = None

class Solution(object):
    def removeNthFromEnd(self, head, n):
        """
        :type head: ListNode
        :type n: int
        :rtype: ListNode
        """
        first,second=head,head
        while n:
            first=first.next
            n-=1
        if not first:
            return head.next
        while first.next!=None:
            first=first.next
            second=second.next
        second.next=second.next.next
        return head


from tkinter import*
import tkinter as tk
import cv2
import numpy as np
import webbrowser

#GRAY ONLY
def obj():
    thres = 0.45
    nms_threshold = 0.2
    cap = cv2.VideoCapture(0)

    className = []
    classFile = 'coco.names'
    with open(classFile, 'rt') as f:
        className = f.read().rstrip('\n').split('\n')

    # print(classNames)
    configPath = 'ssd_mobilenet_v3_large_coco_2020_01_14.pbtxt'
    weightsPath = 'frozen_inference_graph.pb'

    net = cv2.dnn_DetectionModel(weightsPath, configPath)
    net.setInputSize(320, 320)
    net.setInputScale(1.0 / 127.5)
    net.setInputMean((127.5, 127.5, 127.5))
    net.setInputSwapRB(True)

    while True:
        success, img = cap.read()
        classIds, confs, bbox = net.detect(img, confThreshold=thres)
        bbox = list(bbox)
        confs = list(np.array(confs).reshape(1, -1)[0])
        confs = list(map(float, confs))
        # print(type(confs[0]))
        # print(confs)

        indices = cv2.dnn.NMSBoxes(bbox, confs, thres, nms_threshold)

        for i in indices:
            i = i[0]
            box = bbox[i]
            x, y, w, h = box[0], box[1], box[2], box[3]
            cv2.rectangle(img, (x, y), (x + w, h + y), color=(0, 255, 0), thickness=2)
            cv2.putText(img, className[classIds[i][0] - 1].upper(), (box[0] + 10, box[1] + 30),
                        cv2.FONT_HERSHEY_COMPLEX, 1, (0, 255, 0), 2)

        cv2.imshow("Display Object Detection                Q for EXIT", img)

        if cv2.waitKey(1) & 0xFF == ord('q'):
                break
    cv2.destroyAllWindows()

#GRAY ONLY
def GRAY():
    path = cv2.VideoCapture(0)

    while True:
        success, img = path.read()
        imgc = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

        cv2.imshow("Display GRAY                Q for EXIT",imgc)

        if cv2.waitKey(1) & 0xFF == ord('q'):
            break
    cv2.destroyAllWindows()

#HUE ONLY
def HLS():
    path = cv2.VideoCapture(0)

    while True:
        success, img = path.read()
        imgHLS = cv2.cvtColor(img, cv2.COLOR_BGR2HLS)

        cv2.imshow("Display HLS           Q for EXIT",imgHLS)

        if cv2.waitKey(1) & 0xFF == ord('q'):
            break
    cv2.destroyAllWindows()

#HSV
def HSV():

    path = cv2.VideoCapture(0)

    while True:
        success, img = path.read()
        imgHSV = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)

        cv2.imshow("Display HSV                Q for EXIT", imgHSV)

        if cv2.waitKey(1) & 0xFF == ord('q'):
            break
    cv2.destroyAllWindows()

#VIDEO + REC
def Recandvid():
    def empty(a):
          pass

    def stackImages(scale, imgArray):
        rows = len(imgArray)
        cols = len(imgArray[0])
        rowsAvailable = isinstance(imgArray[0], list)
        width = imgArray[0][0].shape[1]
        height = imgArray[0][0].shape[0]
        if rowsAvailable:
            for x in range(0, rows):
                for y in range(0, cols):
                    if imgArray[x][y].shape[:2] == imgArray[0][0].shape[:2]:
                        imgArray[x][y] = cv2.resize(imgArray[x][y], (0, 0), None, scale, scale)
                    else:
                        imgArray[x][y] = cv2.resize(imgArray[x][y], (imgArray[0][0].shape[1], imgArray[0][0].shape[0]),
                                                    None, scale, scale)
                    if len(imgArray[x][y].shape) == 2: imgArray[x][y] = cv2.cvtColor(imgArray[x][y], cv2.COLOR_GRAY2BGR)
            imageBlank = np.zeros((height, width, 3), np.uint8)
            hor = [imageBlank] * rows
            hor_con = [imageBlank] * rows
            for x in range(0, rows):
                hor[x] = np.hstack(imgArray[x])
            ver = np.vstack(hor)
        else:
            for x in range(0, rows):
                if imgArray[x].shape[:2] == imgArray[0].shape[:2]:
                    imgArray[x] = cv2.resize(imgArray[x], (0, 0), None, scale, scale)
                else:
                    imgArray[x] = cv2.resize(imgArray[x], (imgArray[0].shape[1], imgArray[0].shape[0]), None, scale,
                                             scale)
                if len(imgArray[x].shape) == 2: imgArray[x] = cv2.cvtColor(imgArray[x], cv2.COLOR_GRAY2BGR)
            hor = np.hstack(imgArray)
            ver = hor
        return ver

    path = cv2.VideoCapture(0)
    cv2.namedWindow("TrackBars")
    cv2.resizeWindow("TrackBars", 640, 240)
    cv2.createTrackbar("Hue Min", "TrackBars", 0, 179, empty)
    cv2.createTrackbar("Hue Max", "TrackBars", 19, 179, empty)
    cv2.createTrackbar("Sat Min", "TrackBars", 110, 255, empty)
    cv2.createTrackbar("Sat Max", "TrackBars", 240, 255, empty)
    cv2.createTrackbar("Val Min", "TrackBars", 153, 255, empty)
    cv2.createTrackbar("Val Max", "TrackBars", 255, 255, empty)

    fourcc = cv2.VideoWriter_fourcc(*'XVID')
    out = cv2.VideoWriter("Recoded_Vid.avi", fourcc, 20.0, (740, 480))

    while True:
        success, img = path.read()
        imgHSV = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
        h_min = cv2.getTrackbarPos("Hue Min", "TrackBars")
        h_max = cv2.getTrackbarPos("Hue Max", "TrackBars")
        s_min = cv2.getTrackbarPos("Sat Min", "TrackBars")
        s_max = cv2.getTrackbarPos("Sat Max", "TrackBars")
        v_min = cv2.getTrackbarPos("Val Min", "TrackBars")
        v_max = cv2.getTrackbarPos("Val Max", "TrackBars")
        print(h_min, h_max, s_min, s_max, v_min, v_max)
        lower = np.array([h_min, s_min, v_min])
        upper = np.array([h_max, s_max, v_max])
        mask = cv2.inRange(imgHSV, lower, upper)
        imgResult = cv2.bitwise_and(img, img, mask=mask)

        imgStack = stackImages(0.6, ([img, imgHSV], [mask, imgResult]))
        cv2.imshow("All Display and Recording....       Q for EXIT", imgStack)

        if cv2.waitKey(1) & 0xFF == ord('q'):
            break
    cv2.destroyAllWindows()

def callback(event):
    webbrowser.open_new(event.widget.cget("text"))

root =Tk()
button1= Button(root,text="Video and Record",bg='#47d1d1', command=Recandvid).place(x=40, y=70)
button2= Button(root,text="HSV",bg='#47d1d1',command=HSV).place(x=180, y=70)
button3= Button(root,text="HLS",bg='#47d1d1', command=HLS).place(x=230, y=70)
button4= Button(root,text="GRAY",bg='#47d1d1', command=GRAY).place(x=280, y=70)
button5= Button(root,text="Object Detection",bg='#47d1d1', command=obj).place(x=360, y=70)

lbl = tk.Label(root, text=r"https://github.com/jahin44", fg="blue", cursor="hand2")
lbl.pack()
Label(root, text=" Click here to get more software =>", bg="#eee6ff", fg="black").place(x=0, y=0)

lbl.bind("<Button-1>", callback)
lbl1 = tk.Label(root, text=r"https://jahinhasan.blogspot.com", fg="blue", cursor="hand2")
Label(root, text="     Click here to know about me =>", bg="#eee6ff", fg="black").place(x=0, y=20)

lbl1.pack()
lbl1.bind("<Button-1>", callback)

root.geometry("560x300+120+200")
root.mainloop()


from django import forms

from crispy_forms.layout import Submit

from core.forms.base_crispy_form import BaseCrispyForm
from core.models.round import Round

class WinnerForm(BaseCrispyForm, forms.ModelForm):
    SUBMIT_BUTTON_VALUE = "Declare Winner"
    SUBMIT_BUTTON_CSS_CLASSES = "btn-no-bg btn-outline-info"

    class Meta:
        model = Round
        fields = ["winner"]

    def __init__(
        self,
        *args,
        players=None,
        enable_reroll=True,
        moderator_id: int = None,
        **kwargs
    ):
        self.moderator_id = moderator_id
        super().__init__(*args, **kwargs)

        if self.instance.winner:
            self.helper.inputs.clear()
            self.helper.add_input(
                Submit(
                    name="action",
                    value="Start Next Round",
                    css_class="btn-no-bg btn-outline-primary",
                )
            )
            del self.fields["winner"]
        else:
            self.players = players

            player_choices = [{"pk": None, "player_name": "-----"}] + list(players)
            self.fields["winner"].choices = (
                (x["pk"], x["player_name"]) for x in player_choices
            )

            self.helper.add_input(
                Submit(
                    name="action",
                    value="Reveal Submitter",
                    css_class="btn-no-bg btn-outline-danger",
                )
            )
            if enable_reroll:
                self.helper.add_input(
                    Submit(
                        name="action",
                        value="Shuffle",
                        css_class="btn-no-bg btn-outline-primary",
                    )
                )

    def save(self, commit=True):
        self.instance.moderator_id = self.moderator_id

        super().save(self)


import streamlit as st
import pytube as pt
import os
import subprocess
import re
from utils import logtime, load_ffmpeg
import whisper
from langchain.document_loaders import YoutubeLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter

URL = 'URL'
TEXT = 'TEXT'
WHISPER = 'WHISPER'
PROCESSING = 'PROCESSING'
STATES = [URL, TEXT, WHISPER, PROCESSING]
AUDIO_FILE = "audio.mp3"
AUDIO_EXISTS = "AUDIO_EXISTS"
model = ''

st.title('Youtube Audio+Text')

def init_state():
    if URL not in st.session_state:
        st.session_state[URL] = ''

    if TEXT not in st.session_state:
        st.session_state[TEXT] = ''

    if WHISPER not in st.session_state:
        st.session_state[WHISPER] = ''

    if AUDIO_EXISTS not in st.session_state:
        st.session_state[AUDIO_EXISTS] = False

    if not st.session_state[URL]:
        clear_old_files()
        print("Clear old files")

def clear_old_files():
    for file in os.listdir():
        if file.endswith(".mp3") or file == 'transcript.txt':
            os.remove(file)
            print(f"Removed old files::{file}")

def extract_youtube_video_id(url):
  regex = r"v=([^&]+)"
  match = re.search(regex, url)
  if match:
    return match.group(1)
  else:
    return None

@logtime
def load_whisper():
    # if not model:
    model = whisper.load_model("small")
    print('Loaded Whisper Medium model')
    # else:
    #     print('Already downloaded Whisper model')
    print('Transcribing with Whisper model')
    result = model.transcribe("audio.mp3")
    st.session_state[WHISPER] = result["text"]
    write_file(result["text"], "whisper.txt")
AUDIO_FILE = "audio.mp3"

def load_audio():
    if os.path.exists(AUDIO_FILE):
        st.session_state[AUDIO_EXISTS] = True
        audio_file = open(AUDIO_FILE, 'rb')
        audio_bytes = audio_file.read()
        print(f"Audio file exists...{len(audio_bytes)}")
        st.audio(audio_bytes, format="audio/mp3")
    elif st.session_state[AUDIO_EXISTS]:
        st.session_state[AUDIO_EXISTS] = False

def display():
    container = st.container()
    text_container = st.container()
    # whisper_container = st.container()
    load_audio()

    #Download Button section
    col1, col2 = st.columns(2)
    with col1:
        if st.session_state[AUDIO_EXISTS]:
            st.download_button("Download Audio","file","audio.mp3","application/octet-stream")
    with col2:
        if os.path.exists("transcript.txt"):
            st.download_button("Download Transcript",st.session_state[TEXT],"transcript.txt","text/plain")

    with container:
        with st.form(key='input_form'):
            user_input = st.text_input("Youtube URL:", placeholder="http://www.youtube.com", key=URL)
            input_submit_button = st.form_submit_button(label='Send')

    if input_submit_button and user_input:

        st.write("You entered... " + st.session_state[URL])
        # transcribe()
        # download()
        # download_audio()
        load_whisper()

    with text_container:
       st.text_area(label="Youtube Transcript:",
                    height=200,
                    value=st.session_state[TEXT])
    # with whisper_container:
    #    st.text_area(label="Whisper Transcript:",
    #                 height=200,
    #                 value=st.session_state[WHISPER])

@logtime
def download_audio():
    if st.session_state[URL]:
        print("Downloading....")
        yt = pt.YouTube(st.session_state[URL])
        stream = yt.streams.filter(only_audio=True)[0]
        stream.download(filename="audio.mp3")
        print("Downloaded Audio file....")

def download():
  id = extract_youtube_video_id(st.session_state[URL])
  command = [f"yt-dlp --no-config -v --extract-audio --audio-format mp3 {st.session_state[URL]} -o audio.mp3"]
  print(command)
  out = subprocess.run(command, shell=True)
  print('Download with YT-DLP done!!')

@logtime
def transcribe():
    loader = YoutubeLoader.from_youtube_url(
        st.session_state[URL], add_video_info=True)
    splitter = RecursiveCharacterTextSplitter(chunk_size=2000,chunk_overlap=500)
    docs = loader.load_and_split(splitter)
    length = len(docs)
    index = int(length/3+1)
    print(f"Loaded {length} documents, Displaying {index}-th document")
    # st.session_state[TEXT] = docs[index].page_content
    st.session_state[TEXT] = write_chunks(docs,"transcript.txt")

@logtime
def write_chunks(docs, filename):
    full_doc = ''
    for doc in docs:
       full_doc = full_doc + doc.page_content + "\n"
    with open(filename, "w") as f:
        f.write(full_doc)
        return full_doc

def write_file(text, filename):
    with open(filename, "w") as f:
        f.write(text)
        # return full_doc

def main():
    # load_ffmpeg()
    init_state()
    display()

if __name__ == "__main__":
    main()


from html import escape
import constants
import re

def attack_found(data_dict):
    """
    This function uses the attack detecting functions below to check for possible cases of an attack.
    Input: data_dict - a dictionary containing the data from the user request.
    Output: True if a possible attack is detected and False if it is not.
    """
    if csrf_referer(data_dict["headers"]) or detect_possible_sql_injection(data_dict["data"]):
        return True
    return False

def xss_and_sqli_mitigation(data_dict):
    """
    This function will sanitize the user input, in order to prevent XSS and SQL Injection attacks.
    Input: data_dict - a dictionary containing the data from the user request.
    Output: None. The function will modify the data_dict, there's no need to return.
    """
    for key, value in data_dict.items():
        value = "".join([char for char in value if char in constants.SQL_WHITELIST])
        data_dict[key] = escape(value)#re.escape(value)) #escapes both html and sql injection. First escape_string() is for sql injection, second escape() is for html (XSS).

    return data_dict

def csrf_referer(headers):
    """
    This function checks if the Referer header is set to the same domain (Host) as the request. (Note that the header is spelled referer, not referrer as spelled in English)
    Essentially checking if the user is trying to access the server from the same domain or from another domain which might be a CSRF attack.
    Input: headers - a dictionary containing the headers from the user request.
    Output: True if the Referer header is not set to the same domain as the request and False if it is.
    """
    if "Referer" in headers:
        refferer = headers["Referer"].split("//")[1].split("/")[0]
        if refferer != headers["Host"]:
            return True
        else:
            return False

    return False

def csrf_samesite_lax(data_dict):
    """
    This function sets the SameSite attribute to Lax for the cookie, in order to prevent CSRF attacks.
    Input: data_dict - a dictionary containing the data from the server response.
    Output: None. The function will modify the data_dict, there's no need to return.
    """
    if "Set-Cookie" in data_dict["headers"]:
        if "SameSite=Lax" not in data_dict["headers"]["Set-Cookie"]:
            if "SameSite" in data_dict["headers"]["Set-Cookie"]:
                set_cookie_attributes = data_dict["headers"]["Set-Cookie"].split(";")
                wanted_index = 0
                for index, attribute in enumerate(set_cookie_attributes):
                    if "SameSite" in attribute:
                        wanted_index = index
                        break
                set_cookie_attributes[wanted_index] = "SameSite=Lax"
                data_dict["headers"]["Set-Cookie"] = ";".join(set_cookie_attributes)

            else:
                data_dict["headers"]["Set-Cookie"] += "; SameSite=Lax"

def detect_possible_sql_injection(data_dict):
    """
    This function detect a possible SQL injection, return True if it is detected and False if it is not.
    The function will loop through the data and check if there is a possibility of an SQL injection in it.
    Note: This function is not 100% accurate, and might have some false positives but it is good enough for our purposes.
    Input: data_dict - a dictionary containing the data from the user request.
    Output: True if possible SQL Injection is detected and False if it is not.
    """
    for key, value in data_dict.items():
        if re.search(constants.INJECTION_DETECTING_REGEX, value):
            return True

    return False


import codecs

from django.conf import settings
from django.views.generic.edit import CreateView
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.urls import reverse
from django.http.response import HttpResponseRedirect
from django.contrib import messages

from achievement.models import Achievement

from ...models import (
    BulkCreationConsultant, Consultant
)
from ...forms import BulkCreationConsultantForm
from .helpers import read_name_email_coins_from_csv

class BulkCreationConsultantFormView(
        PermissionRequiredMixin,
        CreateView
):
    template_name = 'network/bulk_creation_form.html'
    permission_required = settings.CONSULTANT_FULL_PERMS_ADD_CONSULTANT
    model = BulkCreationConsultant
    form_class = BulkCreationConsultantForm
    raise_exception = True

    def get_success_message(self, *args, **kwargs):
        bulk_creation = kwargs.get('bulk_creation')
        return '{} invitations created successfully'.format(bulk_creation.consultants.success().count())

    def get_success_url(self, bulk_creation):
        return reverse('consultant:bulk-add-detail', kwargs={'pk': bulk_creation.pk})

    def create_bulk_creation(self, form):
        bulk_creation = form.save()
        bulk_creation.created_by = self.request.user
        bulk_creation.save()
        return bulk_creation

    def create_bulk_user(self, new_user, bulk_creation):
        return BulkCreationConsultant.objects.create(
            name=new_user.name,
            email=new_user.email,
            coins=new_user.coins,
            bulk_creation=bulk_creation,
        )

    def invite_consultant(self, new_user, custom_message):
        short_name = new_user.name.split(' ')[0]
        consultant = Consultant.objects.create_consultant(
            short_name=short_name,
            full_name=new_user.name,
            email=new_user.email,
            invite_user=self.request.user,
            registration_process=True,
            custom_text=custom_message,
            coins=new_user.coins,
        )
        return consultant

    def update_existing_user(self, new_user, bulk_creation_consultant):
        if not new_user.user.is_consultant:
            bulk_creation_consultant.set_error_email_used()
        else:
            consultant = new_user.user.consultant
            if not new_user.has_coins:
                bulk_creation_consultant.set_error_email_used()
            elif new_user.has_achievement_created():
                bulk_creation_consultant.set_error_achievement_created()
            else:
                Achievement.objects.create_reward_for_consultant(
                    consultant,
                    new_user.coins,
                )
            bulk_creation_consultant.set_consultant(consultant)

    def form_valid(self, form):
        bulk_creation = self.create_bulk_creation(form)

        filecontent = form.cleaned_data.get('file_csv')
        filecontent = codecs.iterdecode(filecontent, 'utf-8')
        users_list = read_name_email_coins_from_csv(filecontent)
        for new_user in users_list:
            bulk_creation_consultant = self.create_bulk_user(new_user, bulk_creation)
            if new_user.missing_information():
                bulk_creation_consultant.set_error_missing_information()
            elif new_user.exists:
                self.update_existing_user(new_user, bulk_creation_consultant)
            else:
                consultant = self.invite_consultant(
                    new_user, form.cleaned_data.get('custom_text'),
                )
                bulk_creation_consultant.set_consultant(consultant)
        success_message = self.get_success_message(**{'bulk_creation': bulk_creation})
        messages.success(self.request, success_message)
        return HttpResponseRedirect(self.get_success_url(bulk_creation))


def obtener_palabras_mayores(parrafo: str, largo: int) -> list:
    palabras = parrafo.split(" ")

    palabras_mayores = []

    for palabra in palabras:
        palabra_limpia = limpiar_palabra(palabra).upper()
        if len(palabra_limpia) == largo: palabras_mayores.append(palabra_limpia)

    return palabras_mayores

def sacar_palabras_prohibidas(parrafo: str, palabras_prohibidas: list) -> str:
    for palabra in palabras_prohibidas:
        parrafo = parrafo.replace(palabra, "*"*4)

    return parrafo

def imprimir_ocurrencias(ocurrencias: dict) -> None:
    for palabra in ocurrencias:
        print(f" - {palabra} tiene {ocurrencias[palabra]} ocurrencia(s)")

def limpiar_palabra(palabra: str) -> str:
    caracteres_invalidos = []
    for i in range(len(palabra)):
        if not palabra[i].isalnum(): caracteres_invalidos.append(palabra[i])

    for c in caracteres_invalidos:
        palabra = palabra.replace(c, "")

    return palabra

def contar_ocurrencias(parrafo: str) -> dict:
    palabras = parrafo.split(" ")

    for i in range(len(palabras)):
        palabras[i] = limpiar_palabra(palabras[i]).upper()

    ocurrencias = {}

    for palabra in palabras:
        if palabra in ocurrencias: ocurrencias[palabra] += 1
        else: ocurrencias[palabra] = 1

    return ocurrencias

def main():
    parrafo = "Hay quienes me buscan toda una vida, pero no nos encontramos, y quienes reciben mi beso y me rechazan, desagradecidos, desdichados. A veces, parece que prefiero a los inteligentes, a los bellos, a los altos, pero bendigo a todos lo que tienen el coraje de intentarlo. En general, cuando actúo, soy de mano suave, dulce, de miel, pero si me desprecian, me convierto en una bestia difícil de vencer. Porque aunque mis golpes, todos dan siempre en el blanco, cuando mato, lo hago muy, muy despacio..."

    palabras_prohibidas = ["suave", "miel", "mato"]

    print(f"-- Cantidad de Ocurrencias por Palabra: ")
    imprimir_ocurrencias(contar_ocurrencias(parrafo))
    print()
    print(f"-- Parrafo sin Palabras Prohibidas: ")
    print(sacar_palabras_prohibidas(parrafo, palabras_prohibidas))
    print()
    print(f"-- Palabras de largo mayor a 5: ")
    print(obtener_palabras_mayores(parrafo, 5))

main()


#Python imports
import json
from uuid       import UUID
from datetime   import date
from datetime   import datetime
from typing     import Optional, List

#Pydantic imports
from pydantic import Field as FD
from pydantic import BaseModel as BMW
from pydantic import EmailStr

#FastAPI imports
from fastapi import FastAPI
from fastapi import status, HTTPException
from fastapi import Body, Path, Form

#This is the app
app = FastAPI()

#Those are the models to use
class UserBase(BMW):

    user_id : UUID = FD(
        ...,
    )

    email: EmailStr = FD(
        ...,
        example=""
    )

class UserLogin(UserBase):

    password: str = FD(
        ...,
        min_length=8,
        max_length=22,
        example=""  ,
    )

class User(UserBase):

    user_name: str = FD(
        ...,
        min_length=5,
        max_length=15,
        example="@cordellatt"
    )
    first_name : str = FD(
        ...,
        min_length=1,
        max_length=20,
        example="",
    )

    last_name : str = FD(
        ...,
        min_length=1,
        max_length=20,
        example="",
    )

    birth_date: Optional[date] = FD(default=None)

class UserRegister(User):

    password: str = FD(
        ...,
        min_length=8,
        max_length=22,
        example=""
    )

class Tweet(BMW):

    tweet_id : str = FD(
        ...,
        min_length=5,
        max_length=15,
    )

    content: str = FD(
        ...,
        min_length=1,
        max_length=256,
        example="Hello dear. This is the first tweet from my Api"
    )

    created_at: datetime = FD(default=datetime.now())
    updated_at: Optional[datetime] = FD(default=None)
    by: UserBase = FD (...)

# Troll
@app.post(
    path="/F",
    tags=["Troll"]
    )
def troll():
    return "Me está llevando el diablo, send help"

#Home: Here you can see all tweets
@app.get(
    path="/",
    response_model= List[Tweet],
    status_code= status.HTTP_200_OK,
    summary="Show all the tweets",
    tags=["Home"],
)
def home():
    """
    Show all tweets

    In this page you can see all the tweers in the app

    Parameters:
        -

    Returns a Json whith the basics users information:

    - tweet:id : UUID
    - content : str
    - created : datetime
    - updated : Optional[datetime]
    - by: UserBase
    """
    with open("tweets.json", "r", encoding="utf-8") as tweets_data:
        results = json.loads(tweets_data.read())
        return results

#Login
@app.post(
    path="/login/{user_name}",
    response_model= User,
    status_code= status.HTTP_200_OK,
    summary="Login a user",
    tags=["User"],
)
def login(
            user_name: str = Path(..., title="User name"),
            password: str = Form(..., title="Password")
    ):
    """
    Login

    This Path Operation login the user in the app...

    Parameters:
        - Request body parameter:
        - user: UserRegister

    Returns a Json whith the basics users information:

        - user id : UUID
        - email : Emailstr
        - first name: str
        - last name: str
        - birth date: datetime
    """
    with open("user.json", "r", encoding="utf-8") as user_data:
        result: list[dict] = json.load(user_data)

    user_position = 0

    while user_position < len(result) + 1:

        for user in result:

            if result[user_position]["user_name"] == user_name and result[user_position]["password"] == password:
                return user

            else:
                user_position = user_position + 1

            if user_position == len(result):
                raise HTTPException(
                status_code = status.HTTP_404_NOT_FOUND,
                detail="That user don't exist!")

## Path Operations

### Users

#### Register a user
@app.post(
    path="/signup",
    response_model= User,
    status_code= status.HTTP_201_CREATED,
    summary="Register a new User",
    tags=["User"],
)
def signup(user: UserRegister = Body(...)):
    """
    Sign-Up

    Register a user in the app

    Parameters:
        - Request body parameter
            - user: UserRegister

    Returns a Json whith the basice user information:

        - user id : UUID
        - email : Emailstr
        - first name: str
        - last name: str
        - birth date: datetime
    """
    with open("user.json", "r+", encoding="utf-8") as user_data:
        results = json.loads(user_data.read())
        user_dict = user.dict()
        user_dict["user_id"] = str(user_dict["user_id"])
        user_dict["birth_date"] = str(user_dict["birth_date"])
        results.append(user_dict)
        user_data.seek(0)
        user_data.write(json.dumps(results))
        return user

#### Show all user
@app.get(
    path="/users",
    response_model= List[User],
    status_code= status.HTTP_200_OK,
    summary="Show all the users",
    tags=["User"],
)
def show_all_users():
    """
    Show all users

    In this page you can see all the users in the app

    Parameters:
        - Request body parameter:
        - user: UserRegister

    Returns a Json whith the basics users information:

        - user id : UUID
        - email : Emailstr
        - first name: str
        - last name: str
        - birth date: datetime
    """
    with open("user.json", "r", encoding="utf-8") as user_data:
        results = json.loads(user_data.read())
        return results

#### Show a user:
@app.get(
    path="/user/{user_name}",
    response_model= User,
    status_code= status.HTTP_200_OK,
    summary="Show a User",
    tags=["User"],
)
def show_a_user(user_name: str = Path(..., title="User name")):
    """
    Show a user

    In this page you can see a user with her user name

    Parameters:
    - User's user name

    Returns a Json whith the basics users information:

    - user id : UUID
    - email : Emailstr
    - user name: str
    - first name: str
    - last name: str
    - birth date: datetime
    """
    with open("user.json", "r", encoding="utf-8") as user_data:
        result: list[dict] = json.load(user_data)

    user_position = 0

    while user_position < len(result) + 1:

        for user in result:

            if result[user_position]["user_name"] == user_name:
               return user

            if not result[user_position]["user_name"] == user_name:
                user_position = user_position + 1

            if user_position == len(result):
                raise HTTPException(
                status_code = status.HTTP_404_NOT_FOUND,
                detail="That user don't exist!")

#### Delete a user
@app.delete(
    path="/user/{user_name}/delete",
    response_model= str,
    status_code= status.HTTP_200_OK,
    summary="Delete a user",
    tags=["User"],
)
def delete_user(user_name: str = Path(..., title="User name")):
    """
    Delete a user

    In this page you can delete a user with her user name

    Parameters:
    - User's user name

    Returns a message with the result
    """
    with open("user.json", "r+", encoding="utf-8") as user_data:
        result: list[dict] = json.load(user_data)

        for user in result:

            if user["user_name"] == str(user_name):
                result.remove(user)

                with open("user.json", "w", encoding="utf-8") as user_data:
                    user_data.seek(0)
                    json.dump(result, user_data)
                return f"The user {user_name} was be eliminated"

        raise HTTPException(
            status_code = status.HTTP_404_NOT_FOUND,
            detail="This user don't exist!"
            )

#### Update a user
@app.put(
    path="/user/{user_name}/update/",
    response_model= User,
    status_code= status.HTTP_200_OK,
    summary="Update a user",
    tags=["User"],
)
def update_user(

    user_name : str = Path(
    ...,
    title="User name",
    description="Please insert the user that you want update",
    ),

    user: UserRegister = Body(...),
):
    """

    Update a user

    In this page you can update a user with his user name

    Parameters:
        user name: str
        user: UserRegister
        -

    Returns the new tweet model
    """

    user_name = str(user_name)
    user_dict = user.dict()
    user_dict['user_id'] = str(user_dict['user_id'])
    user_dict['birth_date'] = str(user_dict['birth_date'])

    with open("user.json", "r+", encoding="utf-8") as user_data:

        result = json.loads(user_data.read())

        for user in result:

            if  user['user_name'] == user_name:
                result[result.index(user)] = user_dict

                with open("user.json", "w", encoding="utf-8") as user_data:
                    user_data.seek(0)

                    user_data.write(json.dumps(result))
                return user_dict

            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail="Ese usuario no existe")

### Tweets

#### Post a tweet
@app.post(
    path="/post",
    response_model=Tweet,
    status_code=status.HTTP_201_CREATED,
    summary="Post a tweet",
    tags=["Tweets"]
)
def post_a_tweet(tweet : Tweet = Body(...)):
    """
    Post a Tweet

    In this page you can create a new tweet

    Parameters:
    - Request body parameter:
        - tweet: Tweets

    Returns a Json whith the basics users information:

    - tweet:id : UUID
    - content : str
    - created : datetime
    - updated : Optional[datetime]
    - by: UserBase
    """

    with open("tweets.json", "r+", encoding="utf-8") as tweet_data:
        results = json.loads(tweet_data.read())
        tweet_dict = tweet.dict()
        tweet_dict["tweet_id"] = str(tweet_dict["tweet_id"])
        tweet_dict["created_at"] = str(tweet_dict["created_at"])
        if tweet_dict["updated_at"] is not None:
            tweet_dict["updated_at"] = str(tweet_dict["updated_at"])

        tweet_dict["by"]["user_id"] = str(tweet_dict["by"]["user_id"])
        tweet_dict["by"]["email"] = str(tweet_dict["by"]["email"])
        results.append(tweet_dict)
        tweet_data.seek(0)
        tweet_data.write(json.dumps(results))
        return tweet

#### Show a tweet
@app.get(
    path="/tweets/{tweet_id}",
    response_model= Tweet,
    status_code= status.HTTP_200_OK,
    summary="Show a tweet",
    tags=["Tweets"],
)
def show_a_tweet(tweet_id: str = Path(..., title="First name")):
    """
    Show a tweet

    In this page you can see a user with her first name

    Parameters:
    - User's first name

    Returns a Json whith the basics users information:

    - tweet:id : UUID
    - content : str
    - created : datetime
    - updated : Optional[datetime]
    - by: UserBase
    """
    with open("tweets.json", "r", encoding="utf-8") as tweets_data:
        result: list[dict] = json.load(tweets_data)

    tweets_position = 0

    while tweets_position < len(result) + 1:
        for tweets in result:
            if result[tweets_position]["tweet_id"] == tweet_id:
               return tweets

            if not result[tweets_position]["tweet_id"] == tweet_id:
                tweets_position = tweets_position + 1

            if tweets_position == len(result):
                raise HTTPException(
                status_code = status.HTTP_404_NOT_FOUND,
                detail="That tweet don't exist!")

#### Delete a tweet
@app.delete(
    path="/tweets/{tweet_id}/delete",
    response_model= str,
    status_code= status.HTTP_200_OK,
    summary="Delete a tweet",
    tags=["Tweets"],
)
def delete_tweet(tweet_id : str = Path(..., title="Tweet Id")):
    """
    Delete a tweet

    In this page you can delete a tweet with his id

    Parameters:
        -

    Returns a message with the result.
    """
    with open("tweets.json", "r+", encoding="utf-8") as tweets_data:
        result: list[dict] = json.load(tweets_data)

        for tweet in result:

            if tweet["tweet_id"] == str(tweet_id):
                result.remove(tweet)

                with open("tweets.json", "w", encoding="utf-8") as tweets_data:
                    tweets_data.seek(0)
                    json.dump(result, tweets_data)
                    return f"The tweet {tweet_id} was be eliminated"

        raise HTTPException(
            status_code = status.HTTP_404_NOT_FOUND,
            detail="This tweet don't exist!"
            )

#### Update a tweet
@app.put(
    path="/tweets/{tweet_id}/update",
    response_model= Tweet,
    status_code= status.HTTP_200_OK,
    summary="Update a tweet",
    tags=["Tweets"],
)
def update_tweet(

        tweet_id : str = Path(
            ...,
            title="Tweet Id"
            ),

        new_content: str = Form(
            ...,
            title="New tweet information")

):
    """
    Update a tweet

    In this page you can update a tweet content with his id

    Parameters:
        tweet id: str
        new_content: str
        -

    Returns the new tweet model
    """
    with open("tweets.json", "r+", encoding="utf-8") as tweets_data:
        result: list[dict] = json.load(tweets_data)

        for tweet in result:

            if tweet['tweet_id'] == str(tweet_id):
                tweet['content'] = new_content

                with open("tweets.json", "w", encoding="utf-8") as tweets_data:
                    tweets_data.seek(0)
                    tweets_data.write(json.dumps(result))
                    return tweet

            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail="Ese tweet no existe")


class BigBurger:
    def maxWait(self, arrival, service):
        start = 0
        maxW = 0
        for i in xrange(len(arrival)):
            a, s = arrival[i], service[i]
            if a < start:
                wait = start - a
            else:
                wait = 0
            maxW = max(maxW, wait)
            start = a + wait + s
        return maxW

print(BigBurger().maxWait([3,3,9], [2,15,14]))


# 걷기
import sys
input = sys.stdin.readline

if __name__ == "__main__":
    # x,y,w,s
    # x,y = 도착점 좌표
    # w: x 나 y 를 한칸 이동할때 걸리는시간
    # s: 대각이동 할때(x,y) 걸리는 시간
    x, y, w, s = map(int, input().split())
    gap = abs(x-y)  # 큰수에서 작은수를 뺀값
    diagnol = min(x, y)    # 대각 이동 가능한수
    move1 = (x+y)*w  # 평행이동만 하는경우

    if (x+y) % 2 == 0:  # 짝수일때
        move2 = max(x, y) * s
    else:
        move2 = (max(x, y)-1) * s+w
    move3 = min(x, y)*s+gap*w

    print(min(move1, move2, move3))