from tempfile import mkdtemp
import os
import io
import shutil
import threading
import textwrap

import numpy as np
from numpy import array, transpose, pi
from numpy.testing import (assert_equal, assert_allclose,
                           assert_array_equal, assert_array_almost_equal)
import pytest
from pytest import raises as assert_raises

import scipy.sparse
import scipy.io._mmio
import scipy.io._fast_matrix_market as fmm


parametrize_args = [('integer', 'int'),
                    ('unsigned-integer', 'uint')]

pytestmark = pytest.mark.thread_unsafe


# Run the entire test suite on both _mmio and _fast_matrix_market implementations
@pytest.fixture(scope='module', params=(scipy.io._mmio, fmm), autouse=True)
def implementations(request):
    global mminfo
    global mmread
    global mmwrite
    mminfo = request.param.mminfo
    mmread = request.param.mmread
    mmwrite = request.param.mmwrite


class TestMMIOArray:
    def setup_method(self):
        self.tmpdir = mkdtemp(suffix=str(threading.get_native_id()))
        self.fn = os.path.join(self.tmpdir, 'testfile.mtx')

    def teardown_method(self):
        shutil.rmtree(self.tmpdir)

    def check(self, a, info):
        mmwrite(self.fn, a)
        assert_equal(mminfo(self.fn), info)
        b = mmread(self.fn, spmatrix=False)
        assert_array_almost_equal(a, b)

    def check_exact(self, a, info):
        mmwrite(self.fn, a)
        assert_equal(mminfo(self.fn), info)
        b = mmread(self.fn, spmatrix=False)
        assert_equal(a, b)

    @pytest.mark.thread_unsafe
    @pytest.mark.parametrize('typeval, dtype', parametrize_args)
    def test_simple_integer(self, typeval, dtype):
        self.check_exact(array([[1, 2], [3, 4]], dtype=dtype),
                         (2, 2, 4, 'array', typeval, 'general'))

    @pytest.mark.thread_unsafe
    @pytest.mark.parametrize('typeval, dtype', parametrize_args)
    def test_32bit_integer(self, typeval, dtype):
        a = array([[2**31-1, 2**31-2], [2**31-3, 2**31-4]], dtype=dtype)
        self.check_exact(a, (2, 2, 4, 'array', typeval, 'general'))

    def test_64bit_integer(self):
        a = array([[2**31, 2**32], [2**63-2, 2**63-1]], dtype=np.int64)
        if (np.intp(0).itemsize < 8) and mmwrite == scipy.io._mmio.mmwrite:
            assert_raises(OverflowError, mmwrite, self.fn, a)
        else:
            self.check_exact(a, (2, 2, 4, 'array', 'integer', 'general'))

    def test_64bit_unsigned_integer(self):
        a = array([[2**31, 2**32], [2**64-2, 2**64-1]], dtype=np.uint64)
        self.check_exact(a, (2, 2, 4, 'array', 'unsigned-integer', 'general'))

    @pytest.mark.parametrize('typeval, dtype', parametrize_args)
    def test_simple_upper_triangle_integer(self, typeval, dtype):
        self.check_exact(array([[0, 1], [0, 0]], dtype=dtype),
                         (2, 2, 4, 'array', typeval, 'general'))

    @pytest.mark.parametrize('typeval, dtype', parametrize_args)
    def test_simple_lower_triangle_integer(self, typeval, dtype):
        self.check_exact(array([[0, 0], [1, 0]], dtype=dtype),
                         (2, 2, 4, 'array', typeval, 'general'))

    @pytest.mark.parametrize('typeval, dtype', parametrize_args)
    def test_simple_rectangular_integer(self, typeval, dtype):
        self.check_exact(array([[1, 2, 3], [4, 5, 6]], dtype=dtype),
                         (2, 3, 6, 'array', typeval, 'general'))

    def test_simple_rectangular_float(self):
        self.check([[1, 2], [3.5, 4], [5, 6]],
                   (3, 2, 6, 'array', 'real', 'general'))

    def test_simple_float(self):
        self.check([[1, 2], [3, 4.0]],
                   (2, 2, 4, 'array', 'real', 'general'))

    def test_simple_complex(self):
        self.check([[1, 2], [3, 4j]],
                   (2, 2, 4, 'array', 'complex', 'general'))

    @pytest.mark.parametrize('typeval, dtype', parametrize_args)
    def test_simple_symmetric_integer(self, typeval, dtype):
        self.check_exact(array([[1, 2], [2, 4]], dtype=dtype),
                         (2, 2, 4, 'array', typeval, 'symmetric'))

    def test_simple_skew_symmetric_integer(self):
        self.check_exact([[0, 2], [-2, 0]],
                         (2, 2, 4, 'array', 'integer', 'skew-symmetric'))

    def test_simple_skew_symmetric_float(self):
        self.check(array([[0, 2], [-2.0, 0.0]], 'f'),
                   (2, 2, 4, 'array', 'real', 'skew-symmetric'))

    def test_simple_hermitian_complex(self):
        self.check([[1, 2+3j], [2-3j, 4]],
                   (2, 2, 4, 'array', 'complex', 'hermitian'))

    def test_random_symmetric_float(self):
        sz = (20, 20)
        a = np.random.random(sz)
        a = a + transpose(a)
        self.check(a, (20, 20, 400, 'array', 'real', 'symmetric'))

    def test_random_rectangular_float(self):
        sz = (20, 15)
        a = np.random.random(sz)
        self.check(a, (20, 15, 300, 'array', 'real', 'general'))

    @pytest.mark.fail_slow(10)
    def test_bad_number_of_array_header_fields(self):
        s = """\
            %%MatrixMarket matrix array real general
              3  3 999
            1.0
            2.0
            3.0
            4.0
            5.0
            6.0
            7.0
            8.0
            9.0
            """
        text = textwrap.dedent(s).encode('ascii')
        with pytest.raises(ValueError, match='not of length 2'):
            scipy.io.mmread(io.BytesIO(text))

    def test_gh13634_non_skew_symmetric_int(self):
        self.check_exact(array([[1, 2], [-2, 99]], dtype=np.int32),
                         (2, 2, 4, 'array', 'integer', 'general'))

    def test_gh13634_non_skew_symmetric_float(self):
        self.check(array([[1, 2], [-2, 99.]], dtype=np.float32),
                   (2, 2, 4, 'array', 'real', 'general'))


class TestMMIOSparseCSR(TestMMIOArray):
    def setup_method(self):
        self.tmpdir = mkdtemp(suffix=str(threading.get_native_id()))
        self.fn = os.path.join(self.tmpdir, 'testfile.mtx')

    def teardown_method(self):
        shutil.rmtree(self.tmpdir)

    def check(self, a, info):
        mmwrite(self.fn, a)
        assert_equal(mminfo(self.fn), info)
        b = mmread(self.fn, spmatrix=False)
        assert_array_almost_equal(a.toarray(), b.toarray())

    def check_exact(self, a, info):
        mmwrite(self.fn, a)
        assert_equal(mminfo(self.fn), info)
        b = mmread(self.fn, spmatrix=False)
        assert_equal(a.toarray(), b.toarray())

    @pytest.mark.parametrize('typeval, dtype', parametrize_args)
    def test_simple_integer(self, typeval, dtype):
        self.check_exact(scipy.sparse.csr_array([[1, 2], [3, 4]], dtype=dtype),
                         (2, 2, 4, 'coordinate', typeval, 'general'))

    def test_32bit_integer(self):
        a = scipy.sparse.csr_array(array([[2**31-1, -2**31+2],
                                          [2**31-3, 2**31-4]],
                                         dtype=np.int32))
        self.check_exact(a, (2, 2, 4, 'coordinate', 'integer', 'general'))

    def test_64bit_integer(self):
        a = scipy.sparse.csr_array(array([[2**32+1, 2**32+1],
                                          [-2**63+2, 2**63-2]],
                                         dtype=np.int64))
        if (np.intp(0).itemsize < 8) and mmwrite == scipy.io._mmio.mmwrite:
            assert_raises(OverflowError, mmwrite, self.fn, a)
        else:
            self.check_exact(a, (2, 2, 4, 'coordinate', 'integer', 'general'))

    def test_32bit_unsigned_integer(self):
        a = scipy.sparse.csr_array(array([[2**31-1, 2**31-2],
                                          [2**31-3, 2**31-4]],
                                         dtype=np.uint32))
        self.check_exact(a, (2, 2, 4, 'coordinate', 'unsigned-integer', 'general'))

    def test_64bit_unsigned_integer(self):
        a = scipy.sparse.csr_array(array([[2**32+1, 2**32+1],
                                          [2**64-2, 2**64-1]],
                                         dtype=np.uint64))
        self.check_exact(a, (2, 2, 4, 'coordinate', 'unsigned-integer', 'general'))

    @pytest.mark.parametrize('typeval, dtype', parametrize_args)
    def test_simple_upper_triangle_integer(self, typeval, dtype):
        self.check_exact(scipy.sparse.csr_array([[0, 1], [0, 0]], dtype=dtype),
                         (2, 2, 1, 'coordinate', typeval, 'general'))

    @pytest.mark.parametrize('typeval, dtype', parametrize_args)
    def test_simple_lower_triangle_integer(self, typeval, dtype):
        self.check_exact(scipy.sparse.csr_array([[0, 0], [1, 0]], dtype=dtype),
                         (2, 2, 1, 'coordinate', typeval, 'general'))

    @pytest.mark.parametrize('typeval, dtype', parametrize_args)
    def test_simple_rectangular_integer(self, typeval, dtype):
        self.check_exact(scipy.sparse.csr_array([[1, 2, 3], [4, 5, 6]], dtype=dtype),
                         (2, 3, 6, 'coordinate', typeval, 'general'))

    def test_simple_rectangular_float(self):
        self.check(scipy.sparse.csr_array([[1, 2], [3.5, 4], [5, 6]]),
                   (3, 2, 6, 'coordinate', 'real', 'general'))

    def test_simple_float(self):
        self.check(scipy.sparse.csr_array([[1, 2], [3, 4.0]]),
                   (2, 2, 4, 'coordinate', 'real', 'general'))

    def test_simple_complex(self):
        self.check(scipy.sparse.csr_array([[1, 2], [3, 4j]]),
                   (2, 2, 4, 'coordinate', 'complex', 'general'))

    @pytest.mark.parametrize('typeval, dtype', parametrize_args)
    def test_simple_symmetric_integer(self, typeval, dtype):
        self.check_exact(scipy.sparse.csr_array([[1, 2], [2, 4]], dtype=dtype),
                         (2, 2, 3, 'coordinate', typeval, 'symmetric'))

    def test_simple_skew_symmetric_integer(self):
        self.check_exact(scipy.sparse.csr_array([[0, 2], [-2, 0]]),
                         (2, 2, 1, 'coordinate', 'integer', 'skew-symmetric'))

    def test_simple_skew_symmetric_float(self):
        self.check(scipy.sparse.csr_array(array([[0, 2], [-2.0, 0]], 'f')),
                   (2, 2, 1, 'coordinate', 'real', 'skew-symmetric'))

    def test_simple_hermitian_complex(self):
        self.check(scipy.sparse.csr_array([[1, 2+3j], [2-3j, 4]]),
                   (2, 2, 3, 'coordinate', 'complex', 'hermitian'))

    def test_random_symmetric_float(self):
        sz = (20, 20)
        a = np.random.random(sz)
        a = a + transpose(a)
        a = scipy.sparse.csr_array(a)
        self.check(a, (20, 20, 210, 'coordinate', 'real', 'symmetric'))

    def test_random_rectangular_float(self):
        sz = (20, 15)
        a = np.random.random(sz)
        a = scipy.sparse.csr_array(a)
        self.check(a, (20, 15, 300, 'coordinate', 'real', 'general'))

    def test_simple_pattern(self):
        a = scipy.sparse.csr_array([[0, 1.5], [3.0, 2.5]])
        p = np.zeros_like(a.toarray())
        p[a.toarray() > 0] = 1
        info = (2, 2, 3, 'coordinate', 'pattern', 'general')
        mmwrite(self.fn, a, field='pattern')
        assert_equal(mminfo(self.fn), info)
        b = mmread(self.fn, spmatrix=False)
        assert_array_almost_equal(p, b.toarray())
        assert not scipy.sparse.isspmatrix(b)

        b = mmread(self.fn, spmatrix=True)
        assert scipy.sparse.isspmatrix(b)
        b = mmread(self.fn)  # chk default
        assert scipy.sparse.isspmatrix(b)

    def test_gh13634_non_skew_symmetric_int(self):
        a = scipy.sparse.csr_array([[1, 2], [-2, 99]], dtype=np.int32)
        self.check_exact(a, (2, 2, 4, 'coordinate', 'integer', 'general'))

    def test_gh13634_non_skew_symmetric_float(self):
        a = scipy.sparse.csr_array([[1, 2], [-2, 99.]], dtype=np.float32)
        self.check(a, (2, 2, 4, 'coordinate', 'real', 'general'))


_32bit_integer_dense_example = '''\
%%MatrixMarket matrix array integer general
2  2
2147483647
2147483646
2147483647
2147483646
'''

_32bit_integer_sparse_example = '''\
%%MatrixMarket matrix coordinate integer symmetric
2  2  2
1  1  2147483647
2  2  2147483646
'''

_64bit_integer_dense_example = '''\
%%MatrixMarket matrix array integer general
2  2
          2147483648
-9223372036854775806
         -2147483648
 9223372036854775807
'''

_64bit_integer_sparse_general_example = '''\
%%MatrixMarket matrix coordinate integer general
2  2  3
1  1           2147483648
1  2  9223372036854775807
2  2  9223372036854775807
'''

_64bit_integer_sparse_symmetric_example = '''\
%%MatrixMarket matrix coordinate integer symmetric
2  2  3
1  1            2147483648
1  2  -9223372036854775807
2  2   9223372036854775807
'''

_64bit_integer_sparse_skew_example = '''\
%%MatrixMarket matrix coordinate integer skew-symmetric
2  2  3
1  1            2147483648
1  2  -9223372036854775807
2  2   9223372036854775807
'''

_over64bit_integer_dense_example = '''\
%%MatrixMarket matrix array integer general
2  2
         2147483648
9223372036854775807
         2147483648
9223372036854775808
'''

_over64bit_integer_sparse_example = '''\
%%MatrixMarket matrix coordinate integer symmetric
2  2  2
1  1            2147483648
2  2  19223372036854775808
'''


class TestMMIOReadLargeIntegers:
    def setup_method(self):
        self.tmpdir = mkdtemp(suffix=str(threading.get_native_id()))
        self.fn = os.path.join(self.tmpdir, 'testfile.mtx')

    def teardown_method(self):
        shutil.rmtree(self.tmpdir)

    def check_read(self, example, a, info, dense, over32, over64):
        with open(self.fn, 'w') as f:
            f.write(example)
        assert_equal(mminfo(self.fn), info)
        if ((over32 and (np.intp(0).itemsize < 8) and mmwrite == scipy.io._mmio.mmwrite)
            or over64):
            assert_raises(OverflowError, mmread, self.fn)
        else:
            b = mmread(self.fn, spmatrix=False)
            if not dense:
                b = b.toarray()
            assert_equal(a, b)

    def test_read_32bit_integer_dense(self):
        a = array([[2**31-1, 2**31-1],
                   [2**31-2, 2**31-2]], dtype=np.int64)
        self.check_read(_32bit_integer_dense_example,
                        a,
                        (2, 2, 4, 'array', 'integer', 'general'),
                        dense=True,
                        over32=False,
                        over64=False)

    def test_read_32bit_integer_sparse(self):
        a = array([[2**31-1, 0],
                   [0, 2**31-2]], dtype=np.int64)
        self.check_read(_32bit_integer_sparse_example,
                        a,
                        (2, 2, 2, 'coordinate', 'integer', 'symmetric'),
                        dense=False,
                        over32=False,
                        over64=False)

    def test_read_64bit_integer_dense(self):
        a = array([[2**31, -2**31],
                   [-2**63+2, 2**63-1]], dtype=np.int64)
        self.check_read(_64bit_integer_dense_example,
                        a,
                        (2, 2, 4, 'array', 'integer', 'general'),
                        dense=True,
                        over32=True,
                        over64=False)

    def test_read_64bit_integer_sparse_general(self):
        a = array([[2**31, 2**63-1],
                   [0, 2**63-1]], dtype=np.int64)
        self.check_read(_64bit_integer_sparse_general_example,
                        a,
                        (2, 2, 3, 'coordinate', 'integer', 'general'),
                        dense=False,
                        over32=True,
                        over64=False)

    def test_read_64bit_integer_sparse_symmetric(self):
        a = array([[2**31, -2**63+1],
                   [-2**63+1, 2**63-1]], dtype=np.int64)
        self.check_read(_64bit_integer_sparse_symmetric_example,
                        a,
                        (2, 2, 3, 'coordinate', 'integer', 'symmetric'),
                        dense=False,
                        over32=True,
                        over64=False)

    def test_read_64bit_integer_sparse_skew(self):
        a = array([[2**31, -2**63+1],
                   [2**63-1, 2**63-1]], dtype=np.int64)
        self.check_read(_64bit_integer_sparse_skew_example,
                        a,
                        (2, 2, 3, 'coordinate', 'integer', 'skew-symmetric'),
                        dense=False,
                        over32=True,
                        over64=False)

    def test_read_over64bit_integer_dense(self):
        self.check_read(_over64bit_integer_dense_example,
                        None,
                        (2, 2, 4, 'array', 'integer', 'general'),
                        dense=True,
                        over32=True,
                        over64=True)

    def test_read_over64bit_integer_sparse(self):
        self.check_read(_over64bit_integer_sparse_example,
                        None,
                        (2, 2, 2, 'coordinate', 'integer', 'symmetric'),
                        dense=False,
                        over32=True,
                        over64=True)


_general_example = '''\
%%MatrixMarket matrix coordinate real general
%=================================================================================
%
% This ASCII file represents a sparse MxN matrix with L
% nonzeros in the following Matrix Market format:
%
% +----------------------------------------------+
% |%%MatrixMarket matrix coordinate real general | <--- header line
% |%                                             | <--+
% |% comments                                    |    |-- 0 or more comment lines
% |%                                             | <--+
% |    M  N  L                                   | <--- rows, columns, entries
% |    I1  J1  A(I1, J1)                         | <--+
% |    I2  J2  A(I2, J2)                         |    |
% |    I3  J3  A(I3, J3)                         |    |-- L lines
% |        . . .                                 |    |
% |    IL JL  A(IL, JL)                          | <--+
% +----------------------------------------------+
%
% Indices are 1-based, i.e. A(1,1) is the first element.
%
%=================================================================================
  5  5  8
    1     1   1.000e+00
    2     2   1.050e+01
    3     3   1.500e-02
    1     4   6.000e+00
    4     2   2.505e+02
    4     4  -2.800e+02
    4     5   3.332e+01
    5     5   1.200e+01
'''

_hermitian_example = '''\
%%MatrixMarket matrix coordinate complex hermitian
  5  5  7
    1     1     1.0      0
    2     2    10.5      0
    4     2   250.5     22.22
    3     3     1.5e-2   0
    4     4    -2.8e2    0
    5     5    12.       0
    5     4     0       33.32
'''

_skew_example = '''\
%%MatrixMarket matrix coordinate real skew-symmetric
  5  5  7
    1     1     1.0
    2     2    10.5
    4     2   250.5
    3     3     1.5e-2
    4     4    -2.8e2
    5     5    12.
    5     4     0
'''

_symmetric_example = '''\
%%MatrixMarket matrix coordinate real symmetric
  5  5  7
    1     1     1.0
    2     2    10.5
    4     2   250.5
    3     3     1.5e-2
    4     4    -2.8e2
    5     5    12.
    5     4     8
'''

_symmetric_pattern_example = '''\
%%MatrixMarket matrix coordinate pattern symmetric
  5  5  7
    1     1
    2     2
    4     2
    3     3
    4     4
    5     5
    5     4
'''

# example (without comment lines) from Figure 1 in
# https://math.nist.gov/MatrixMarket/reports/MMformat.ps
_empty_lines_example = '''\
%%MatrixMarket  MATRIX    Coordinate    Real General

   5  5         8

1 1  1.0
2 2       10.5
3 3             1.5e-2
4 4                     -2.8E2
5 5                              12.
     1      4      6
     4      2      250.5
     4      5      33.32

'''


class TestMMIOCoordinate:
    def setup_method(self):
        self.tmpdir = mkdtemp(suffix=str(threading.get_native_id()))
        self.fn = os.path.join(self.tmpdir, 'testfile.mtx')

    def teardown_method(self):
        shutil.rmtree(self.tmpdir)

    def check_read(self, example, a, info):
        f = open(self.fn, 'w')
        f.write(example)
        f.close()
        assert_equal(mminfo(self.fn), info)
        b = mmread(self.fn, spmatrix=False).toarray()
        assert_array_almost_equal(a, b)

    def test_read_general(self):
        a = [[1, 0, 0, 6, 0],
             [0, 10.5, 0, 0, 0],
             [0, 0, .015, 0, 0],
             [0, 250.5, 0, -280, 33.32],
             [0, 0, 0, 0, 12]]
        self.check_read(_general_example, a,
                        (5, 5, 8, 'coordinate', 'real', 'general'))

    def test_read_hermitian(self):
        a = [[1, 0, 0, 0, 0],
             [0, 10.5, 0, 250.5 - 22.22j, 0],
             [0, 0, .015, 0, 0],
             [0, 250.5 + 22.22j, 0, -280, -33.32j],
             [0, 0, 0, 33.32j, 12]]
        self.check_read(_hermitian_example, a,
                        (5, 5, 7, 'coordinate', 'complex', 'hermitian'))

    def test_read_skew(self):
        a = [[1, 0, 0, 0, 0],
             [0, 10.5, 0, -250.5, 0],
             [0, 0, .015, 0, 0],
             [0, 250.5, 0, -280, 0],
             [0, 0, 0, 0, 12]]
        self.check_read(_skew_example, a,
                        (5, 5, 7, 'coordinate', 'real', 'skew-symmetric'))

    def test_read_symmetric(self):
        a = [[1, 0, 0, 0, 0],
             [0, 10.5, 0, 250.5, 0],
             [0, 0, .015, 0, 0],
             [0, 250.5, 0, -280, 8],
             [0, 0, 0, 8, 12]]
        self.check_read(_symmetric_example, a,
                        (5, 5, 7, 'coordinate', 'real', 'symmetric'))

    def test_read_symmetric_pattern(self):
        a = [[1, 0, 0, 0, 0],
             [0, 1, 0, 1, 0],
             [0, 0, 1, 0, 0],
             [0, 1, 0, 1, 1],
             [0, 0, 0, 1, 1]]
        self.check_read(_symmetric_pattern_example, a,
                        (5, 5, 7, 'coordinate', 'pattern', 'symmetric'))

    def test_read_empty_lines(self):
        a = [[1, 0, 0, 6, 0],
             [0, 10.5, 0, 0, 0],
             [0, 0, .015, 0, 0],
             [0, 250.5, 0, -280, 33.32],
             [0, 0, 0, 0, 12]]
        self.check_read(_empty_lines_example, a,
                        (5, 5, 8, 'coordinate', 'real', 'general'))

    def test_empty_write_read(self):
        # https://github.com/scipy/scipy/issues/1410 (Trac #883)

        b = scipy.sparse.coo_array((10, 10))
        mmwrite(self.fn, b)

        assert_equal(mminfo(self.fn),
                     (10, 10, 0, 'coordinate', 'real', 'symmetric'))
        a = b.toarray()
        b = mmread(self.fn, spmatrix=False).toarray()
        assert_array_almost_equal(a, b)

    def test_bzip2_py3(self):
        # test if fix for #2152 works
        try:
            # bz2 module isn't always built when building Python.
            import bz2
        except ImportError:
            return
        I = array([0, 0, 1, 2, 3, 3, 3, 4])
        J = array([0, 3, 1, 2, 1, 3, 4, 4])
        V = array([1.0, 6.0, 10.5, 0.015, 250.5, -280.0, 33.32, 12.0])

        b = scipy.sparse.coo_array((V, (I, J)), shape=(5, 5))

        mmwrite(self.fn, b)

        fn_bzip2 = f"{self.fn}.bz2"
        with open(self.fn, 'rb') as f_in:
            f_out = bz2.BZ2File(fn_bzip2, 'wb')
            f_out.write(f_in.read())
            f_out.close()

        a = mmread(fn_bzip2, spmatrix=False).toarray()
        assert_array_almost_equal(a, b.toarray())

    def test_gzip_py3(self):
        # test if fix for #2152 works
        try:
            # gzip module can be missing from Python installation
            import gzip
        except ImportError:
            return
        I = array([0, 0, 1, 2, 3, 3, 3, 4])
        J = array([0, 3, 1, 2, 1, 3, 4, 4])
        V = array([1.0, 6.0, 10.5, 0.015, 250.5, -280.0, 33.32, 12.0])

        b = scipy.sparse.coo_array((V, (I, J)), shape=(5, 5))

        mmwrite(self.fn, b)

        fn_gzip = f"{self.fn}.gz"
        with open(self.fn, 'rb') as f_in:
            f_out = gzip.open(fn_gzip, 'wb')
            f_out.write(f_in.read())
            f_out.close()

        a = mmread(fn_gzip, spmatrix=False).toarray()
        assert_array_almost_equal(a, b.toarray())

    def test_real_write_read(self):
        I = array([0, 0, 1, 2, 3, 3, 3, 4])
        J = array([0, 3, 1, 2, 1, 3, 4, 4])
        V = array([1.0, 6.0, 10.5, 0.015, 250.5, -280.0, 33.32, 12.0])

        b = scipy.sparse.coo_array((V, (I, J)), shape=(5, 5))

        mmwrite(self.fn, b)

        assert_equal(mminfo(self.fn),
                     (5, 5, 8, 'coordinate', 'real', 'general'))
        a = b.toarray()
        b = mmread(self.fn, spmatrix=False).toarray()
        assert_array_almost_equal(a, b)

    def test_complex_write_read(self):
        I = array([0, 0, 1, 2, 3, 3, 3, 4])
        J = array([0, 3, 1, 2, 1, 3, 4, 4])
        V = array([1.0 + 3j, 6.0 + 2j, 10.50 + 0.9j, 0.015 + -4.4j,
                   250.5 + 0j, -280.0 + 5j, 33.32 + 6.4j, 12.00 + 0.8j])

        b = scipy.sparse.coo_array((V, (I, J)), shape=(5, 5))

        mmwrite(self.fn, b)

        assert_equal(mminfo(self.fn),
                     (5, 5, 8, 'coordinate', 'complex', 'general'))
        a = b.toarray()
        b = mmread(self.fn, spmatrix=False).toarray()
        assert_array_almost_equal(a, b)

    def test_sparse_formats(self, tmp_path):
        # Note: `tmp_path` is a pytest fixture, it handles cleanup
        tmpdir = tmp_path / 'sparse_formats'
        tmpdir.mkdir()

        mats = []
        I = array([0, 0, 1, 2, 3, 3, 3, 4])
        J = array([0, 3, 1, 2, 1, 3, 4, 4])

        V = array([1.0, 6.0, 10.5, 0.015, 250.5, -280.0, 33.32, 12.0])
        mats.append(scipy.sparse.coo_array((V, (I, J)), shape=(5, 5)))

        V = array([1.0 + 3j, 6.0 + 2j, 10.50 + 0.9j, 0.015 + -4.4j,
                   250.5 + 0j, -280.0 + 5j, 33.32 + 6.4j, 12.00 + 0.8j])
        mats.append(scipy.sparse.coo_array((V, (I, J)), shape=(5, 5)))

        for mat in mats:
            expected = mat.toarray()
            for fmt in ['csr', 'csc', 'coo']:
                fname = tmpdir / (fmt + '.mtx')
                mmwrite(fname, mat.asformat(fmt))
                result = mmread(fname, spmatrix=False).toarray()
                assert_array_almost_equal(result, expected)

    def test_precision(self):
        test_values = [pi] + [10**(i) for i in range(0, -10, -1)]
        test_precisions = range(1, 10)
        for value in test_values:
            for precision in test_precisions:
                # construct sparse matrix with test value at last main diagonal
                n = 10**precision + 1
                A = scipy.sparse.dok_array((n, n))
                A[n-1, n-1] = value
                # write matrix with test precision and read again
                mmwrite(self.fn, A, precision=precision)
                A = scipy.io.mmread(self.fn, spmatrix=False)
                # check for right entries in matrix
                assert_array_equal(A.row, [n-1])
                assert_array_equal(A.col, [n-1])
                assert_allclose(A.data, [float('%%.%dg' % precision % value)])

    def test_bad_number_of_coordinate_header_fields(self):
        s = """\
            %%MatrixMarket matrix coordinate real general
              5  5  8 999
                1     1   1.000e+00
                2     2   1.050e+01
                3     3   1.500e-02
                1     4   6.000e+00
                4     2   2.505e+02
                4     4  -2.800e+02
                4     5   3.332e+01
                5     5   1.200e+01
            """
        text = textwrap.dedent(s).encode('ascii')
        with pytest.raises(ValueError, match='not of length 3'):
            scipy.io.mmread(io.BytesIO(text))


def test_gh11389():
    mmread(io.StringIO("%%MatrixMarket matrix coordinate complex symmetric\n"
                       " 1 1 1\n"
                       "1 1 -2.1846000000000e+02  0.0000000000000e+00"),
           spmatrix=False)


def test_gh18123(tmp_path):
    lines = [" %%MatrixMarket matrix coordinate real general\n",
             "5 5 3\n",
             "2 3 1.0\n",
             "3 4 2.0\n",
             "3 5 3.0\n"]
    test_file = tmp_path / "test.mtx"
    with open(test_file, "w") as f:
        f.writelines(lines)
    mmread(test_file, spmatrix=False)

def test_mtx_append(tmp_path):
    a = mmread(io.StringIO("%%MatrixMarket matrix coordinate complex symmetric\n"
                           " 1 1 1\n"
                           "1 1 -2.1846000000000e+02  0.0000000000000e+00"),
               spmatrix=False)
    test_writefile = tmp_path / "test_mtx"
    test_readfile  = tmp_path / "test_mtx.mtx"
    mmwrite(test_writefile, a)
    mmread(test_readfile, spmatrix=False)


def test_threadpoolctl():
    try:
        import threadpoolctl
        if not hasattr(threadpoolctl, "register"):
            pytest.skip("threadpoolctl too old")
            return
    except ImportError:
        pytest.skip("no threadpoolctl")
        return

    with threadpoolctl.threadpool_limits(limits=4):
        assert_equal(fmm.PARALLELISM, 4)

    with threadpoolctl.threadpool_limits(limits=2, user_api='scipy'):
        assert_equal(fmm.PARALLELISM, 2)
