import sys
import gc
from hypothesis import given
from hypothesis.extra import numpy as hynp
import pytest

import numpy as np
from numpy.testing import (
    assert_, assert_equal, assert_raises, assert_warns, HAS_REFCOUNT,
    assert_raises_regex,
    )
from numpy.core.arrayprint import _typelessdata
import textwrap

class TestArrayRepr:
    def test_nan_inf(self):
        x = np.array([np.nan, np.inf])
        assert_equal(repr(x), 'array([nan, inf])')

    def test_subclass(self):
        class sub(np.ndarray): pass

        # one dimensional
        x1d = np.array([1, 2]).view(sub)
        assert_equal(repr(x1d), 'sub([1, 2])')

        # two dimensional
        x2d = np.array([[1, 2], [3, 4]]).view(sub)
        assert_equal(repr(x2d),
            'sub([[1, 2],\n'
            '     [3, 4]])')

        # two dimensional with flexible dtype
        xstruct = np.ones((2,2), dtype=[('a', ' 1)
        y = sub(None)
        x[()] = y
        y[()] = x
        assert_equal(repr(x),
            'sub(sub(sub(..., dtype=object), dtype=object), dtype=object)')
        assert_equal(str(x), '...')
        x[()] = 0  # resolve circular references for garbage collector

        # nested 0d-subclass-object
        x = sub(None)
        x[()] = sub(None)
        assert_equal(repr(x), 'sub(sub(None, dtype=object), dtype=object)')
        assert_equal(str(x), 'None')

        # gh-10663
        class DuckCounter(np.ndarray):
            def __getitem__(self, item):
                result = super().__getitem__(item)
                if not isinstance(result, DuckCounter):
                    result = result[...].view(DuckCounter)
                return result

            def to_string(self):
                return {0: 'zero', 1: 'one', 2: 'two'}.get(self.item(), 'many')

            def __str__(self):
                if self.shape == ():
                    return self.to_string()
                else:
                    fmt = {'all': lambda x: x.to_string()}
                    return np.array2string(self, formatter=fmt)

        dc = np.arange(5).view(DuckCounter)
        assert_equal(str(dc), "[zero one two many many]")
        assert_equal(str(dc[0]), "zero")

    def test_self_containing(self):
        arr0d = np.array(None)
        arr0d[()] = arr0d
        assert_equal(repr(arr0d),
            'array(array(..., dtype=object), dtype=object)')
        arr0d[()] = 0  # resolve recursion for garbage collector

        arr1d = np.array([None, None])
        arr1d[1] = arr1d
        assert_equal(repr(arr1d),
            'array([None, array(..., dtype=object)], dtype=object)')
        arr1d[1] = 0  # resolve recursion for garbage collector

        first = np.array(None)
        second = np.array(None)
        first[()] = second
        second[()] = first
        assert_equal(repr(first),
            'array(array(array(..., dtype=object), dtype=object), dtype=object)')
        first[()] = 0  # resolve circular references for garbage collector

    def test_containing_list(self):
        # printing square brackets directly would be ambiguuous
        arr1d = np.array([None, None])
        arr1d[0] = [1, 2]
        arr1d[1] = [3]
        assert_equal(repr(arr1d),
            'array([list([1, 2]), list([3])], dtype=object)')

    def test_void_scalar_recursion(self):
        # gh-9345
        repr(np.void(b'test'))  # RecursionError ?

    def test_fieldless_structured(self):
        # gh-10366
        no_fields = np.dtype([])
        arr_no_fields = np.empty(4, dtype=no_fields)
        assert_equal(repr(arr_no_fields), 'array([(), (), (), ()], dtype=[])')


class TestComplexArray:
    def test_str(self):
        rvals = [0, 1, -1, np.inf, -np.inf, np.nan]
        cvals = [complex(rp, ip) for rp in rvals for ip in rvals]
        dtypes = [np.complex64, np.cdouble, np.clongdouble]
        actual = [str(np.array([c], dt)) for c in cvals for dt in dtypes]
        wanted = [
            '[0.+0.j]',    '[0.+0.j]',    '[0.+0.j]',
            '[0.+1.j]',    '[0.+1.j]',    '[0.+1.j]',
            '[0.-1.j]',    '[0.-1.j]',    '[0.-1.j]',
            '[0.+infj]',   '[0.+infj]',   '[0.+infj]',
            '[0.-infj]',   '[0.-infj]',   '[0.-infj]',
            '[0.+nanj]',   '[0.+nanj]',   '[0.+nanj]',
            '[1.+0.j]',    '[1.+0.j]',    '[1.+0.j]',
            '[1.+1.j]',    '[1.+1.j]',    '[1.+1.j]',
            '[1.-1.j]',    '[1.-1.j]',    '[1.-1.j]',
            '[1.+infj]',   '[1.+infj]',   '[1.+infj]',
            '[1.-infj]',   '[1.-infj]',   '[1.-infj]',
            '[1.+nanj]',   '[1.+nanj]',   '[1.+nanj]',
            '[-1.+0.j]',   '[-1.+0.j]',   '[-1.+0.j]',
            '[-1.+1.j]',   '[-1.+1.j]',   '[-1.+1.j]',
            '[-1.-1.j]',   '[-1.-1.j]',   '[-1.-1.j]',
            '[-1.+infj]',  '[-1.+infj]',  '[-1.+infj]',
            '[-1.-infj]',  '[-1.-infj]',  '[-1.-infj]',
            '[-1.+nanj]',  '[-1.+nanj]',  '[-1.+nanj]',
            '[inf+0.j]',   '[inf+0.j]',   '[inf+0.j]',
            '[inf+1.j]',   '[inf+1.j]',   '[inf+1.j]',
            '[inf-1.j]',   '[inf-1.j]',   '[inf-1.j]',
            '[inf+infj]',  '[inf+infj]',  '[inf+infj]',
            '[inf-infj]',  '[inf-infj]',  '[inf-infj]',
            '[inf+nanj]',  '[inf+nanj]',  '[inf+nanj]',
            '[-inf+0.j]',  '[-inf+0.j]',  '[-inf+0.j]',
            '[-inf+1.j]',  '[-inf+1.j]',  '[-inf+1.j]',
            '[-inf-1.j]',  '[-inf-1.j]',  '[-inf-1.j]',
            '[-inf+infj]', '[-inf+infj]', '[-inf+infj]',
            '[-inf-infj]', '[-inf-infj]', '[-inf-infj]',
            '[-inf+nanj]', '[-inf+nanj]', '[-inf+nanj]',
            '[nan+0.j]',   '[nan+0.j]',   '[nan+0.j]',
            '[nan+1.j]',   '[nan+1.j]',   '[nan+1.j]',
            '[nan-1.j]',   '[nan-1.j]',   '[nan-1.j]',
            '[nan+infj]',  '[nan+infj]',  '[nan+infj]',
            '[nan-infj]',  '[nan-infj]',  '[nan-infj]',
            '[nan+nanj]',  '[nan+nanj]',  '[nan+nanj]']

        for res, val in zip(actual, wanted):
            assert_equal(res, val)

class TestArray2String:
    def test_basic(self):
        """Basic test of array2string."""
        a = np.arange(3)
        assert_(np.array2string(a) == '[0 1 2]')
        assert_(np.array2string(a, max_line_width=4, legacy='1.13') == '[0 1\n 2]')
        assert_(np.array2string(a, max_line_width=4) == '[0\n 1\n 2]')

    def test_unexpected_kwarg(self):
        # ensure than an appropriate TypeError
        # is raised when array2string receives
        # an unexpected kwarg

        with assert_raises_regex(TypeError, 'nonsense'):
            np.array2string(np.array([1, 2, 3]),
                            nonsense=None)

    def test_format_function(self):
        """Test custom format function for each element in array."""
        def _format_function(x):
            if np.abs(x) < 1:
                return '.'
            elif np.abs(x) < 2:
                return 'o'
            else:
                return 'O'

        x = np.arange(3)
        x_hex = "[0x0 0x1 0x2]"
        x_oct = "[0o0 0o1 0o2]"
        assert_(np.array2string(x, formatter={'all':_format_function}) ==
                "[. o O]")
        assert_(np.array2string(x, formatter={'int_kind':_format_function}) ==
                "[. o O]")
        assert_(np.array2string(x, formatter={'all':lambda x: "%.4f" % x}) ==
                "[0.0000 1.0000 2.0000]")
        assert_equal(np.array2string(x, formatter={'int':lambda x: hex(x)}),
                x_hex)
        assert_equal(np.array2string(x, formatter={'int':lambda x: oct(x)}),
                x_oct)

        x = np.arange(3.)
        assert_(np.array2string(x, formatter={'float_kind':lambda x: "%.2f" % x}) ==
                "[0.00 1.00 2.00]")
        assert_(np.array2string(x, formatter={'float':lambda x: "%.2f" % x}) ==
                "[0.00 1.00 2.00]")

        s = np.array(['abc', 'def'])
        assert_(np.array2string(s, formatter={'numpystr':lambda s: s*2}) ==
                '[abcabc defdef]')

    def test_structure_format_mixed(self):
        dt = np.dtype([('name', np.str_, 16), ('grades', np.float64, (2,))])
        x = np.array([('Sarah', (8.0, 7.0)), ('John', (6.0, 7.0))], dtype=dt)
        assert_equal(np.array2string(x),
                "[('Sarah', [8., 7.]) ('John', [6., 7.])]")

        np.set_printoptions(legacy='1.13')
        try:
            # for issue #5692
            A = np.zeros(shape=10, dtype=[("A", "M8[s]")])
            A[5:].fill(np.datetime64('NaT'))
            assert_equal(
                np.array2string(A),
                textwrap.dedent("""\
                [('1970-01-01T00:00:00',) ('1970-01-01T00:00:00',) ('1970-01-01T00:00:00',)
                 ('1970-01-01T00:00:00',) ('1970-01-01T00:00:00',) ('NaT',) ('NaT',)
                 ('NaT',) ('NaT',) ('NaT',)]""")
            )
        finally:
            np.set_printoptions(legacy=False)

        # same again, but with non-legacy behavior
        assert_equal(
            np.array2string(A),
            textwrap.dedent("""\
            [('1970-01-01T00:00:00',) ('1970-01-01T00:00:00',)
             ('1970-01-01T00:00:00',) ('1970-01-01T00:00:00',)
             ('1970-01-01T00:00:00',) (                'NaT',)
             (                'NaT',) (                'NaT',)
             (                'NaT',) (                'NaT',)]""")
        )

        # and again, with timedeltas
        A = np.full(10, 123456, dtype=[("A", "m8[s]")])
        A[5:].fill(np.datetime64('NaT'))
        assert_equal(
            np.array2string(A),
            textwrap.dedent("""\
            [(123456,) (123456,) (123456,) (123456,) (123456,) ( 'NaT',) ( 'NaT',)
             ( 'NaT',) ( 'NaT',) ( 'NaT',)]""")
        )

    def test_structure_format_int(self):
        # See #8160
        struct_int = np.array([([1, -1],), ([123, 1],)], dtype=[('B', 'i4', 2)])
        assert_equal(np.array2string(struct_int),
                "[([  1,  -1],) ([123,   1],)]")
        struct_2dint = np.array([([[0, 1], [2, 3]],), ([[12, 0], [0, 0]],)],
                dtype=[('B', 'i4', (2, 2))])
        assert_equal(np.array2string(struct_2dint),
                "[([[ 0,  1], [ 2,  3]],) ([[12,  0], [ 0,  0]],)]")

    def test_structure_format_float(self):
        # See #8172
        array_scalar = np.array(
                (1., 2.1234567890123456789, 3.), dtype=('f8,f8,f8'))
        assert_equal(np.array2string(array_scalar), "(1., 2.12345679, 3.)")

    def test_unstructured_void_repr(self):
        a = np.array([27, 91, 50, 75,  7, 65, 10,  8,
                      27, 91, 51, 49,109, 82,101,100], dtype='u1').view('V8')
        assert_equal(repr(a[0]), r"void(b'\x1B\x5B\x32\x4B\x07\x41\x0A\x08')")
        assert_equal(str(a[0]), r"b'\x1B\x5B\x32\x4B\x07\x41\x0A\x08'")
        assert_equal(repr(a),
            r"array([b'\x1B\x5B\x32\x4B\x07\x41\x0A\x08'," "\n"
            r"       b'\x1B\x5B\x33\x31\x6D\x52\x65\x64'], dtype='|V8')")

        assert_equal(eval(repr(a), vars(np)), a)
        assert_equal(eval(repr(a[0]), vars(np)), a[0])

    def test_edgeitems_kwarg(self):
        # previously the global print options would be taken over the kwarg
        arr = np.zeros(3, int)
        assert_equal(
            np.array2string(arr, edgeitems=1, threshold=0),
            "[0 ... 0]"
        )

    def test_summarize_1d(self):
        A = np.arange(1001)
        strA = '[   0    1    2 ...  998  999 1000]'
        assert_equal(str(A), strA)

        reprA = 'array([   0,    1,    2, ...,  998,  999, 1000])'
        assert_equal(repr(A), reprA)

    def test_summarize_2d(self):
        A = np.arange(1002).reshape(2, 501)
        strA = '[[   0    1    2 ...  498  499  500]\n' \
               ' [ 501  502  503 ...  999 1000 1001]]'
        assert_equal(str(A), strA)

        reprA = 'array([[   0,    1,    2, ...,  498,  499,  500],\n' \
                '       [ 501,  502,  503, ...,  999, 1000, 1001]])'
        assert_equal(repr(A), reprA)

    def test_summarize_structure(self):
        A = (np.arange(2002, dtype="i8", (2, 1001))])
        strB = "[([[1, 1, 1, ..., 1, 1, 1], [1, 1, 1, ..., 1, 1, 1]],)]"
        assert_equal(str(B), strB)

        reprB = (
            "array([([[1, 1, 1, ..., 1, 1, 1], [1, 1, 1, ..., 1, 1, 1]],)],\n"
            "      dtype=[('i', '>i8', (2, 1001))])"
        )
        assert_equal(repr(B), reprB)

        C = (np.arange(22, dtype=" 1:
            # if the type is >1 byte, the non-native endian version
            # must show endianness.
            assert non_native_repr != native_repr
            assert f"dtype='{non_native_dtype.byteorder}" in non_native_repr

    def test_linewidth_repr(self):
        a = np.full(7, fill_value=2)
        np.set_printoptions(linewidth=17)
        assert_equal(
            repr(a),
            textwrap.dedent("""\
            array([2, 2, 2,
                   2, 2, 2,
                   2])""")
        )
        np.set_printoptions(linewidth=17, legacy='1.13')
        assert_equal(
            repr(a),
            textwrap.dedent("""\
            array([2, 2, 2,
                   2, 2, 2, 2])""")
        )

        a = np.full(8, fill_value=2)

        np.set_printoptions(linewidth=18, legacy=False)
        assert_equal(
            repr(a),
            textwrap.dedent("""\
            array([2, 2, 2,
                   2, 2, 2,
                   2, 2])""")
        )

        np.set_printoptions(linewidth=18, legacy='1.13')
        assert_equal(
            repr(a),
            textwrap.dedent("""\
            array([2, 2, 2, 2,
                   2, 2, 2, 2])""")
        )

    def test_linewidth_str(self):
        a = np.full(18, fill_value=2)
        np.set_printoptions(linewidth=18)
        assert_equal(
            str(a),
            textwrap.dedent("""\
            [2 2 2 2 2 2 2 2
             2 2 2 2 2 2 2 2
             2 2]""")
        )
        np.set_printoptions(linewidth=18, legacy='1.13')
        assert_equal(
            str(a),
            textwrap.dedent("""\
            [2 2 2 2 2 2 2 2 2
             2 2 2 2 2 2 2 2 2]""")
        )

    def test_edgeitems(self):
        np.set_printoptions(edgeitems=1, threshold=1)
        a = np.arange(27).reshape((3, 3, 3))
        assert_equal(
            repr(a),
            textwrap.dedent("""\
            array([[[ 0, ...,  2],
                    ...,
                    [ 6, ...,  8]],

                   ...,

                   [[18, ..., 20],
                    ...,
                    [24, ..., 26]]])""")
        )

        b = np.zeros((3, 3, 1, 1))
        assert_equal(
            repr(b),
            textwrap.dedent("""\
            array([[[[0.]],

                    ...,

                    [[0.]]],


                   ...,


                   [[[0.]],

                    ...,

                    [[0.]]]])""")
        )

        # 1.13 had extra trailing spaces, and was missing newlines
        np.set_printoptions(legacy='1.13')

        assert_equal(
            repr(a),
            textwrap.dedent("""\
            array([[[ 0, ...,  2],
                    ..., 
                    [ 6, ...,  8]],

                   ..., 
                   [[18, ..., 20],
                    ..., 
                    [24, ..., 26]]])""")
        )

        assert_equal(
            repr(b),
            textwrap.dedent("""\
            array([[[[ 0.]],

                    ..., 
                    [[ 0.]]],


                   ..., 
                   [[[ 0.]],

                    ..., 
                    [[ 0.]]]])""")
        )

    def test_edgeitems_structured(self):
        np.set_printoptions(edgeitems=1, threshold=1)
        A = np.arange(5*2*3, dtype="