# cython: language_level=3
# distutils: language = c++
# -*- coding: utf-8 -*-
# *****************************************************************************
# Copyright (c) 2016-2020, Intel Corporation
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# - Redistributions of source code must retain the above copyright notice,
#   this list of conditions and the following disclaimer.
# - Redistributions in binary form must reproduce the above copyright notice,
#   this list of conditions and the following disclaimer in the documentation
#   and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
# THE POSSIBILITY OF SUCH DAMAGE.
# *****************************************************************************

"""
Interface of the statistics function of the DPNP

Notes
-----
This module is a face or public interface file for the library
it contains:
 - Interface functions
 - documentation for the functions
 - The functions parameters check

"""


import numpy

from dpnp.dpnp_algo import *
from dpnp.dpnp_utils import *
import dpnp


__all__ = [
    'amax',
    'amin',
    'average',
    'bincount',
    'correlate',
    'cov',
    'histogram',
    'max',
    'mean',
    'median',
    'min',
    'nanvar',
    'std',
    'var',
]


def amax(input, axis=None, out=None):
    """
    Return the maximum of an array or maximum along an axis.

    For full documentation refer to :obj:`numpy.amax`.

    See Also
    --------
    :obj:`dpnp.amin` : The minimum value of an array along a given axis,
                       propagating any NaNs.
    :obj:`dpnp.nanmax` : The maximum value of an array along a given axis,
                         ignoring any NaNs.
    :obj:`dpnp.maximum` : Element-wise maximum of two arrays,
                          propagating any NaNs.
    :obj:`dpnp.fmax` : Element-wise maximum of two arrays, ignoring any NaNs.
    :obj:`dpnp.argmax` : Return the indices of the maximum values.
    :obj:`dpnp.nanmin` : Return minimum of an array or minimum along an axis,
                         ignoring any NaNs.
    :obj:`dpnp.minimum` : Element-wise minimum of array elements.
    :obj:`dpnp.fmin` : Element-wise minimum of array elements.

    Notes
    -----
    This function works exactly the same as :obj:`dpnp.max`.

    """
    return max(input, axis=axis, out=out)


def amin(input, axis=None, out=None):
    """
    Return the minimum of an array or minimum along an axis.

    For full documentation refer to :obj:`numpy.amin`.

    See Also
    --------
    :obj:`dpnp.amax` : The maximum value of an array along a given axis,
                       propagating any NaNs.
    :obj:`dpnp.nanmin` : Return minimum of an array or minimum along an axis,
                         ignoring any NaNs.
    :obj:`dpnp.minimum` : Element-wise minimum of array elements.
    :obj:`dpnp.fmin` : Element-wise minimum of array elements.
    :obj:`dpnp.argmin` : Return the indices of the minimum values.
    :obj:`dpnp.nanmax` : The maximum value of an array along a given axis,
                         ignoring any NaNs.
    :obj:`dpnp.maximum` : Element-wise maximum of two arrays,
                          propagating any NaNs.
    :obj:`dpnp.fmax` : Element-wise maximum of two arrays, ignoring any NaNs.

    Notes
    -----
    This function works exactly the same as :obj:`dpnp.min`.

    """

    return min(input, axis=axis, out=out)


def average(x1, axis=None, weights=None, returned=False):
    """
    Compute the weighted average along the specified axis.

    For full documentation refer to :obj:`numpy.average`.

    Limitations
    -----------
    Input array is supported as :obj:`dpnp.ndarray`.
    Prameters ``axis`` is supported only with default value ``None``.
    Prameters ``weights`` is supported only with default value ``None``.
    Prameters ``returned`` is supported only with default value ``False``.
    Otherwise the function will be executed sequentially on CPU.
    Input array data types are limited by supported DPNP :ref:`Data types`.

    See Also
    --------
    :obj:`dpnp.mean` : Compute the arithmetic mean along the specified axis.

    Examples
    --------
    >>> import dpnp as np
    >>> data = np.arange(1, 5)
    >>> [i for i in data]
    [1, 2, 3, 4]
    >>> np.average(data)
    2.5

    """

    x1_desc = dpnp.get_dpnp_descriptor(x1)
    if x1_desc:
        if axis is not None:
            pass
        elif weights is not None:
            pass
        elif returned:
            pass
        else:
            result_obj = dpnp_average(x1_desc)
            result = dpnp.convert_single_elem_array_to_scalar(result_obj)

            return result

    return call_origin(numpy.average, x1, axis, weights, returned)


def bincount(x1, weights=None, minlength=0):
    """
    Count number of occurrences of each value in array of non-negative ints.

    For full documentation refer to :obj:`numpy.bincount`.

    See Also
    --------
    :obj:`dpnp.unique` : Find the unique elements of an array.

    Examples
    --------
    >>> import dpnp as np
    >>> res = np.bincount(np.arange(5))
    >>> print(res)
    [1, 1, 1, 1, 1]

    """

    return call_origin(numpy.bincount, x1, weights=weights, minlength=minlength)


def correlate(x1, x2, mode='valid'):
    """
    Cross-correlation of two 1-dimensional sequences.

    For full documentation refer to :obj:`numpy.correlate`.

    Limitations
    -----------
    Input arrays are supported as :obj:`dpnp.ndarray`.
    Size and shape of input arrays are supported to be equal.
    Prameters ``mode`` is supported only with default value ``"valid``.
    Otherwise the function will be executed sequentially on CPU.
    Input array data types are limited by supported DPNP :ref:`Data types`.

    See Also
    --------
    :obj:`dpnp.convolve` : Discrete, linear convolution of
                           two one-dimensional sequences.

    Examples
    --------
    >>> import dpnp as np
    >>> x = np.correlate([1, 2, 3], [0, 1, 0.5])
    >>> [i for i in x]
    [3.5]

    """

    x1_desc = dpnp.get_dpnp_descriptor(x1)
    x2_desc = dpnp.get_dpnp_descriptor(x2)
    if x1_desc and x2_desc:
        if x1_desc.size != x2_desc.size or x1_desc.size == 0:
            pass
        elif x1_desc.shape != x2_desc.shape:
            pass
        elif mode != 'valid':
            pass
        else:
            return dpnp_correlate(x1_desc, x2_desc).get_pyobj()

    return call_origin(numpy.correlate, x1, x2, mode=mode)


def cov(x1, y=None, rowvar=True, bias=False, ddof=None, fweights=None, aweights=None):
    """
    Estimate a covariance matrix, given data and weights.

    For full documentation refer to :obj:`numpy.cov`.

    Limitations
    -----------
    Input array ``m`` is supported as :obj:`dpnp.ndarray`.
    Dimension of input array ``m`` is limited by ``m.ndim > 2``.
    Size and shape of input arrays are supported to be equal.
    Prameters ``y`` is supported only with default value ``None``.
    Prameters ``rowvar`` is supported only with default value ``True``.
    Prameters ``bias`` is supported only with default value ``False``.
    Prameters ``ddof`` is supported only with default value ``None``.
    Prameters ``fweights`` is supported only with default value ``None``.
    Prameters ``aweights`` is supported only with default value ``None``.
    Otherwise the function will be executed sequentially on CPU.
    Input array data types are limited by supported DPNP :ref:`Data types`.

    .. seealso:: :obj:`dpnp.corrcoef` normalized covariance matrix.

    Examples
    --------
    >>> import dpnp as np
    >>> x = np.array([[0, 2], [1, 1], [2, 0]]).T
    >>> x.shape
    (2, 3)
    >>> [i for i in x]
    [0, 1, 2, 2, 1, 0]
    >>> out = np.cov(x)
    >>> out.shape
    (2, 2)
    >>> [i for i in out]
    [1.0, -1.0, -1.0, 1.0]

    """

    x1_desc = dpnp.get_dpnp_descriptor(x1)
    if x1_desc:
        if x1_desc.ndim > 2:
            pass
        elif y is not None:
            pass
        elif not rowvar:
            pass
        elif bias:
            pass
        elif ddof is not None:
            pass
        elif fweights is not None:
            pass
        elif aweights is not None:
            pass
        else:
            if x1_desc.dtype != dpnp.float64:
                x1_desc = dpnp.get_dpnp_descriptor(dpnp.astype(x1, dpnp.float64))

            return dpnp_cov(x1_desc).get_pyobj()

    return call_origin(numpy.cov, x1, y, rowvar, bias, ddof, fweights, aweights)


def histogram(a, bins=10, range=None, normed=None, weights=None, density=None):
    """
    Compute the histogram of a dataset.
    For full documentation refer to :obj:`numpy.histogram`.
    Examples
    --------
    >>> import dpnp
    >>> dpnp.histogram([1, 2, 1], bins=[0, 1, 2, 3])
    (array([0, 2, 1]), array([0, 1, 2, 3]))
    >>> dpnp.histogram(dpnp.arange(4), bins=dpnp.arange(5), density=True)
    (array([0.25, 0.25, 0.25, 0.25]), array([0, 1, 2, 3, 4]))
    >>> dpnp.histogram([[1, 2, 1], [1, 0, 1]], bins=[0,1,2,3])
    (array([1, 4, 1]), array([0, 1, 2, 3]))
    >>> a = dpnp.arange(5)
    >>> hist, bin_edges = dpnp.histogram(a, density=True)
    >>> hist
    array([0.5, 0. , 0.5, 0. , 0. , 0.5, 0. , 0.5, 0. , 0.5])
    >>> hist.sum()
    2.4999999999999996
    >>> res = dpnp.sum(hist * dpnp.diff(bin_edges))
    >>> print(res)
    1.0
    """

    return call_origin(numpy.histogram, a=a, bins=bins, range=range, normed=normed, weights=weights, density=density)


def max(x1, axis=None, out=None, keepdims=False, initial=None, where=True):
    """
    Return the maximum of an array or maximum along an axis.

    Limitations
    -----------
    Input array is supported as :obj:`dpnp.ndarray`.
    Otherwise the function will be executed sequentially on CPU.
    Prameters ``out`` is supported only with default value ``None``.
    Input array data types are limited by supported DPNP :ref:`Data types`.

    Examples
    --------
    >>> import dpnp as np
    >>> a = np.arange(4).reshape((2,2))
    >>> a.shape
    (2, 2)
    >>> [i for i in a]
    [0, 1, 2, 3]
    >>> np.max(a)
    3

    """

    x1_desc = dpnp.get_dpnp_descriptor(x1)
    if x1_desc:
        # Negative values in 'shape' are not allowed in input array
        # 306-322 check on negative and duplicate axis
        isaxis = True
        if axis is not None:
            if dpnp.isscalar(axis):
                if axis < 0:
                    isaxis = False
            else:
                for val in axis:
                    if val < 0:
                        isaxis = False
                        break
                if isaxis:
                    for i in range(len(axis)):
                        for j in range(len(axis)):
                            if i != j:
                                if axis[i] == axis[j]:
                                    isaxis = False
                                    break

        if not isaxis:
            pass
        elif out is not None:
            pass
        elif keepdims:
            pass
        elif initial is not None:
            pass
        elif where is not True:
            pass
        else:
            result_obj = dpnp_max(x1_desc, axis).get_pyobj()
            result = dpnp.convert_single_elem_array_to_scalar(result_obj)

            return result

    return call_origin(numpy.max, x1, axis, out, keepdims, initial, where)


def mean(x1, axis=None, **kwargs):
    """
    Compute the arithmetic mean along the specified axis.

    For full documentation refer to :obj:`numpy.mean`.

    Limitations
    -----------
    Input array is supported as :obj:`dpnp.ndarray`.
    Prameters ``axis`` is supported only with default value ``None``.
    Keyword arguments ``kwargs`` are currently unsupported.
    Size of input array is limited by ``x1.size > 0``.
    Otherwise the function will be executed sequentially on CPU.
    Input array data types are limited by supported DPNP :ref:`Data types`.

    See Also
    --------
    :obj:`dpnp.average` : Weighted average.
    :obj:`dpnp.std` : Compute the standard deviation along the specified axis.
    :obj:`dpnp.var` : Compute the variance along the specified axis.
    :obj:`dpnp.nanmean` : Compute the arithmetic mean along the specified axis,
                          ignoring NaNs.
    :obj:`dpnp.nanstd` : Compute the standard deviation along
                         the specified axis, while ignoring NaNs.
    :obj:`dpnp.nanvar` : Compute the variance along the specified axis,
                         while ignoring NaNs.

    Examples
    --------
    >>> import dpnp as np
    >>> a = np.array([[1, 2], [3, 4]])
    >>> np.mean(a)
    2.5

    """

    x1_desc = dpnp.get_dpnp_descriptor(x1)
    if x1_desc and not kwargs:
        if x1_desc.size == 0:
            pass
        elif axis is not None:
            pass
        else:
            result_obj = dpnp_mean(x1_desc, axis)
            result = dpnp.convert_single_elem_array_to_scalar(result_obj)

            return result

    return call_origin(numpy.mean, x1, axis=axis, **kwargs)


def median(x1, axis=None, out=None, overwrite_input=False, keepdims=False):
    """
    Compute the median along the specified axis.

    For full documentation refer to :obj:`numpy.median`.

    Limitations
    -----------
    Input array is supported as :obj:`dpnp.ndarray`.
    Prameters ``axis`` is supported only with default value ``None``.
    Prameters ``out`` is supported only with default value ``None``.
    Prameters ``overwrite_input`` is supported only with default value ``False``.
    Prameters ``keepdims`` is supported only with default value ``False``.
    Otherwise the function will be executed sequentially on CPU.
    Input array data types are limited by supported DPNP :ref:`Data types`.

    See Also
    --------
    :obj:`dpnp.mean` : Compute the arithmetic mean along the specified axis.
    :obj:`dpnp.percentile` : Compute the q-th percentile of the data
                             along the specified axis.

    Examples
    --------
    >>> import dpnp as np
    >>> a = np.array([[10, 7, 4], [3, 2, 1]])
    >>> np.median(a)
    3.5

    """

    x1_desc = dpnp.get_dpnp_descriptor(x1)
    if x1_desc:
        if axis is not None:
            pass
        elif out is not None:
            pass
        elif overwrite_input:
            pass
        elif keepdims:
            pass
        else:
            result_obj = dpnp_median(x1_desc).get_pyobj()
            result = dpnp.convert_single_elem_array_to_scalar(result_obj)

            return result

    return call_origin(numpy.median, x1, axis, out, overwrite_input, keepdims)


def min(x1, axis=None, out=None, keepdims=False, initial=None, where=True):
    """
    Return the minimum along a given axis.

    Limitations
    -----------
    Input array is supported as :obj:`dpnp.ndarray`.
    Otherwise the function will be executed sequentially on CPU.
    Prameters ``out`` is supported only with default value ``None``.
    Input array data types are limited by supported DPNP :ref:`Data types`.

    Examples
    --------
    >>> import dpnp as np
    >>> a = np.arange(4).reshape((2,2))
    >>> a.shape
    (2, 2)
    >>> [i for i in a]
    [0, 1, 2, 3]
    >>> np.min(a)
    0

    """

    x1_desc = dpnp.get_dpnp_descriptor(x1)
    if x1_desc:
        if out is not None:
            pass
        elif keepdims:
            pass
        elif initial is not None:
            pass
        elif where is not True:
            pass
        else:
            result_obj = dpnp_min(x1_desc, axis).get_pyobj()
            result = dpnp.convert_single_elem_array_to_scalar(result_obj)

            return result

    return call_origin(numpy.min, x1, axis, out, keepdims, initial, where)


def nanvar(x1, axis=None, dtype=None, out=None, ddof=0, keepdims=False):
    """
    Compute the variance along the specified axis, while ignoring NaNs.

    For full documentation refer to :obj:`numpy.nanvar`.

    Limitations
    -----------
    Input array is supported as :obj:`dpnp.ndarray`.
    Prameters ``axis`` is supported only with default value ``None``.
    Prameters ``dtype`` is supported only with default value ``None``.
    Prameters ``out`` is supported only with default value ``None``.
    Prameters ``keepdims`` is supported only with default value ``numpy._NoValue``.
    Otherwise the function will be executed sequentially on CPU.
    """

    x1_desc = dpnp.get_dpnp_descriptor(x1)
    if x1_desc:
        if x1.size == 0:
            pass
        elif axis is not None:
            pass
        elif dtype is not None:
            pass
        elif out is not None:
            pass
        elif keepdims:
            pass
        else:
            result_obj = dpnp_nanvar(x1_desc, ddof).get_pyobj()
            result = dpnp.convert_single_elem_array_to_scalar(result_obj)

            return result

    return call_origin(numpy.nanvar, x1, axis=axis, dtype=dtype, out=out, ddof=ddof, keepdims=keepdims)


def std(x1, axis=None, dtype=None, out=None, ddof=0, keepdims=False):
    """
    Compute the standard deviation along the specified axis.

    For full documentation refer to :obj:`numpy.std`.

    Limitations
    -----------
    Input array is supported as :obj:`dpnp.ndarray`.
    Size of input array is limited by ``a.size > 0``.
    Prameters ``axis`` is supported only with default value ``None``.
    Prameters ``dtype`` is supported only with default value ``None``.
    Prameters ``out`` is supported only with default value ``None``.
    Prameters ``keepdims`` is supported only with default value ``numpy._NoValue``.
    Otherwise the function will be executed sequentially on CPU.
    Input array data types are limited by supported DPNP :ref:`Data types`.

    See Also
    --------
    :obj:`dpnp.var` : Compute the variance along the specified axis.
    :obj:`dpnp.mean` : Compute the arithmetic mean along the specified axis.
    :obj:`dpnp.nanmean` : Compute the arithmetic mean along the specified axis,
                          ignoring NaNs.
    :obj:`dpnp.nanstd` : Compute the standard deviation along
                         the specified axis, while ignoring NaNs.
    :obj:`dpnp.nanvar` : Compute the variance along the specified axis,
                         while ignoring NaNs.

    Examples
    --------
    >>> import dpnp as np
    >>> a = np.array([[1, 2], [3, 4]])
    >>> np.std(a)
    1.118033988749895

    """

    x1_desc = dpnp.get_dpnp_descriptor(x1)
    if x1_desc:
        if x1_desc.size == 0:
            pass
        elif axis is not None:
            pass
        elif dtype is not None:
            pass
        elif out is not None:
            pass
        elif keepdims:
            pass
        else:
            result_obj = dpnp_std(x1_desc, ddof).get_pyobj()
            result = dpnp.convert_single_elem_array_to_scalar(result_obj)

            return result

    return call_origin(numpy.std, x1, axis, dtype, out, ddof, keepdims)


def var(x1, axis=None, dtype=None, out=None, ddof=0, keepdims=False):
    """
    Compute the variance along the specified axis.

    For full documentation refer to :obj:`numpy.var`.

    Limitations
    -----------
    Input array is supported as :obj:`dpnp.ndarray`.
    Size of input array is limited by ``a.size > 0``.
    Prameters ``axis`` is supported only with default value ``None``.
    Prameters ``dtype`` is supported only with default value ``None``.
    Prameters ``out`` is supported only with default value ``None``.
    Prameters ``keepdims`` is supported only with default value ``numpy._NoValue``.
    Otherwise the function will be executed sequentially on CPU.
    Input array data types are limited by supported DPNP :ref:`Data types`.

    See Also
    --------
    :obj:`dpnp.std` : Compute the standard deviation along the specified axis.
    :obj:`dpnp.mean` : Compute the arithmetic mean along the specified axis.
    :obj:`dpnp.nanmean` : Compute the arithmetic mean along the specified axis,
                          ignoring NaNs.
    :obj:`dpnp.nanstd` : Compute the standard deviation along
                         the specified axis, while ignoring NaNs.
    :obj:`dpnp.nanvar` : Compute the variance along the specified axis,
                         while ignoring NaNs.

    Examples
    --------
    >>> import dpnp as np
    >>> a = np.array([[1, 2], [3, 4]])
    >>> np.var(a)
    1.25

    """

    x1_desc = dpnp.get_dpnp_descriptor(x1)
    if x1_desc:
        if x1_desc.size == 0:
            pass
        elif axis is not None:
            pass
        elif dtype is not None:
            pass
        elif out is not None:
            pass
        elif keepdims:
            pass
        else:
            result_obj = dpnp_var(x1_desc, ddof).get_pyobj()
            result = dpnp.convert_single_elem_array_to_scalar(result_obj)

            return result

    return call_origin(numpy.var, x1, axis, dtype, out, ddof, keepdims)
