"""
Implementation of method overloads for Generator objects.
"""

import numpy as np
from numba.core import types
from numba.core.extending import overload_method, register_jitable
from numba.np.numpy_support import as_dtype, from_dtype
from numba.np.random.generator_core import next_float, next_double
from numba.np.numpy_support import is_nonelike
from numba.core.errors import TypingError
from numba.core.types.containers import Tuple, UniTuple
from numba.np.random.distributions import \
    (random_standard_exponential_inv_f, random_standard_exponential_inv,
     random_standard_exponential, random_standard_normal_f,
     random_standard_gamma, random_standard_normal, random_uniform,
     random_standard_exponential_f, random_standard_gamma_f, random_normal,
     random_exponential, random_gamma, random_beta, random_power,
     random_f,random_chisquare,random_standard_cauchy,random_pareto,
     random_weibull, random_laplace, random_logistic,
     random_lognormal, random_rayleigh, random_standard_t, random_wald,
     random_geometric, random_zipf, random_triangular,
     random_poisson, random_negative_binomial, random_logseries,
     random_noncentral_chisquare, random_noncentral_f)
from numba.np.random import random_methods


def _get_proper_func(func_32, func_64, dtype, dist_name="the given"):
    """
        Most of the standard NumPy distributions that accept dtype argument
        only support either np.float32 or np.float64 as dtypes.

        This is a helper function that helps Numba select the proper underlying
        implementation according to provided dtype.
    """
    if isinstance(dtype, types.Omitted):
        dtype = dtype.value

    np_dt = dtype
    if isinstance(dtype, type):
        nb_dt = from_dtype(np.dtype(dtype))
    elif isinstance(dtype, types.NumberClass):
        nb_dt = dtype
        np_dt = as_dtype(nb_dt)

    if np_dt not in [np.float32, np.float64]:
        raise TypingError("Argument dtype is not one of the" +
                          " expected type(s): " +
                          " np.float32 or np.float64")

    if np_dt == np.float32:
        next_func = func_32
    else:
        next_func = func_64

    return next_func, nb_dt


def check_size(size):
    if not any([isinstance(size, UniTuple) and
                isinstance(size.dtype, types.Integer),
                isinstance(size, Tuple) and size.count == 0,
                isinstance(size, types.Integer)]):
        raise TypingError("Argument size is not one of the" +
                          " expected type(s): " +
                          " an integer, an empty tuple or a tuple of integers")


def check_types(obj, type_list, arg_name):
    """
    Check if given object is one of the provided types.
    If not raises an TypeError
    """
    if isinstance(obj, types.Omitted):
        obj = obj.value

    if not isinstance(type_list, (list, tuple)):
        type_list = [type_list]

    if not any([isinstance(obj, _type) for _type in type_list]):
        raise TypingError(f"Argument {arg_name} is not one of the" +
                          f" expected type(s): {type_list}")


# Overload the Generator().integers()
@overload_method(types.NumPyRandomGeneratorType, 'integers')
def NumPyRandomGeneratorType_integers(inst, low, high, size=None,
                                      dtype=np.int64, endpoint=False):
    check_types(low, [types.Integer,
                      types.Boolean, bool, int], 'low')
    check_types(high, [types.Integer, types.Boolean,
                       bool, int], 'high')
    check_types(endpoint, [types.Boolean, bool], 'endpoint')

    if isinstance(size, types.Omitted):
        size = size.value

    if isinstance(dtype, types.Omitted):
        dtype = dtype.value

    if isinstance(dtype, type):
        nb_dt = from_dtype(np.dtype(dtype))
        _dtype = dtype
    elif isinstance(dtype, types.NumberClass):
        nb_dt = dtype
        _dtype = as_dtype(nb_dt)
    else:
        raise TypingError("Argument dtype is not one of the" +
                          " expected type(s): " +
                          "np.int32, np.int64, np.int16, np.int8, "
                          "np.uint32, np.uint64, np.uint16, np.uint8, "
                          "np.bool_")

    if _dtype == np.bool_:
        int_func = random_methods.random_bounded_bool_fill
        lower_bound = -1
        upper_bound = 2
    else:
        try:
            i_info = np.iinfo(_dtype)
        except ValueError:
            raise TypingError("Argument dtype is not one of the" +
                              " expected type(s): " +
                              "np.int32, np.int64, np.int16, np.int8, "
                              "np.uint32, np.uint64, np.uint16, np.uint8, "
                              "np.bool_")
        int_func = getattr(random_methods,
                           f'random_bounded_uint{i_info.bits}_fill')
        lower_bound = i_info.min
        upper_bound = i_info.max

    if is_nonelike(size):
        def impl(inst, low, high, size=None,
                 dtype=np.int64, endpoint=False):
            random_methods._randint_arg_check(low, high, endpoint,
                                              lower_bound, upper_bound)
            if not endpoint:
                high -= dtype(1)
                low = dtype(low)
                high = dtype(high)
                rng = high - low
                return int_func(inst.bit_generator, low, rng, 1, dtype)[0]
            else:
                low = dtype(low)
                high = dtype(high)
                rng = high - low
                return int_func(inst.bit_generator, low, rng, 1, dtype)[0]
        return impl
    else:
        check_size(size)

        def impl(inst, low, high, size=None,
                 dtype=np.int64, endpoint=False):
            random_methods._randint_arg_check(low, high, endpoint,
                                              lower_bound, upper_bound)
            if not endpoint:
                high -= dtype(1)
                low = dtype(low)
                high = dtype(high)
                rng = high - low
                return int_func(inst.bit_generator, low, rng, size, dtype)
            else:
                low = dtype(low)
                high = dtype(high)
                rng = high - low
                return int_func(inst.bit_generator, low, rng, size, dtype)
        return impl


# The following `shuffle` implementation is a direct translation from:
# https://github.com/numpy/numpy/blob/95e3e7f445407e4f355b23d6a9991d8774f0eb0c/numpy/random/_generator.pyx#L4578

# Overload the Generator().shuffle()
@overload_method(types.NumPyRandomGeneratorType, 'shuffle')
def NumPyRandomGeneratorType_shuffle(inst, x, axis=0):
    check_types(x, [types.Array], 'x')
    check_types(axis, [int, types.Integer], 'axis')

    def impl(inst, x, axis=0):
        if axis < 0:
            axis = axis + x.ndim
        if axis > x.ndim - 1 or axis < 0:
            raise IndexError("Axis is out of bounds for the given array")

        z = np.swapaxes(x, 0, axis)
        buf = np.empty_like(z[0, ...])

        for i in range(len(z) - 1, 0, -1):
            j = types.intp(random_methods.random_interval(inst.bit_generator,
                                                          i))
            if i == j:
                continue
            buf[...] = z[j, ...]
            z[j, ...] = z[i, ...]
            z[i, ...] = buf

    return impl


# The following `permutation` implementation is a direct translation from:
# https://github.com/numpy/numpy/blob/95e3e7f445407e4f355b23d6a9991d8774f0eb0c/numpy/random/_generator.pyx#L4710
# Overload the Generator().permutation()
@overload_method(types.NumPyRandomGeneratorType, 'permutation')
def NumPyRandomGeneratorType_permutation(inst, x, axis=0):
    check_types(x, [types.Array, types.Integer], 'x')
    check_types(axis, [int, types.Integer], 'axis')

    IS_INT = isinstance(x, types.Integer)

    def impl(inst, x, axis=0):
        if IS_INT:
            new_arr = np.arange(x)
            # NumPy ignores the axis argument when x is an integer
            inst.shuffle(new_arr)
        else:
            new_arr = x.copy()
            inst.shuffle(new_arr, axis=axis)
        return new_arr

    return impl


# Overload the Generator().random()
@overload_method(types.NumPyRandomGeneratorType, 'random')
def NumPyRandomGeneratorType_random(inst, size=None, dtype=np.float64):
    dist_func, nb_dt = _get_proper_func(next_float, next_double,
                                        dtype, "random")
    if isinstance(size, types.Omitted):
        size = size.value

    if is_nonelike(size):
        def impl(inst, size=None, dtype=np.float64):
            return nb_dt(dist_func(inst.bit_generator))
        return impl
    else:
        check_size(size)

        def impl(inst, size=None, dtype=np.float64):
            out = np.empty(size, dtype=dtype)
            out_f = out.flat
            for i in range(out.size):
                out_f[i] = dist_func(inst.bit_generator)
            return out
        return impl


# Overload the Generator().standard_exponential() method
@overload_method(types.NumPyRandomGeneratorType, 'standard_exponential')
def NumPyRandomGeneratorType_standard_exponential(inst, size=None,
                                                  dtype=np.float64,
                                                  method='zig'):
    check_types(method, [types.UnicodeType, str], 'method')
    dist_func_inv, nb_dt = _get_proper_func(
        random_standard_exponential_inv_f,
        random_standard_exponential_inv,
        dtype
    )

    dist_func, nb_dt = _get_proper_func(random_standard_exponential_f,
                                        random_standard_exponential,
                                        dtype)

    if isinstance(size, types.Omitted):
        size = size.value

    if is_nonelike(size):
        def impl(inst, size=None, dtype=np.float64, method='zig'):
            if method == 'zig':
                return nb_dt(dist_func(inst.bit_generator))
            elif method == 'inv':
                return nb_dt(dist_func_inv(inst.bit_generator))
            else:
                raise ValueError("Method must be either 'zig' or 'inv'")
        return impl
    else:
        check_size(size)

        def impl(inst, size=None, dtype=np.float64, method='zig'):
            out = np.empty(size, dtype=dtype)
            out_f = out.flat
            if method == 'zig':
                for i in range(out.size):
                    out_f[i] = dist_func(inst.bit_generator)
            elif method == 'inv':
                for i in range(out.size):
                    out_f[i] = dist_func_inv(inst.bit_generator)
            else:
                raise ValueError("Method must be either 'zig' or 'inv'")
            return out
        return impl


# Overload the Generator().standard_normal() method
@overload_method(types.NumPyRandomGeneratorType, 'standard_normal')
def NumPyRandomGeneratorType_standard_normal(inst, size=None, dtype=np.float64):
    dist_func, nb_dt = _get_proper_func(random_standard_normal_f,
                                        random_standard_normal,
                                        dtype)
    if isinstance(size, types.Omitted):
        size = size.value

    if is_nonelike(size):
        def impl(inst, size=None, dtype=np.float64):
            return nb_dt(dist_func(inst.bit_generator))
        return impl
    else:
        check_size(size)

        def impl(inst, size=None, dtype=np.float64):
            out = np.empty(size, dtype=dtype)
            out_f = out.flat
            for i in range(out.size):
                out_f[i] = dist_func(inst.bit_generator)
            return out
        return impl


# Overload the Generator().standard_gamma() method
@overload_method(types.NumPyRandomGeneratorType, 'standard_gamma')
def NumPyRandomGeneratorType_standard_gamma(inst, shape, size=None,
                                            dtype=np.float64):
    check_types(shape, [types.Float, types.Integer, int, float], 'shape')
    dist_func, nb_dt = _get_proper_func(random_standard_gamma_f,
                                        random_standard_gamma,
                                        dtype)
    if isinstance(size, types.Omitted):
        size = size.value

    if is_nonelike(size):
        def impl(inst, shape, size=None, dtype=np.float64):
            return nb_dt(dist_func(inst.bit_generator, shape))
        return impl
    else:
        check_size(size)

        def impl(inst, shape, size=None, dtype=np.float64):
            out = np.empty(size, dtype=dtype)
            out_f = out.flat
            for i in range(out.size):
                out_f[i] = dist_func(inst.bit_generator, shape)
            return out
        return impl


# Overload the Generator().normal() method
@overload_method(types.NumPyRandomGeneratorType, 'normal')
def NumPyRandomGeneratorType_normal(inst, loc=0.0, scale=1.0,
                                    size=None):
    check_types(loc, [types.Float, types.Integer, int, float], 'loc')
    check_types(scale, [types.Float, types.Integer, int, float], 'scale')
    if isinstance(size, types.Omitted):
        size = size.value

    if is_nonelike(size):
        def impl(inst, loc=0.0, scale=1.0, size=None):
            return random_normal(inst.bit_generator, loc, scale)
        return impl
    else:
        check_size(size)

        def impl(inst, loc=0.0, scale=1.0, size=None):
            out = np.empty(size, dtype=np.float64)
            out_f = out.flat
            for i in range(out.size):
                out_f[i] = random_normal(inst.bit_generator, loc, scale)
            return out
        return impl


# Overload the Generator().uniform() method
@overload_method(types.NumPyRandomGeneratorType, 'uniform')
def NumPyRandomGeneratorType_uniform(inst, low=0.0, high=1.0,
                                     size=None):
    check_types(low, [types.Float, types.Integer, int, float], 'low')
    check_types(high, [types.Float, types.Integer, int, float], 'high')
    if isinstance(size, types.Omitted):
        size = size.value

    if is_nonelike(size):
        def impl(inst, low=0.0, high=1.0, size=None):
            return random_uniform(inst.bit_generator, low, high - low)
        return impl
    else:
        check_size(size)

        def impl(inst, low=0.0, high=1.0, size=None):
            out = np.empty(size, dtype=np.float64)
            out_f = out.flat
            for i in range(out.size):
                out_f[i] = random_uniform(inst.bit_generator, low, high - low)
            return out
        return impl


# Overload the Generator().exponential() method
@overload_method(types.NumPyRandomGeneratorType, 'exponential')
def NumPyRandomGeneratorType_exponential(inst, scale=1.0, size=None):
    check_types(scale, [types.Float, types.Integer, int, float], 'scale')
    if isinstance(size, types.Omitted):
        size = size.value

    if is_nonelike(size):
        def impl(inst, scale=1.0, size=None):
            return random_exponential(inst.bit_generator, scale)
        return impl
    else:
        check_size(size)

        def impl(inst, scale=1.0, size=None):
            out = np.empty(size, dtype=np.float64)
            out_f = out.flat
            for i in range(out.size):
                out_f[i] = random_exponential(inst.bit_generator, scale)
            return out
        return impl


# Overload the Generator().gamma() method
@overload_method(types.NumPyRandomGeneratorType, 'gamma')
def NumPyRandomGeneratorType_gamma(inst, shape, scale=1.0, size=None):
    check_types(shape, [types.Float, types.Integer, int, float], 'shape')
    check_types(scale, [types.Float, types.Integer, int, float], 'scale')
    if isinstance(size, types.Omitted):
        size = size.value

    if is_nonelike(size):
        def impl(inst, shape, scale=1.0, size=None):
            return random_gamma(inst.bit_generator, shape, scale)
        return impl
    else:
        check_size(size)

        def impl(inst, shape, scale=1.0, size=None):
            out = np.empty(size, dtype=np.float64)
            out_f = out.flat
            for i in range(out.size):
                out_f[i] = random_gamma(inst.bit_generator, shape, scale)
            return out
        return impl


# Overload the Generator().beta() method
@overload_method(types.NumPyRandomGeneratorType, 'beta')
def NumPyRandomGeneratorType_beta(inst, a, b, size=None):
    check_types(a, [types.Float, types.Integer, int, float], 'a')
    check_types(b, [types.Float, types.Integer, int, float], 'b')
    if isinstance(size, types.Omitted):
        size = size.value

    if is_nonelike(size):
        def impl(inst, a, b, size=None):
            return random_beta(inst.bit_generator, a, b)
        return impl
    else:
        check_size(size)

        def impl(inst, a, b, size=None):
            out = np.empty(size)
            out_f = out.flat
            for i in range(out.size):
                out_f[i] = random_beta(inst.bit_generator, a, b)
            return out
        return impl


# Overload the Generator().f() method
@overload_method(types.NumPyRandomGeneratorType, 'f')
def NumPyRandomGeneratorType_f(inst, dfnum, dfden, size=None):
    check_types(dfnum, [types.Float, types.Integer, int, float], 'dfnum')
    check_types(dfden, [types.Float, types.Integer, int, float], 'dfden')
    if isinstance(size, types.Omitted):
        size = size.value

    if is_nonelike(size):
        def impl(inst, dfnum, dfden, size=None):
            return random_f(inst.bit_generator, dfnum, dfden)
        return impl
    else:
        check_size(size)

        def impl(inst, dfnum, dfden, size=None):
            out = np.empty(size)
            out_f = out.flat
            for i in range(out.size):
                out_f[i] = random_f(inst.bit_generator, dfnum, dfden)
            return out
        return impl


# Overload the Generator().chisquare() method
@overload_method(types.NumPyRandomGeneratorType, 'chisquare')
def NumPyRandomGeneratorType_chisquare(inst, df, size=None):
    check_types(df, [types.Float, types.Integer, int, float], 'df')
    if isinstance(size, types.Omitted):
        size = size.value

    if is_nonelike(size):
        def impl(inst, df, size=None):
            return random_chisquare(inst.bit_generator, df)
        return impl
    else:
        check_size(size)

        def impl(inst, df, size=None):
            out = np.empty(size)
            out_f = out.flat
            for i in range(out.size):
                out_f[i] = random_chisquare(inst.bit_generator, df)
            return out
        return impl


@overload_method(types.NumPyRandomGeneratorType, 'standard_cauchy')
def NumPyRandomGeneratorType_standard_cauchy(inst, size=None):

    if isinstance(size, types.Omitted):
        size = size.value

    if is_nonelike(size):
        def impl(inst, size=None):
            return random_standard_cauchy(inst.bit_generator)
        return impl
    else:
        check_size(size)

        def impl(inst, size=None):
            out = np.empty(size)
            out_f = out.flat
            for i in range(out.size):
                out_f[i] = random_standard_cauchy(inst.bit_generator)
            return out
        return impl


@overload_method(types.NumPyRandomGeneratorType, 'pareto')
def NumPyRandomGeneratorType_pareto(inst, a, size=None):
    check_types(a, [types.Float, types.Integer, int, float], 'a')
    if isinstance(size, types.Omitted):
        size = size.value

    if is_nonelike(size):
        def impl(inst, a, size=None):
            return random_pareto(inst.bit_generator, a)
        return impl
    else:
        check_size(size)

        def impl(inst, a, size=None):
            out = np.empty(size)
            out_f = out.flat
            for i in range(out.size):
                out_f[i] = random_pareto(inst.bit_generator, a)
            return out
        return impl


@overload_method(types.NumPyRandomGeneratorType, 'weibull')
def NumPyRandomGeneratorType_weibull(inst, a, size=None):
    check_types(a, [types.Float, types.Integer, int, float], 'a')
    if isinstance(size, types.Omitted):
        size = size.value

    if is_nonelike(size):
        def impl(inst, a, size=None):
            return random_weibull(inst.bit_generator, a)
        return impl
    else:
        check_size(size)

        def impl(inst, a, size=None):
            out = np.empty(size)
            out_f = out.flat
            for i in range(out.size):
                out_f[i] = random_weibull(inst.bit_generator, a)
            return out
        return impl


@overload_method(types.NumPyRandomGeneratorType, 'power')
def NumPyRandomGeneratorType_power(inst, a, size=None):
    check_types(a, [types.Float, types.Integer, int, float], 'a')
    if isinstance(size, types.Omitted):
        size = size.value

    if is_nonelike(size):
        def impl(inst, a, size=None):
            return random_power(inst.bit_generator, a)
        return impl
    else:
        check_size(size)

        def impl(inst, a, size=None):
            out = np.empty(size)
            out_f = out.flat
            for i in range(out.size):
                out_f[i] = random_power(inst.bit_generator, a)
            return out
        return impl


@overload_method(types.NumPyRandomGeneratorType, 'laplace')
def NumPyRandomGeneratorType_laplace(inst, loc=0.0, scale=1.0, size=None):
    check_types(loc, [types.Float, types.Integer, int, float], 'loc')
    check_types(scale, [types.Float, types.Integer, int, float], 'scale')
    if isinstance(size, types.Omitted):
        size = size.value

    if is_nonelike(size):
        def impl(inst, loc=0.0, scale=1.0, size=None):
            return random_laplace(inst.bit_generator, loc, scale)
        return impl
    else:
        check_size(size)

        def impl(inst, loc=0.0, scale=1.0, size=None):
            out = np.empty(size)
            out_f = out.flat
            for i in range(out.size):
                out_f[i] = random_laplace(inst.bit_generator, loc, scale)
            return out
        return impl


@overload_method(types.NumPyRandomGeneratorType, 'logistic')
def NumPyRandomGeneratorType_logistic(inst, loc=0.0, scale=1.0, size=None):
    check_types(loc, [types.Float, types.Integer, int, float], 'loc')
    check_types(scale, [types.Float, types.Integer, int, float], 'scale')
    if isinstance(size, types.Omitted):
        size = size.value

    if is_nonelike(size):
        def impl(inst, loc=0.0, scale=1.0, size=None):
            return random_logistic(inst.bit_generator, loc, scale)
        return impl
    else:
        check_size(size)

        def impl(inst, loc=0.0, scale=1.0, size=None):
            out = np.empty(size)
            out_f = out.flat
            for i in range(out.size):
                out_f[i] = random_logistic(inst.bit_generator, loc, scale)
            return out
        return impl


@overload_method(types.NumPyRandomGeneratorType, 'lognormal')
def NumPyRandomGeneratorType_lognormal(inst, mean=0.0, sigma=1.0, size=None):
    check_types(mean, [types.Float, types.Integer, int, float], 'mean')
    check_types(sigma, [types.Float, types.Integer, int, float], 'sigma')
    if isinstance(size, types.Omitted):
        size = size.value

    if is_nonelike(size):
        def impl(inst, mean=0.0, sigma=1.0, size=None):
            return random_lognormal(inst.bit_generator, mean, sigma)
        return impl
    else:
        check_size(size)

        def impl(inst, mean=0.0, sigma=1.0, size=None):
            out = np.empty(size)
            out_f = out.flat
            for i in range(out.size):
                out_f[i] = random_lognormal(inst.bit_generator, mean, sigma)
            return out
        return impl


@overload_method(types.NumPyRandomGeneratorType, 'rayleigh')
def NumPyRandomGeneratorType_rayleigh(inst, scale=1.0, size=None):
    check_types(scale, [types.Float, types.Integer, int, float], 'scale')
    if isinstance(size, types.Omitted):
        size = size.value

    if is_nonelike(size):
        def impl(inst, scale=1.0, size=None):
            return random_rayleigh(inst.bit_generator, scale)
        return impl
    else:
        check_size(size)

        def impl(inst, scale=1.0, size=None):
            out = np.empty(size)
            out_f = out.flat
            for i in range(out.size):
                out_f[i] = random_rayleigh(inst.bit_generator, scale)
            return out
        return impl


@overload_method(types.NumPyRandomGeneratorType, 'standard_t')
def NumPyRandomGeneratorType_standard_t(inst, df, size=None):
    check_types(df, [types.Float, types.Integer, int, float], 'df')
    if isinstance(size, types.Omitted):
        size = size.value

    if is_nonelike(size):
        def impl(inst, df, size=None):
            return random_standard_t(inst.bit_generator, df)
        return impl
    else:
        check_size(size)

        def impl(inst, df, size=None):
            out = np.empty(size)
            out_f = out.flat
            for i in range(out.size):
                out_f[i] = random_standard_t(inst.bit_generator, df)
            return out
        return impl


@overload_method(types.NumPyRandomGeneratorType, 'wald')
def NumPyRandomGeneratorType_wald(inst, mean, scale, size=None):
    check_types(mean, [types.Float, types.Integer, int, float], 'mean')
    check_types(scale, [types.Float, types.Integer, int, float], 'scale')
    if isinstance(size, types.Omitted):
        size = size.value

    if is_nonelike(size):
        def impl(inst, mean, scale, size=None):
            return random_wald(inst.bit_generator, mean, scale)
        return impl
    else:
        check_size(size)

        def impl(inst, mean, scale, size=None):
            out = np.empty(size)
            out_f = out.flat
            for i in range(out.size):
                out_f[i] = random_wald(inst.bit_generator, mean, scale)
            return out
        return impl


@overload_method(types.NumPyRandomGeneratorType, 'geometric')
def NumPyRandomGeneratorType_geometric(inst, p, size=None):
    check_types(p, [types.Float, types.Integer, int, float], 'p')
    if isinstance(size, types.Omitted):
        size = size.value

    if is_nonelike(size):
        def impl(inst, p, size=None):
            return np.int64(random_geometric(inst.bit_generator, p))
        return impl
    else:
        check_size(size)

        def impl(inst, p, size=None):
            out = np.empty(size, dtype=np.int64)
            out_f = out.flat
            for i in range(out.size):
                out_f[i] = random_geometric(inst.bit_generator, p)
            return out
        return impl


@overload_method(types.NumPyRandomGeneratorType, 'zipf')
def NumPyRandomGeneratorType_zipf(inst, a, size=None):
    check_types(a, [types.Float, types.Integer, int, float], 'a')
    if isinstance(size, types.Omitted):
        size = size.value

    if is_nonelike(size):
        def impl(inst, a, size=None):
            return np.int64(random_zipf(inst.bit_generator, a))
        return impl
    else:
        check_size(size)

        def impl(inst, a, size=None):
            out = np.empty(size, dtype=np.int64)
            out_f = out.flat
            for i in range(out.size):
                out_f[i] = random_zipf(inst.bit_generator, a)
            return out
        return impl


@overload_method(types.NumPyRandomGeneratorType, 'triangular')
def NumPyRandomGeneratorType_triangular(inst, left, mode, right, size=None):
    check_types(left, [types.Float, types.Integer, int, float], 'left')
    check_types(mode, [types.Float, types.Integer, int, float], 'mode')
    check_types(right, [types.Float, types.Integer, int, float], 'right')
    if isinstance(size, types.Omitted):
        size = size.value

    if is_nonelike(size):
        def impl(inst, left, mode, right, size=None):
            return random_triangular(inst.bit_generator, left, mode, right)
        return impl
    else:
        check_size(size)

        def impl(inst, left, mode, right, size=None):
            out = np.empty(size)
            out_f = out.flat
            for i in range(out.size):
                out_f[i] = random_triangular(inst.bit_generator,
                                             left, mode, right)
            return out
        return impl


@overload_method(types.NumPyRandomGeneratorType, 'poisson')
def NumPyRandomGeneratorType_poisson(inst, lam , size=None):
    check_types(lam, [types.Float, types.Integer, int, float], 'lam')
    if isinstance(size, types.Omitted):
        size = size.value

    if is_nonelike(size):
        def impl(inst, lam , size=None):
            return np.int64(random_poisson(inst.bit_generator, lam))
        return impl
    else:
        check_size(size)

        def impl(inst, lam , size=None):
            out = np.empty(size, dtype=np.int64)
            out_f = out.flat
            for i in range(out.size):
                out_f[i] = random_poisson(inst.bit_generator, lam)
            return out
        return impl


@overload_method(types.NumPyRandomGeneratorType, 'negative_binomial')
def NumPyRandomGeneratorType_negative_binomial(inst, n, p, size=None):
    check_types(n, [types.Float, types.Integer, int, float], 'n')
    check_types(p, [types.Float, types.Integer, int, float], 'p')
    if isinstance(size, types.Omitted):
        size = size.value

    if is_nonelike(size):
        def impl(inst,  n, p , size=None):
            return np.int64(random_negative_binomial(inst.bit_generator, n, p))
        return impl
    else:
        check_size(size)

        def impl(inst, n, p , size=None):
            out = np.empty(size, dtype=np.int64)
            out_f = out.flat
            for i in range(out.size):
                out_f[i] = random_negative_binomial(inst.bit_generator, n, p)
            return out
        return impl


@overload_method(types.NumPyRandomGeneratorType, 'noncentral_chisquare')
def NumPyRandomGeneratorType_noncentral_chisquare(inst, df, nonc, size=None):
    check_types(df, [types.Float, types.Integer, int, float], 'df')
    check_types(nonc, [types.Float, types.Integer, int, float], 'nonc')
    if isinstance(size, types.Omitted):
        size = size.value

    @register_jitable
    def check_arg_bounds(df, nonc):
        if df <= 0:
            raise ValueError("df <= 0")
        if nonc < 0:
            raise ValueError("nonc < 0")

    if is_nonelike(size):
        def impl(inst, df, nonc, size=None):
            check_arg_bounds(df, nonc)
            return np.float64(random_noncentral_chisquare(inst.bit_generator,
                                                          df, nonc))
        return impl
    else:
        check_size(size)

        def impl(inst, df, nonc, size=None):
            check_arg_bounds(df, nonc)
            out = np.empty(size, dtype=np.float64)
            out_f = out.flat
            for i in range(out.size):
                out_f[i] = random_noncentral_chisquare(inst.bit_generator,
                                                       df, nonc)
            return out
        return impl


@overload_method(types.NumPyRandomGeneratorType, 'noncentral_f')
def NumPyRandomGeneratorType_noncentral_f(inst, dfnum, dfden, nonc, size=None):
    check_types(dfnum, [types.Float, types.Integer, int, float], 'dfnum')
    check_types(dfden, [types.Float, types.Integer, int, float], 'dfden')
    check_types(nonc, [types.Float, types.Integer, int, float], 'nonc')
    if isinstance(size, types.Omitted):
        size = size.value

    @register_jitable
    def check_arg_bounds(dfnum, dfden, nonc):
        if dfnum <= 0:
            raise ValueError("dfnum <= 0")
        if dfden <= 0:
            raise ValueError("dfden <= 0")
        if nonc < 0:
            raise ValueError("nonc < 0")

    if is_nonelike(size):
        def impl(inst,  dfnum, dfden, nonc, size=None):
            check_arg_bounds(dfnum, dfden, nonc)
            return np.float64(random_noncentral_f(inst.bit_generator,
                                                  dfnum, dfden, nonc))
        return impl
    else:
        check_size(size)

        def impl(inst, dfnum, dfden, nonc, size=None):
            check_arg_bounds(dfnum, dfden, nonc)
            out = np.empty(size, dtype=np.float64)
            out_f = out.flat
            for i in range(out.size):
                out_f[i] = random_noncentral_f(inst.bit_generator,
                                               dfnum, dfden, nonc)
            return out
        return impl


@overload_method(types.NumPyRandomGeneratorType, 'logseries')
def NumPyRandomGeneratorType_logseries(inst, p, size=None):
    check_types(p, [types.Float, types.Integer, int, float], 'p')
    if isinstance(size, types.Omitted):
        size = size.value

    @register_jitable
    def check_arg_bounds(p):
        if p < 0 or p >= 1 or np.isnan(p):
            raise ValueError("p < 0, p >= 1 or p is NaN")

    if is_nonelike(size):
        def impl(inst, p, size=None):
            check_arg_bounds(p)
            return np.int64(random_logseries(inst.bit_generator, p))
        return impl
    else:
        check_size(size)

        def impl(inst, p, size=None):
            check_arg_bounds(p)
            out = np.empty(size, dtype=np.int64)
            out_f = out.flat
            for i in range(out.size):
                out_f[i] = random_logseries(inst.bit_generator, p)
            return out
        return impl
