# Copyright 2020 pydicom authors. See LICENSE file for details.
"""Tests for the pixel_data_handlers.pylibjpeg_handler module."""

import pytest

import pydicom
from pydicom.data import get_testdata_file
from pydicom.encaps import defragment_data
from pydicom.filereader import dcmread
from pydicom.pixel_data_handlers.util import (
    convert_color_space, get_j2k_parameters, get_expected_length
)
from pydicom.uid import (
    ImplicitVRLittleEndian,
    JPEGBaseline8Bit,
    JPEGExtended12Bit,
    JPEGLosslessP14,
    JPEGLosslessSV1,
    JPEGLSLossless,
    JPEGLSNearLossless,
    JPEG2000Lossless,
    JPEG2000,
    RLELossless,
    AllTransferSyntaxes
)

try:
    import numpy as np
    from pydicom.pixel_data_handlers import numpy_handler as NP_HANDLER
    HAVE_NP = True
except ImportError:
    NP_HANDLER = None
    HAVE_NP = False

try:
    from pydicom.pixel_data_handlers import pylibjpeg_handler as LJ_HANDLER
    from pydicom.pixel_data_handlers.pylibjpeg_handler import (
        get_pixeldata, as_array, generate_frames
    )
    HAVE_PYLIBJPEG = LJ_HANDLER.HAVE_PYLIBJPEG
    HAVE_LJ = LJ_HANDLER.HAVE_LIBJPEG
    HAVE_OJ = LJ_HANDLER.HAVE_OPENJPEG
    HAVE_RLE = LJ_HANDLER.HAVE_RLE
except ImportError:
    LJ_HANDLER = None
    HAVE_PYLIBJPEG = False
    HAVE_LJ = False
    HAVE_OJ = False
    HAVE_RLE = False


TEST_HANDLER = HAVE_NP and HAVE_PYLIBJPEG  # Run handler tests
TEST_JPEG = TEST_HANDLER and HAVE_LJ  # Run 10918 JPEG tests
TEST_JPEGLS = TEST_HANDLER and HAVE_LJ  # Run 14495 JPEG-LS tests
TEST_JPEG2K = TEST_HANDLER and HAVE_OJ  # Run 15444 JPEG 2000 tests
TEST_RLE = TEST_HANDLER and HAVE_RLE  # Run RLE Lossless tests


SUPPORTED_SYNTAXES = [
    JPEGBaseline8Bit,
    JPEGExtended12Bit,
    JPEGLosslessP14,
    JPEGLosslessSV1,
    JPEGLSLossless,
    JPEGLSNearLossless,
    JPEG2000Lossless,
    JPEG2000,
    RLELossless,
]
UNSUPPORTED_SYNTAXES = list(
    set(AllTransferSyntaxes) ^ set(SUPPORTED_SYNTAXES)
)

# Transfer syntaxes supported by other handlers
IMPL = get_testdata_file("MR_small_implicit.dcm")
EXPL = get_testdata_file("OBXXXX1A.dcm")
EXPB = get_testdata_file("OBXXXX1A_expb.dcm")
DEFL = get_testdata_file("image_dfl.dcm")

REFERENCE_DATA_UNSUPPORTED = [
    (IMPL, ('1.2.840.10008.1.2', 'CompressedSamples^MR1')),
    (EXPL, ('1.2.840.10008.1.2.1', 'OB^^^^')),
    (EXPB, ('1.2.840.10008.1.2.2', 'OB^^^^')),
    (DEFL, ('1.2.840.10008.1.2.1.99', '^^^^')),
]

# RLE Lossless - PackBits algorithm
RLE_8_1_1F = get_testdata_file("OBXXXX1A_rle.dcm")
RLE_8_1_2F = get_testdata_file("OBXXXX1A_rle_2frame.dcm")
RLE_8_3_1F = get_testdata_file("SC_rgb_rle.dcm")
RLE_8_3_2F = get_testdata_file("SC_rgb_rle_2frame.dcm")
RLE_16_1_1F = get_testdata_file("MR_small_RLE.dcm")
RLE_16_1_10F = get_testdata_file("emri_small_RLE.dcm")
RLE_16_3_1F = get_testdata_file("SC_rgb_rle_16bit.dcm")
RLE_16_3_2F = get_testdata_file("SC_rgb_rle_16bit_2frame.dcm")
RLE_32_1_1F = get_testdata_file("rtdose_rle_1frame.dcm")
RLE_32_1_15F = get_testdata_file("rtdose_rle.dcm")
RLE_32_3_1F = get_testdata_file("SC_rgb_rle_32bit.dcm")
RLE_32_3_2F = get_testdata_file("SC_rgb_rle_32bit_2frame.dcm")

# JPEG - ISO/IEC 10918 Standard
# FMT_BA_BV_SPX_PR_FRAMESF_PI
# JPGB: 1.2.840.10008.1.2.4.50 - JPEG Baseline (8-bit only)
JPGB_08_08_3_0_1F_YBR_FULL = get_testdata_file("SC_rgb_small_odd_jpeg.dcm")
JPGB_08_08_3_0_120F_YBR_FULL_422 = get_testdata_file("color3d_jpeg_baseline.dcm")  # noqa
# Different subsampling 411, 422, 444
JPGB_08_08_3_0_1F_YBR_FULL_422_411 = get_testdata_file("SC_rgb_dcmtk_+eb+cy+np.dcm")  # noqa
JPGB_08_08_3_0_1F_YBR_FULL_422_422 = get_testdata_file("SC_rgb_dcmtk_+eb+cy+s2.dcm")  # noqa
JPGB_08_08_3_0_1F_YBR_FULL_411 = get_testdata_file("SC_rgb_dcmtk_+eb+cy+n1.dcm")  # noqa
JPGB_08_08_3_0_1F_YBR_FULL_422 = get_testdata_file("SC_rgb_dcmtk_+eb+cy+n2.dcm")  # noqa
JPGB_08_08_3_0_1F_YBR_FULL_444 = get_testdata_file("SC_rgb_dcmtk_+eb+cy+s4.dcm")  # noqa
JPGB_08_08_3_0_1F_RGB = get_testdata_file("SC_rgb_dcmtk_+eb+cr.dcm")
# JPGE: 1.2.840.1.2.4.51 - JPEG Extended
JPGE_BAD = get_testdata_file("JPEG-lossy.dcm")  # Bad JPEG file
JPGE_16_12_1_0_1F_M2 = get_testdata_file("JPGExtended.dcm")  # Fixed version
# JPGL: 1.2.840.10008.1.2.4.70 - JPEG Lossless, Non-hierarchical, 1st Order
JPGL_08_08_1_0_1F = get_testdata_file("JPGLosslessP14SV1_1s_1f_8b.dcm")
JPGL_16_16_1_1_1F_M2 = get_testdata_file("JPEG-LL.dcm")

JPGB = JPEGBaseline8Bit
JPGE = JPEGExtended12Bit
JPGL = JPEGLosslessSV1

JPG_REFERENCE_DATA = [
    # fpath, (syntax, bits, nr samples, pixel repr, nr frames, shape, dtype)
    (JPGB_08_08_3_0_120F_YBR_FULL_422, (JPGB, 8, 3, 0, 120, (120, 480, 640, 3), 'uint8')),  # noqa
    (JPGB_08_08_3_0_1F_YBR_FULL_422_411, (JPGB, 8, 3, 0, 1, (100, 100, 3), 'uint8')),  # noqa
    (JPGB_08_08_3_0_1F_YBR_FULL_422_422, (JPGB, 8, 3, 0, 1, (100, 100, 3), 'uint8')),  # noqa
    (JPGB_08_08_3_0_1F_YBR_FULL_411, (JPGB, 8, 3, 0, 1, (100, 100, 3), 'uint8')),  # noqa
    (JPGB_08_08_3_0_1F_YBR_FULL_422, (JPGB, 8, 3, 0, 1, (100, 100, 3), 'uint8')),  # noqa
    (JPGB_08_08_3_0_1F_YBR_FULL_444, (JPGB, 8, 3, 0, 1, (100, 100, 3), 'uint8')),  # noqa
    (JPGB_08_08_3_0_1F_RGB, (JPGB, 8, 3, 0, 1, (100, 100, 3), 'uint8')),
    (JPGE_16_12_1_0_1F_M2, (JPGE, 16, 1, 0, 1, (1024, 256), 'uint16')),
    (JPGL_08_08_1_0_1F, (JPGL, 8, 1, 0, 1, (768, 1024), 'uint8')),
    (JPGL_16_16_1_1_1F_M2, (JPGL, 16, 1, 1, 1, (1024, 256), 'int16')),
]
JPG_MATCHING_DATASETS = [
    # (compressed, reference, hard coded check values), px tolerance
    pytest.param(
        JPGB_08_08_3_0_1F_YBR_FULL_422_411,
        get_testdata_file("SC_rgb_dcmtk_ebcynp_dcmd.dcm"),
        [
            (253, 1, 0), (253, 129, 131), (0, 255, 5), (127, 255, 129),
            (0, 0, 254), (127, 128, 255), (0, 0, 0), (64, 64, 64),
            (192, 192, 192), (255, 255, 255),
        ],
        2
    ),
    pytest.param(
        JPGB_08_08_3_0_1F_YBR_FULL_422_422,
        get_testdata_file("SC_rgb_dcmtk_ebcys2_dcmd.dcm"),
        [
            (254, 0, 0), (255, 127, 127), (0, 255, 5), (129, 255, 129),
            (0, 0, 254), (128, 127, 255), (0, 0, 0), (64, 64, 64),
            (192, 192, 192), (255, 255, 255),
        ],
        0
    ),
    pytest.param(
        JPGB_08_08_3_0_1F_YBR_FULL_411,
        get_testdata_file("SC_rgb_dcmtk_ebcyn1_dcmd.dcm"),
        [
            (253, 1, 0), (253, 129, 131), (0, 255, 5), (127, 255, 129),
            (0, 0, 254), (127, 128, 255), (0, 0, 0), (64, 64, 64),
            (192, 192, 192), (255, 255, 255),
        ],
        2
    ),
    pytest.param(
        JPGB_08_08_3_0_1F_YBR_FULL_422,
        get_testdata_file("SC_rgb_dcmtk_ebcyn2_dcmd.dcm"),
        [
            (254, 0, 0), (255, 127, 127), (0, 255, 5), (129, 255, 129),
            (0, 0, 254), (128, 127, 255), (0, 0, 0), (64, 64, 64),
            (192, 192, 192), (255, 255, 255),
        ],
        0
    ),
    pytest.param(
        JPGB_08_08_3_0_1F_YBR_FULL_444,
        get_testdata_file("SC_rgb_dcmtk_ebcys4_dcmd.dcm"),
        [
            (254, 0, 0), (255, 127, 127), (0, 255, 5), (129, 255, 129),
            (0, 0, 254), (128, 127, 255), (0, 0, 0), (64, 64, 64),
            (192, 192, 192), (255, 255, 255),
        ],
        0
    ),
    pytest.param(
        JPGB_08_08_3_0_1F_RGB,
        get_testdata_file("SC_rgb_dcmtk_ebcr_dcmd.dcm"),
        [
            (255, 0, 0), (255, 128, 128), (0, 255, 0), (128, 255, 128),
            (0, 0, 255), (128, 128, 255), (0, 0, 0), (64, 64, 64),
            (192, 192, 192), (255, 255, 255),
        ],
        1
    ),
]


# JPEG-LS - ISO/IEC 14495 Standard
JLSL = JPEGLSNearLossless
JLSN = JPEGLSLossless
JPEG_LS_LOSSLESS = get_testdata_file("MR_small_jpeg_ls_lossless.dcm")
JLS_REFERENCE_DATA = [
    # fpath, (syntax, bits, nr samples, pixel repr, nr frames, shape, dtype)
    (JPEG_LS_LOSSLESS, (JLSN, 16, 1, 1, 1, (64, 64), 'int16')),
]

# JPEG 2000 - ISO/IEC 15444 Standard
J2KR = JPEG2000Lossless
J2KI = JPEG2000
# J2KR: 1.2.840.100008.1.2.4.90 - JPEG 2000 Lossless
J2KR_08_08_3_0_1F_YBR_ICT = get_testdata_file("US1_J2KR.dcm")
J2KR_16_10_1_0_1F_M1 = get_testdata_file("RG3_J2KR.dcm")
J2KR_16_12_1_0_1F_M2 = get_testdata_file("MR2_J2KR.dcm")
J2KR_16_15_1_0_1F_M1 = get_testdata_file("RG1_J2KR.dcm")
J2KR_16_16_1_0_10F_M2 = get_testdata_file("emri_small_jpeg_2k_lossless.dcm")
J2KR_16_14_1_1_1F_M2 = get_testdata_file("693_J2KR.dcm")
J2KR_16_16_1_1_1F_M2 = get_testdata_file("MR_small_jp2klossless.dcm")
J2KR_16_13_1_1_1F_M2_MISMATCH = get_testdata_file("J2K_pixelrep_mismatch.dcm")
# Non-conformant pixel data -> JP2 header present
J2KR_08_08_3_0_1F_YBR_RCT = get_testdata_file("GDCMJ2K_TextGBR.dcm")
# J2KI: 1.2.840.10008.1.2.4.91 - JPEG 2000
J2KI_08_08_3_0_1F_RGB = get_testdata_file("SC_rgb_gdcm_KY.dcm")
J2KI_08_08_3_0_1F_YBR_ICT = get_testdata_file("US1_J2KI.dcm")
J2KI_16_10_1_0_1F_M1 = get_testdata_file("RG3_J2KI.dcm")
J2KI_16_12_1_0_1F_M2 = get_testdata_file("MR2_J2KI.dcm")
J2KI_16_15_1_0_1F_M1 = get_testdata_file("RG1_J2KI.dcm")
J2KI_16_14_1_1_1F_M2 = get_testdata_file("693_J2KI.dcm")
J2KI_16_16_1_1_1F_M2 = get_testdata_file("JPEG2000.dcm")

J2K_REFERENCE_DATA = [
    # fpath, (syntax, bits, nr samples, pixel repr, nr frames, shape, dtype)
    (J2KR_08_08_3_0_1F_YBR_ICT, (J2KR, 8, 3, 0, 1, (480, 640, 3), 'uint8')),
    (J2KR_16_10_1_0_1F_M1, (J2KR, 16, 1, 0, 1, (1760, 1760), 'uint16')),
    (J2KR_16_12_1_0_1F_M2, (J2KR, 16, 1, 0, 1, (1024, 1024), 'uint16')),
    (J2KR_16_15_1_0_1F_M1, (J2KR, 16, 1, 0, 1, (1955, 1841), 'uint16')),
    # should be Bits Stored = 12
    (J2KR_16_16_1_0_10F_M2, (J2KR, 16, 1, 0, 10, (10, 64, 64), 'uint16')),
    # should be Bits Stored = 16
    (J2KR_16_14_1_1_1F_M2, (J2KR, 16, 1, 1, 1, (512, 512), 'int16')),
    (J2KR_16_16_1_1_1F_M2, (J2KR, 16, 1, 1, 1, (64, 64), 'int16')),
    (J2KI_08_08_3_0_1F_RGB, (J2KI, 8, 3, 0, 1, (100, 100, 3), 'uint8')),
    (J2KI_08_08_3_0_1F_YBR_ICT, (J2KI, 8, 3, 0, 1, (480, 640, 3), 'uint8')),
    (J2KI_16_10_1_0_1F_M1, (J2KI, 16, 1, 0, 1, (1760, 1760), 'uint16')),
    (J2KI_16_12_1_0_1F_M2, (J2KI, 16, 1, 0, 1, (1024, 1024), 'uint16')),
    (J2KI_16_15_1_0_1F_M1, (J2KI, 16, 1, 0, 1, (1955, 1841), 'uint16')),
    # should be Bits Stored = 16
    (J2KI_16_14_1_1_1F_M2, (J2KI, 16, 1, 1, 1, (512, 512), 'int16')),
    (J2KI_16_16_1_1_1F_M2, (J2KI, 16, 1, 1, 1, (1024, 256), 'int16')),
]
J2K_MATCHING_DATASETS = [
    # (compressed, reference, fixes)
    pytest.param(
        J2KR_08_08_3_0_1F_YBR_ICT,
        get_testdata_file("US1_UNCR.dcm"),
        {},
    ),
    pytest.param(
        J2KR_16_10_1_0_1F_M1,
        get_testdata_file("RG3_UNCR.dcm"),
        {},
    ),
    pytest.param(
        J2KR_16_12_1_0_1F_M2,
        get_testdata_file("MR2_UNCR.dcm"),
        {},
    ),
    pytest.param(
        J2KR_16_15_1_0_1F_M1,
        get_testdata_file("RG1_UNCR.dcm"),
        {},
    ),
    pytest.param(
        J2KR_16_16_1_0_10F_M2,
        get_testdata_file("emri_small.dcm"),
        {'BitsStored': 16},
    ),
    pytest.param(
        J2KR_16_14_1_1_1F_M2,
        get_testdata_file("693_UNCR.dcm"),
        {'BitsStored': 14},
    ),
    pytest.param(
        J2KR_16_16_1_1_1F_M2,
        get_testdata_file("MR_small.dcm"),
        {},
    ),
    pytest.param(
        J2KI_08_08_3_0_1F_RGB,
        get_testdata_file("SC_rgb_gdcm2k_uncompressed.dcm"),
        {},
    ),
    pytest.param(
        J2KI_08_08_3_0_1F_YBR_ICT,
        get_testdata_file("US1_UNCI.dcm"),
        {},
    ),
    pytest.param(
        J2KI_16_10_1_0_1F_M1,
        get_testdata_file("RG3_UNCI.dcm"),
        {},
    ),
    pytest.param(
        J2KI_16_12_1_0_1F_M2,
        get_testdata_file("MR2_UNCI.dcm"),
        {},
    ),
    pytest.param(
        J2KI_16_15_1_0_1F_M1,
        get_testdata_file("RG1_UNCI.dcm"),
        {},
    ),
    pytest.param(
        J2KI_16_14_1_1_1F_M2,
        get_testdata_file("693_UNCI.dcm"),
        {'BitsStored': 16},
    ),
    pytest.param(
        J2KI_16_16_1_1_1F_M2,
        get_testdata_file("JPEG2000_UNC.dcm"),
        {},
    ),
]


def test_unsupported_syntaxes():
    """Test that UNSUPPORTED_SYNTAXES is as expected."""
    for syntax in SUPPORTED_SYNTAXES:
        assert syntax not in UNSUPPORTED_SYNTAXES


print(not HAVE_PYLIBJPEG, (HAVE_LJ or HAVE_OJ or HAVE_RLE))
@pytest.mark.skipif(not HAVE_PYLIBJPEG, reason='pylibjpeg not available')
class TestHandler:
    """Tests for handling Pixel Data with the handler."""
    def setup_method(self):
        """Setup the test datasets and the environment."""
        self.original_handlers = pydicom.config.pixel_data_handlers
        pydicom.config.pixel_data_handlers = [NP_HANDLER, LJ_HANDLER]

    def teardown_method(self):
        """Restore the environment."""
        pydicom.config.pixel_data_handlers = self.original_handlers

    def test_environment(self):
        """Check that the testing environment is as expected."""
        assert HAVE_NP
        assert HAVE_PYLIBJPEG
        assert LJ_HANDLER is not None

    def test_unsupported_syntax_raises(self):
        """Test pixel_array raises exception for unsupported syntaxes."""
        pydicom.config.pixel_data_handlers = [LJ_HANDLER]

        ds = dcmread(EXPL)
        for uid in UNSUPPORTED_SYNTAXES:
            ds.file_meta.TransferSyntaxUID = uid
            with pytest.raises((NotImplementedError, RuntimeError)):
                ds.pixel_array

    @pytest.mark.skipif(
        HAVE_LJ or HAVE_OJ or HAVE_RLE, reason="plugins available"
    )
    def test_no_plugins_raises(self):
        """Test exception raised if required plugin missing."""
        ds = dcmread(JPGB_08_08_3_0_1F_YBR_FULL)
        msg = (
            r"Unable to convert the Pixel Data as the 'pylibjpeg-libjpeg' "
            r"plugin is not installed"
        )
        with pytest.raises(RuntimeError, match=msg):
            ds.pixel_array

        ds = dcmread(J2KI_08_08_3_0_1F_RGB)
        msg = (
            r"Unable to convert the Pixel Data as the 'pylibjpeg-openjpeg' "
            r"plugin is not installed"
        )
        with pytest.raises(RuntimeError, match=msg):
            ds.pixel_array

        # Don't use pydicom decoder
        ds = dcmread(RLE_8_1_1F)
        msg = (
            r"Unable to convert the Pixel Data as the 'pylibjpeg-rle' "
            r"plugin is not installed"
        )
        with pytest.raises(RuntimeError, match=msg):
            ds.pixel_array

    def test_change_photometric_interpretation(self):
        """Test returned value."""
        ds = dcmread(J2KR_16_12_1_0_1F_M2)
        func = LJ_HANDLER.should_change_PhotometricInterpretation_to_RGB
        assert func(ds) is False


@pytest.mark.skipif(not TEST_JPEG, reason="no -libjpeg plugin")
class TestJPEG:
    def setup_method(self):
        """Setup the test datasets and the environment."""
        self.original_handlers = pydicom.config.pixel_data_handlers
        pydicom.config.pixel_data_handlers = [NP_HANDLER, LJ_HANDLER]

    def teardown_method(self):
        """Restore the environment."""
        pydicom.config.pixel_data_handlers = self.original_handlers

    @pytest.mark.parametrize('fpath, data', JPG_REFERENCE_DATA)
    def test_properties(self, fpath, data):
        """Test dataset and pixel array properties are as expected."""
        ds = dcmread(fpath)
        assert ds.file_meta.TransferSyntaxUID == data[0]
        assert ds.BitsAllocated == data[1]
        assert ds.SamplesPerPixel == data[2]
        assert ds.PixelRepresentation == data[3]
        assert getattr(ds, 'NumberOfFrames', 1) == data[4]

        arr = ds.pixel_array

        assert arr.flags.writeable
        assert data[5] == arr.shape
        assert arr.dtype == data[6]

    @pytest.mark.parametrize('fpath, rpath, val, tol', JPG_MATCHING_DATASETS)
    def test_array(self, fpath, rpath, val, tol):
        """Test pixel_array returns correct values."""
        ds = dcmread(fpath)
        arr = ds.pixel_array
        if 'YBR' in ds.PhotometricInterpretation:
            arr = convert_color_space(arr, ds.PhotometricInterpretation, 'RGB')

        ref = dcmread(rpath).pixel_array

        if val:
            assert tuple(arr[5, 50, :]) == val[0]
            assert tuple(arr[15, 50, :]) == val[1]
            assert tuple(arr[25, 50, :]) == val[2]
            assert tuple(arr[35, 50, :]) == val[3]
            assert tuple(arr[45, 50, :]) == val[4]
            assert tuple(arr[55, 50, :]) == val[5]
            assert tuple(arr[65, 50, :]) == val[6]
            assert tuple(arr[75, 50, :]) == val[7]
            assert tuple(arr[85, 50, :]) == val[8]
            assert tuple(arr[95, 50, :]) == val[9]

        # All results within `tol` intensity units of the reference
        assert np.allclose(arr, ref, atol=tol)

    @pytest.mark.parametrize('fpath, rpath, val, tol', JPG_MATCHING_DATASETS)
    def test_generate_frames(self, fpath, rpath, val, tol):
        """Test pixel_array returns correct values."""
        ds = dcmread(fpath)
        frame_generator = generate_frames(ds)
        ref = dcmread(rpath).pixel_array

        nr_frames = getattr(ds, 'NumberOfFrames', 1)
        for ii in range(nr_frames):
            arr = next(frame_generator)
            if 'YBR' in ds.PhotometricInterpretation:
                arr = convert_color_space(
                    arr, ds.PhotometricInterpretation, 'RGB'
                )

            if nr_frames > 1:
                assert np.allclose(arr, ref[ii, ...], atol=tol)
            else:
                assert np.allclose(arr, ref, atol=tol)

        with pytest.raises(StopIteration):
            next(frame_generator)

    def test_bad_file_raises(self):
        """Test a bad JPEG file raises an exception."""
        ds = dcmread(JPGE_BAD)
        msg = (
            r"libjpeg error code '-1038' returned from Decode\(\): A "
            r"misplaced marker segment was found - scan start must be zero "
            r"and scan stop must be 63 for the sequential operating modes"
        )
        with pytest.raises(RuntimeError, match=msg):
            ds.pixel_array

    def test_missing_element_raises(self):
        """Test that missing required element raises exception."""
        ds = dcmread(JPGB_08_08_3_0_1F_YBR_FULL)
        del ds.PixelData
        msg = (
            r"Unable to convert the pixel data as the following required "
            r"elements are missing from the dataset: PixelData"
        )
        with pytest.raises(AttributeError, match=msg):
            ds.pixel_array


@pytest.mark.skipif(not TEST_JPEGLS, reason="no -libjpeg plugin")
class TestJPEGLS:
    def setup_method(self):
        """Setup the test datasets and the environment."""
        self.original_handlers = pydicom.config.pixel_data_handlers
        pydicom.config.pixel_data_handlers = [NP_HANDLER, LJ_HANDLER]

    def teardown_method(self):
        """Restore the environment."""
        pydicom.config.pixel_data_handlers = self.original_handlers

    @pytest.mark.parametrize('fpath, data', JLS_REFERENCE_DATA)
    def test_properties(self, fpath, data):
        """Test dataset and pixel array properties are as expected."""
        ds = dcmread(fpath)
        assert ds.file_meta.TransferSyntaxUID == data[0]
        assert ds.BitsAllocated == data[1]
        assert ds.SamplesPerPixel == data[2]
        assert ds.PixelRepresentation == data[3]
        assert getattr(ds, 'NumberOfFrames', 1) == data[4]

        arr = ds.pixel_array

        assert arr.flags.writeable
        assert data[5] == arr.shape
        assert arr.dtype == data[6]

    def test_arrary(self):
        """Test returned array values are OK."""
        ds = dcmread(JPEG_LS_LOSSLESS)
        arr = ds.pixel_array

        # Checked against GDCM
        assert (
            [170, 193, 191, 373, 1293, 2053, 1879, 1683, 1711] ==
            arr[55:65, 35].tolist()
        )


@pytest.mark.skipif(not TEST_JPEG2K, reason="no -openjpeg plugin")
class TestJPEG2K:
    def setup_method(self):
        """Setup the test datasets and the environment."""
        self.original_handlers = pydicom.config.pixel_data_handlers
        pydicom.config.pixel_data_handlers = [NP_HANDLER, LJ_HANDLER]

    def teardown_method(self):
        """Restore the environment."""
        pydicom.config.pixel_data_handlers = self.original_handlers

    @pytest.mark.parametrize('fpath, data', J2K_REFERENCE_DATA)
    def test_properties_as_array(self, fpath, data):
        """Test dataset, pixel_array and as_array() are as expected."""
        req_fixes = [
            J2KR_16_16_1_0_10F_M2,
            J2KR_16_14_1_1_1F_M2,
            J2KI_16_14_1_1_1F_M2
        ]

        ds = dcmread(fpath)
        assert ds.file_meta.TransferSyntaxUID == data[0]
        assert ds.BitsAllocated == data[1]
        assert ds.SamplesPerPixel == data[2]
        assert ds.PixelRepresentation == data[3]
        assert getattr(ds, 'NumberOfFrames', 1) == data[4]

        # Check Dataset.pixel_array
        if fpath in req_fixes:
            with pytest.warns(UserWarning):
                arr = ds.pixel_array
        else:
            arr = ds.pixel_array

        assert arr.flags.writeable
        assert data[5] == arr.shape
        assert arr.dtype == data[6]

        # Check handlers as_array() function
        if fpath in req_fixes:
            with pytest.warns(UserWarning):
                arr = as_array(ds)
        else:
            arr = as_array(ds)

        assert arr.flags.writeable
        assert data[5] == arr.shape
        assert arr.dtype == data[6]

    @pytest.mark.parametrize('fpath, rpath, fixes', J2K_MATCHING_DATASETS)
    def test_array(self, fpath, rpath, fixes):
        """Test pixel_array returns correct values."""
        ds = dcmread(fpath)
        if fixes:
            with pytest.warns(UserWarning):
                arr = ds.pixel_array
        else:
            arr = ds.pixel_array

        ref = dcmread(rpath).pixel_array
        assert np.array_equal(arr, ref)

    @pytest.mark.parametrize('fpath, rpath, fixes', J2K_MATCHING_DATASETS)
    def test_generate_frames(self, fpath, rpath, fixes):
        """Test pixel_array returns correct values."""
        ds = dcmread(fpath)
        frame_generator = generate_frames(ds)
        ref = dcmread(rpath).pixel_array

        nr_frames = getattr(ds, 'NumberOfFrames', 1)
        for ii in range(nr_frames):
            if fixes:
                with pytest.warns(UserWarning):
                    arr = next(frame_generator)
            else:
                arr = next(frame_generator)

            if nr_frames > 1:
                assert np.array_equal(arr, ref[ii, ...])
            else:
                assert np.array_equal(arr, ref)

        with pytest.raises(StopIteration):
            next(frame_generator)

    def test_warnings(self):
        """Test the plugin warnings work."""
        # Bits Stored
        ds = dcmread(J2KR_16_14_1_1_1F_M2)
        msg = (
            r"The \(0028,0101\) Bits Stored value '16' in the dataset does "
            r"not match the component precision value '14' found in the JPEG "
            r"2000 data. It's recommended that you change the Bits Stored "
            r"value to produce the correct output"
        )
        with pytest.warns(UserWarning, match=msg):
            ds.pixel_array

        # Pixel Representation
        ds.BitsStored = 14
        ds.PixelRepresentation = 0
        msg = (
            r"The \(0028,0103\) Pixel Representation value '0' \(unsigned\) "
            r"in the dataset does not match the format of the values found in "
            r"the JPEG 2000 data 'signed'"
        )
        with pytest.warns(UserWarning, match=msg):
            ds.pixel_array

        # Samples per Pixel
        ds.PixelRepresentation = 0
        ds.SamplesPerPixel = 3
        msg = (
            r"The \(0028,0002\) Samples per Pixel value '3' in the dataset "
            r"does not match the number of components '1' found in the JPEG "
            r"2000 data. It's recommended that you change the  Samples per "
            r"Pixel value to produce the correct output"
        )
        with pytest.warns(UserWarning, match=msg):
            with pytest.raises(ValueError):
                ds.pixel_array

        # JP2 header
        ds = dcmread(J2KR_08_08_3_0_1F_YBR_RCT)
        msg = (
            r"The \(7FE0,0010\) Pixel Data contains a JPEG 2000 codestream "
            r"with the optional JP2 file format header, which is "
            r"non-conformant to the DICOM Standard \(Part 5, Annex A.4.4\)"
        )
        with pytest.warns(UserWarning, match=msg):
            ds.pixel_array

    def test_decompress_using_pylibjpeg(self):
        """Test decompressing JPEG2K with pylibjpeg handler succeeds."""
        ds = dcmread(J2KR_16_12_1_0_1F_M2)
        ds.decompress(handler_name='pylibjpeg')
        arr = ds.pixel_array

        ds = dcmread(get_testdata_file("MR2_J2KR.dcm"))
        ref = ds.pixel_array
        assert np.array_equal(arr, ref)

    def test_pixel_rep_mismatch(self):
        """Test mismatched j2k sign and Pixel Representation."""
        ds = dcmread(J2KR_16_13_1_1_1F_M2_MISMATCH)
        assert 1 == ds.PixelRepresentation
        assert 13 == ds.BitsStored

        bs = defragment_data(ds.PixelData)
        params = get_j2k_parameters(bs)
        assert 13 == params["precision"]
        assert not params["is_signed"]

        msg = r"value '1' \(signed\)"
        with pytest.warns(UserWarning, match=msg):
            arr = ds.pixel_array

        assert 'int16' == arr.dtype
        assert (512, 512) == arr.shape
        assert arr.flags.writeable

        assert -2000 == arr[0, 0]
        assert [621, 412, 138, -193, -520, -767, -907, -966, -988, -995] == (
            arr[47:57, 279].tolist()
        )
        assert [-377, -121, 141, 383, 633, 910, 1198, 1455, 1638, 1732] == (
            arr[328:338, 106].tolist()
        )


RLE_REFERENCE_DATA = [
    # fpath, (bits, nr samples, pixel repr, nr frames, shape, dtype)
    (RLE_8_1_1F, (8, 1, 0, 1, (600, 800), 'uint8')),
    (RLE_8_1_2F, (8, 1, 0, 2, (2, 600, 800), 'uint8')),
    (RLE_8_3_1F, (8, 3, 0, 1, (100, 100, 3), 'uint8')),
    (RLE_8_3_2F, (8, 3, 0, 2, (2, 100, 100, 3), 'uint8')),
    (RLE_16_1_1F, (16, 1, 1, 1, (64, 64), 'int16')),
    (RLE_16_1_10F, (16, 1, 0, 10, (10, 64, 64), 'uint16')),
    (RLE_16_3_1F, (16, 3, 0, 1, (100, 100, 3), 'uint16')),
    (RLE_16_3_2F, (16, 3, 0, 2, (2, 100, 100, 3), 'uint16')),
    (RLE_32_1_1F, (32, 1, 0, 1, (10, 10), 'uint32')),
    (RLE_32_1_15F, (32, 1, 0, 15, (15, 10, 10), 'uint32')),
    (RLE_32_3_1F, (32, 3, 0, 1, (100, 100, 3), 'uint32')),
    (RLE_32_3_2F, (32, 3, 0, 2, (2, 100, 100, 3), 'uint32')),
]
RLE_MATCHING_DATASETS = [
    # (compressed, reference)
    pytest.param(RLE_8_1_1F, get_testdata_file("OBXXXX1A.dcm")),
    pytest.param(RLE_8_1_2F, get_testdata_file("OBXXXX1A_2frame.dcm")),
    pytest.param(RLE_8_3_1F, get_testdata_file("SC_rgb.dcm")),
    pytest.param(RLE_8_3_2F, get_testdata_file("SC_rgb_2frame.dcm")),
    pytest.param(RLE_16_1_1F, get_testdata_file("MR_small.dcm")),
    pytest.param(RLE_16_1_10F, get_testdata_file("emri_small.dcm")),
    pytest.param(RLE_16_3_1F, get_testdata_file("SC_rgb_16bit.dcm")),
    pytest.param(RLE_16_3_2F, get_testdata_file("SC_rgb_16bit_2frame.dcm")),
    pytest.param(RLE_32_1_1F, get_testdata_file("rtdose_1frame.dcm")),
    pytest.param(RLE_32_1_15F, get_testdata_file("rtdose.dcm")),
    pytest.param(RLE_32_3_1F, get_testdata_file("SC_rgb_32bit.dcm")),
    pytest.param(RLE_32_3_2F, get_testdata_file("SC_rgb_32bit_2frame.dcm")),
]


@pytest.mark.skipif(not TEST_RLE, reason="no -rle plugin")
class TestRLE:
    def test_decompress_using_pylibjpeg(self):
        """Test decompressing RLE with pylibjpeg handler succeeds."""
        ds = dcmread(RLE_8_3_1F)
        ds.decompress(handler_name='pylibjpeg')
        arr = ds.pixel_array

        ds = dcmread(get_testdata_file("SC_rgb.dcm"))
        ref = ds.pixel_array
        assert np.array_equal(arr, ref)

    @pytest.mark.parametrize('fpath, data', RLE_REFERENCE_DATA)
    def test_properties_as_array(self, fpath, data):
        """Test dataset, pixel_array and as_array() are as expected."""
        ds = dcmread(fpath)
        assert RLELossless == ds.file_meta.TransferSyntaxUID
        assert ds.BitsAllocated == data[0]
        assert ds.SamplesPerPixel == data[1]
        assert ds.PixelRepresentation == data[2]
        assert getattr(ds, 'NumberOfFrames', 1) == data[3]

        # Note: decompress modifies the dataset inplace
        ds.decompress("pylibjpeg")

        # Check Dataset.pixel_array
        arr = ds.pixel_array
        assert arr.flags.writeable
        assert data[4] == arr.shape
        assert arr.dtype == data[5]

        # Check handler's as_array() function
        ds = dcmread(fpath)
        arr = as_array(ds)
        assert arr.flags.writeable
        assert data[4] == arr.shape
        assert arr.dtype == data[5]

    @pytest.mark.parametrize('fpath, rpath', RLE_MATCHING_DATASETS)
    def test_array(self, fpath, rpath):
        """Test pixel_array returns correct values."""
        ds = dcmread(fpath)
        ds.decompress("pylibjpeg")
        arr = ds.pixel_array

        ref = dcmread(rpath).pixel_array
        assert np.array_equal(arr, ref)

    @pytest.mark.parametrize('fpath, rpath', RLE_MATCHING_DATASETS)
    def test_generate_frames(self, fpath, rpath):
        """Test pixel_array returns correct values."""
        ds = dcmread(fpath)
        frame_generator = generate_frames(ds)
        ref = dcmread(rpath).pixel_array

        nr_frames = getattr(ds, 'NumberOfFrames', 1)
        for ii in range(nr_frames):
            arr = next(frame_generator)

            if nr_frames > 1:
                assert np.array_equal(arr, ref[ii, ...])
            else:
                assert np.array_equal(arr, ref)

        with pytest.raises(StopIteration):
            next(frame_generator)


@pytest.mark.skipif(not TEST_RLE, reason="no -rle plugin")
class TestRLEEncoding:
    def test_encode(self):
        """Test encoding"""
        ds = dcmread(EXPL)
        assert 'PlanarConfiguration' not in ds
        expected = get_expected_length(ds, 'bytes')
        assert expected == len(ds.PixelData)
        ref = ds.pixel_array
        del ds.PixelData
        del ds._pixel_array
        ds.compress(RLELossless, ref, encoding_plugin='pylibjpeg')
        assert expected > len(ds.PixelData)
        assert np.array_equal(ref, ds.pixel_array)
        assert ref is not ds.pixel_array

    def test_encode_bit(self):
        """Test encoding big-endian src"""
        ds = dcmread(IMPL)
        ref = ds.pixel_array
        del ds._pixel_array
        ds.compress(
            RLELossless,
            ds.PixelData,
            byteorder='>',
            encoding_plugin='pylibjpeg'
        )
        assert np.array_equal(ref.newbyteorder('>'), ds.pixel_array)
        assert ref is not ds.pixel_array
