"""
Template for each `dtype` helper function for hashtable

WARNING: DO NOT edit .pxi FILE directly, .pxi is generated from .pxi.in
"""

{{py:

# dtype, ttype, c_type
dtypes = [('float64', 'float64', 'float64_t'),
          ('uint64', 'uint64', 'uint64_t'),
          ('object', 'pymap', 'object'),
          ('int64', 'int64', 'int64_t')]

}}

{{for dtype, ttype, c_type in dtypes}}


@cython.wraparound(False)
@cython.boundscheck(False)
{{if dtype == 'object'}}
cdef build_count_table_{{dtype}}(ndarray[{{dtype}}] values,
                                 kh_{{ttype}}_t *table, bint dropna):
{{else}}
cdef build_count_table_{{dtype}}({{dtype}}_t[:] values,
                                 kh_{{ttype}}_t *table, bint dropna):
{{endif}}
    cdef:
        khiter_t k
        Py_ssize_t i, n = len(values)

        {{c_type}} val

        int ret = 0

    {{if dtype == 'object'}}
    kh_resize_{{ttype}}(table, n // 10)

    for i in range(n):
        val = values[i]

        if not checknull(val) or not dropna:
            k = kh_get_{{ttype}}(table, <PyObject*>val)
            if k != table.n_buckets:
                table.vals[k] += 1
            else:
                k = kh_put_{{ttype}}(table, <PyObject*>val, &ret)
                table.vals[k] = 1
    {{else}}
    with nogil:
        kh_resize_{{ttype}}(table, n)

        for i in range(n):
            val = values[i]

            {{if dtype == 'float64'}}
            if val == val or not dropna:
            {{else}}
            if True:
            {{endif}}
                k = kh_get_{{ttype}}(table, val)
                if k != table.n_buckets:
                    table.vals[k] += 1
                else:
                    k = kh_put_{{ttype}}(table, val, &ret)
                    table.vals[k] = 1
    {{endif}}


@cython.wraparound(False)
@cython.boundscheck(False)
{{if dtype == 'object'}}
cpdef value_count_{{dtype}}(ndarray[{{dtype}}] values, bint dropna):
{{else}}
cpdef value_count_{{dtype}}({{c_type}}[:] values, bint dropna):
{{endif}}
    cdef:
        Py_ssize_t i = 0
        kh_{{ttype}}_t *table

        {{if dtype != 'object'}}
        {{dtype}}_t[:] result_keys
        int64_t[:] result_counts
        {{endif}}

        # Don't use Py_ssize_t, since table.n_buckets is unsigned
        khiter_t k

    table = kh_init_{{ttype}}()
    {{if dtype == 'object'}}
    build_count_table_{{dtype}}(values, table, 1)
    {{else}}
    build_count_table_{{dtype}}(values, table, dropna)
    {{endif}}

    result_keys = np.empty(table.n_occupied, '{{dtype}}')
    result_counts = np.zeros(table.n_occupied, dtype=np.int64)

    {{if dtype == 'object'}}
    for k in range(table.n_buckets):
        if kh_exist_{{ttype}}(table, k):
            result_keys[i] = <{{dtype}}>table.keys[k]
            result_counts[i] = table.vals[k]
            i += 1
    {{else}}
    with nogil:
        for k in range(table.n_buckets):
            if kh_exist_{{ttype}}(table, k):
                result_keys[i] = table.keys[k]
                result_counts[i] = table.vals[k]
                i += 1
    {{endif}}

    kh_destroy_{{ttype}}(table)

    {{if dtype == 'object'}}
    return result_keys, result_counts
    {{else}}
    return np.asarray(result_keys), np.asarray(result_counts)
    {{endif}}


@cython.wraparound(False)
@cython.boundscheck(False)
{{if dtype == 'object'}}
def duplicated_{{dtype}}(ndarray[{{dtype}}] values, object keep='first'):
{{else}}
def duplicated_{{dtype}}(const {{c_type}}[:] values, object keep='first'):
{{endif}}
    cdef:
        int ret = 0
        {{if dtype != 'object'}}
        {{dtype}}_t value
        {{endif}}
        Py_ssize_t i, n = len(values)
        khiter_t k
        kh_{{ttype}}_t *table = kh_init_{{ttype}}()
        ndarray[uint8_t, ndim=1, cast=True] out = np.empty(n, dtype='bool')

    kh_resize_{{ttype}}(table, min(n, _SIZE_HINT_LIMIT))

    if keep not in ('last', 'first', False):
        raise ValueError('keep must be either "first", "last" or False')

    if keep == 'last':
        {{if dtype == 'object'}}
        for i in range(n - 1, -1, -1):
            # equivalent: range(n)[::-1], which cython doesn't like in nogil
            kh_put_{{ttype}}(table, <PyObject*>values[i], &ret)
            out[i] = ret == 0
        {{else}}
        with nogil:
            for i in range(n - 1, -1, -1):
                # equivalent: range(n)[::-1], which cython doesn't like in nogil
                kh_put_{{ttype}}(table, values[i], &ret)
                out[i] = ret == 0
        {{endif}}
    elif keep == 'first':
        {{if dtype == 'object'}}
        for i in range(n):
            kh_put_{{ttype}}(table, <PyObject*>values[i], &ret)
            out[i] = ret == 0
        {{else}}
        with nogil:
            for i in range(n):
                kh_put_{{ttype}}(table, values[i], &ret)
                out[i] = ret == 0
        {{endif}}
    else:
        {{if dtype == 'object'}}
        for i in range(n):
            value = values[i]
            k = kh_get_{{ttype}}(table, <PyObject*>value)
            if k != table.n_buckets:
                out[table.vals[k]] = 1
                out[i] = 1
            else:
                k = kh_put_{{ttype}}(table, <PyObject*>value, &ret)
                table.keys[k] = <PyObject*>value
                table.vals[k] = i
                out[i] = 0
        {{else}}
        with nogil:
            for i in range(n):
                value = values[i]
                k = kh_get_{{ttype}}(table, value)
                if k != table.n_buckets:
                    out[table.vals[k]] = 1
                    out[i] = 1
                else:
                    k = kh_put_{{ttype}}(table, value, &ret)
                    table.keys[k] = value
                    table.vals[k] = i
                    out[i] = 0
        {{endif}}
    kh_destroy_{{ttype}}(table)
    return out


# ----------------------------------------------------------------------
# Membership
# ----------------------------------------------------------------------


@cython.wraparound(False)
@cython.boundscheck(False)
{{if dtype == 'object'}}
def ismember_{{dtype}}(ndarray[{{c_type}}] arr, ndarray[{{c_type}}] values):
{{else}}
def ismember_{{dtype}}(const {{c_type}}[:] arr, const {{c_type}}[:] values):
{{endif}}
    """
    Return boolean of values in arr on an
    element by-element basis

    Parameters
    ----------
    arr : {{dtype}} ndarray
    values : {{dtype}} ndarray

    Returns
    -------
    boolean ndarry len of (arr)
    """
    cdef:
        Py_ssize_t i, n
        khiter_t k
        int ret = 0
        ndarray[uint8_t] result
        {{c_type}} val
        kh_{{ttype}}_t *table = kh_init_{{ttype}}()

    # construct the table
    n = len(values)
    kh_resize_{{ttype}}(table, n)

    {{if dtype == 'object'}}
    for i in range(n):
        kh_put_{{ttype}}(table, <PyObject*>values[i], &ret)
    {{else}}
    with nogil:
        for i in range(n):
            kh_put_{{ttype}}(table, values[i], &ret)
    {{endif}}

    # test membership
    n = len(arr)
    result = np.empty(n, dtype=np.uint8)

    {{if dtype == 'object'}}
    for i in range(n):
        val = arr[i]
        k = kh_get_{{ttype}}(table, <PyObject*>val)
        result[i] = (k != table.n_buckets)
    {{else}}
    with nogil:
        for i in range(n):
            val = arr[i]
            k = kh_get_{{ttype}}(table, val)
            result[i] = (k != table.n_buckets)
    {{endif}}

    kh_destroy_{{ttype}}(table)
    return result.view(np.bool_)

{{endfor}}


# ----------------------------------------------------------------------
# Mode Computations
# ----------------------------------------------------------------------

{{py:

# dtype, ctype, table_type, npy_dtype
dtypes = [('float64', 'float64_t', 'float64', 'float64'),
          ('int64', 'int64_t', 'int64', 'int64'),
          ('uint64', 'uint64_t', 'uint64', 'uint64'),
          ('object', 'object', 'pymap', 'object_')]
}}

{{for dtype, ctype, table_type, npy_dtype in dtypes}}


@cython.wraparound(False)
@cython.boundscheck(False)

{{if dtype == 'object'}}


def mode_{{dtype}}(ndarray[{{ctype}}] values, bint dropna):
{{else}}


def mode_{{dtype}}({{ctype}}[:] values, bint dropna):
{{endif}}
    cdef:
        int count, max_count = 1
        int j = -1  # so you can do +=
        # Don't use Py_ssize_t, since table.n_buckets is unsigned
        khiter_t k
        kh_{{table_type}}_t *table
        ndarray[{{ctype}}] modes

    table = kh_init_{{table_type}}()
    build_count_table_{{dtype}}(values, table, dropna)

    modes = np.empty(table.n_buckets, dtype=np.{{npy_dtype}})

    {{if dtype != 'object'}}
    with nogil:
        for k in range(table.n_buckets):
            if kh_exist_{{table_type}}(table, k):
                count = table.vals[k]
                if count == max_count:
                    j += 1
                elif count > max_count:
                    max_count = count
                    j = 0
                else:
                    continue

                modes[j] = table.keys[k]
    {{else}}
    for k in range(table.n_buckets):
        if kh_exist_{{table_type}}(table, k):
            count = table.vals[k]

            if count == max_count:
                j += 1
            elif count > max_count:
                max_count = count
                j = 0
            else:
                continue

            modes[j] = <object>table.keys[k]
    {{endif}}

    kh_destroy_{{table_type}}(table)

    return modes[:j + 1]

{{endfor}}
