# Copyright 2008-2018 pydicom authors. See LICENSE file for details.
"""Test suite for uid.py"""

from importlib import import_module
import sys
import uuid

import pytest

from pydicom._storage_sopclass_uids import CTImageStorage
from pydicom.uid import UID, generate_uid, PYDICOM_ROOT_UID, JPEGLSNearLossless
import pydicom.uid


def test_storage_sopclass_uids():
    assert CTImageStorage == pydicom.uid.CTImageStorage


def test_jpeglossless_warning():
    """Test warning when importing JPEGLossless for Python 3.7+."""
    if sys.version_info[:2] < (3, 7):
        from pydicom.uid import JPEGLossless
        assert '1.2.840.10008.1.2.4.70' == JPEGLossless
    else:
        msg = (
            r"In pydicom v3.0 the UID for 'JPEGLossless' will change "
            r"from '1.2.840.10008.1.2.4.70' to '1.2.840.10008.1.2.4.57' to "
            r"match its UID keyword. Use 'JPEGLosslessSV1' instead"
        )
        with pytest.warns(UserWarning, match=msg):
            from pydicom.uid import JPEGLossless
            assert '1.2.840.10008.1.2.4.70' == JPEGLossless


def test_deprecation_warnings():
    """Test deprecations warnings for other UIDs for Python 3.7+."""
    _deprecations = {
        "JPEGBaseline": ("1.2.840.10008.1.2.4.50", "JPEGBaseline8Bit"),
        "JPEGExtended": ("1.2.840.10008.1.2.4.51", "JPEGExtended12Bit"),
        "JPEGLSLossy": ("1.2.840.10008.1.2.4.81", "JPEGLSNearLossless"),
        "JPEG2000MultiComponentLossless": (
            "1.2.840.10008.1.2.4.92", "JPEG2000MCLossless"
        ),
        "JPEG2000MultiComponent": ("1.2.840.10008.1.2.4.93", "JPEG2000MC"),
    }

    if sys.version_info[:2] < (3, 7):
        for name, (value, replacement) in _deprecations.items():
            uid = getattr(pydicom.uid, name)

            assert value == uid
    else:
        for name, (value, replacement) in _deprecations.items():
            msg = (
                f"The UID constant '{name}' is deprecated and will be removed "
                f"in pydicom v3.0, use '{replacement}' instead"
            )
            with pytest.warns(DeprecationWarning, match=msg):
                uid = getattr(pydicom.uid, name)
                assert value == uid


class TestGenerateUID:
    def test_generate_uid(self):
        """Test UID generator"""
        # Test standard UID generation with pydicom prefix
        uid = generate_uid()
        assert uid[:26] == PYDICOM_ROOT_UID
        assert len(uid) <= 64

        # Test standard UID generation with no prefix
        uid = generate_uid(None)
        assert uid[:5] == '2.25.'
        assert len(uid) <= 64

        # Test invalid UID prefixes
        for invalid_prefix in (('1' * 63) + '.',
                               '',
                               '.',
                               '1',
                               '1.2',
                               '1.2..3.',
                               '1.a.2.',
                               '1.01.1.'):
            with pytest.raises(ValueError):
                generate_uid(prefix=invalid_prefix)

        # Test some valid prefixes and make sure they survive
        for valid_prefix in ('0.',
                             '1.',
                             '1.23.',
                             '1.0.23.',
                             ('1' * 62) + '.',
                             '1.2.3.444444.'):
            uid = generate_uid(prefix=valid_prefix)

            assert uid[:len(valid_prefix)] == valid_prefix
            assert len(uid) <= 64

    def test_entropy_src(self):
        """Test UID generator with default entropy sources"""
        # Should be different
        uid = generate_uid(entropy_srcs=None)
        assert uid != generate_uid(entropy_srcs=None)

    def test_entropy_src_custom(self):
        """Test UID generator with custom entropy sources"""
        # Should be identical
        uid = generate_uid(entropy_srcs=['lorem', 'ipsum'])
        rf = '1.2.826.0.1.3680043.8.498.87507166259346337659265156363895084463'
        assert uid == rf
        assert len(uid) == 64

    def test_none(self):
        """Test generate_uid(None)."""
        uid = generate_uid(prefix=None)
        # Check prefix
        assert '2.25.' == uid[:5]
        # Check UUID suffix
        as_uuid = uuid.UUID(int=int(uid[5:]))
        assert isinstance(as_uuid, uuid.UUID)
        assert as_uuid.version == 4
        assert as_uuid.variant == uuid.RFC_4122

    def test_none_iterate(self):
        """Test generate_uid(None) generates valid UIDs."""
        # Generate random UIDs, if a bad method then should eventually fail
        for ii in range(10000):
            uid = generate_uid(None)
            assert uid.is_valid


class TestUID:
    """Test DICOM UIDs"""
    def setup_method(self):
        """Set default UID"""
        self.uid = UID('1.2.840.10008.1.2')

    def test_equality(self):
        """Test that UID.__eq__ works."""
        assert self.uid == UID('1.2.840.10008.1.2')
        assert self.uid == '1.2.840.10008.1.2'
        assert '1.2.840.10008.1.2' == self.uid
        assert not self.uid == 'Implicit VR Little Endian'
        assert not 'Implicit VR Little Endian' == self.uid
        assert not self.uid == UID('1.2.840.10008.1.2.1')
        assert not self.uid == '1.2.840.10008.1.2.1'
        assert not '1.2.840.10008.1.2.1' == self.uid
        # Issue 96
        assert not self.uid == 3
        assert self.uid is not None

    def test_inequality(self):
        """Test that UID.__ne__ works."""
        assert not self.uid != UID('1.2.840.10008.1.2')
        assert not self.uid != '1.2.840.10008.1.2'
        assert not '1.2.840.10008.1.2' != self.uid
        assert self.uid != 'Implicit VR Little Endian'
        assert 'Implicit VR Little Endian' != self.uid
        assert self.uid != UID('1.2.840.10008.1.2.1')
        assert self.uid != '1.2.840.10008.1.2.1'
        assert '1.2.840.10008.1.2.1' != self.uid
        # Issue 96
        assert self.uid != 3

    def test_hash(self):
        """Test that UID.__hash_- works."""
        assert hash(self.uid) == hash(self.uid)

    def test_str(self):
        """Test that UID.__str__ works."""
        assert self.uid.__str__() == '1.2.840.10008.1.2'

    def test_is_implicit_vr(self):
        """Test that UID.is_implicit_VR works."""
        # '1.2.840.10008.1.2' Implicit VR Little Endian
        # '1.2.840.10008.1.2.1' Explicit VR Little Endian
        # '1.2.840.10008.1.2.1.99' Deflated Explicit VR Little Endian
        # '1.2.840.10008.1.2.2' Explicit VR Big Endian
        # '1.2.840.10008.1.2.4.50'JPEG Baseline (Process 1)
        assert UID('1.2.840.10008.1.2').is_implicit_VR
        assert not UID('1.2.840.10008.1.2.1').is_implicit_VR
        assert not UID('1.2.840.10008.1.2.1.99').is_implicit_VR
        assert not UID('1.2.840.10008.1.2.2').is_implicit_VR
        assert not UID('1.2.840.10008.1.2.4.50').is_implicit_VR

        with pytest.raises(ValueError):
            UID('1.2.840.10008.5.1.4.1.1.2').is_implicit_VR

    def test_is_little_endian(self):
        """Test that UID.is_little_endian works."""
        # '1.2.840.10008.1.2' Implicit VR Little Endian
        # '1.2.840.10008.1.2.1' Explicit VR Little Endian
        # '1.2.840.10008.1.2.1.99' Deflated Explicit VR Little Endian
        # '1.2.840.10008.1.2.2' Explicit VR Big Endian
        # '1.2.840.10008.1.2.4.50'JPEG Baseline (Process 1)
        assert UID('1.2.840.10008.1.2').is_little_endian
        assert UID('1.2.840.10008.1.2.1').is_little_endian
        assert UID('1.2.840.10008.1.2.1.99').is_little_endian
        assert not UID('1.2.840.10008.1.2.2').is_little_endian
        assert UID('1.2.840.10008.1.2.4.50').is_little_endian

        with pytest.raises(ValueError):
            UID('1.2.840.10008.5.1.4.1.1.2').is_little_endian

    def test_is_deflated(self):
        """Test that UID.is_deflated works."""
        # '1.2.840.10008.1.2' Implicit VR Little Endian
        # '1.2.840.10008.1.2.1' Explicit VR Little Endian
        # '1.2.840.10008.1.2.1.99' Deflated Explicit VR Little Endian
        # '1.2.840.10008.1.2.2' Explicit VR Big Endian
        # '1.2.840.10008.1.2.4.50'JPEG Baseline (Process 1)
        assert not UID('1.2.840.10008.1.2').is_deflated
        assert not UID('1.2.840.10008.1.2.1').is_deflated
        assert UID('1.2.840.10008.1.2.1.99').is_deflated
        assert not UID('1.2.840.10008.1.2.2').is_deflated
        assert not UID('1.2.840.10008.1.2.4.50').is_deflated

        with pytest.raises(ValueError):
            UID('1.2.840.10008.5.1.4.1.1.2').is_deflated

    def test_is_transfer_syntax(self):
        """Test that UID.is_transfer_syntax works."""
        # '1.2.840.10008.1.2' Implicit VR Little Endian
        # '1.2.840.10008.1.2.1' Explicit VR Little Endian
        # '1.2.840.10008.1.2.1.99' Deflated Explicit VR Little Endian
        # '1.2.840.10008.1.2.2' Explicit VR Big Endian
        # '1.2.840.10008.1.2.4.50'JPEG Baseline (Process 1)
        assert UID('1.2.840.10008.1.2').is_transfer_syntax
        assert UID('1.2.840.10008.1.2.1').is_transfer_syntax
        assert UID('1.2.840.10008.1.2.1.99').is_transfer_syntax
        assert UID('1.2.840.10008.1.2.2').is_transfer_syntax
        assert UID('1.2.840.10008.1.2.4.50').is_transfer_syntax

        assert not UID('1.2.840.10008.5.1.4.1.1.2').is_transfer_syntax

    def test_is_compressed(self):
        """Test that UID.is_compressed works."""
        # '1.2.840.10008.1.2' Implicit VR Little Endian
        # '1.2.840.10008.1.2.1' Explicit VR Little Endian
        # '1.2.840.10008.1.2.1.99' Deflated Explicit VR Little Endian
        # '1.2.840.10008.1.2.2' Explicit VR Big Endian
        # '1.2.840.10008.1.2.4.50'JPEG Baseline (Process 1)
        assert not UID('1.2.840.10008.1.2').is_compressed
        assert not UID('1.2.840.10008.1.2.1').is_compressed
        assert not UID('1.2.840.10008.1.2.1.99').is_compressed
        assert not UID('1.2.840.10008.1.2.2').is_compressed
        assert UID('1.2.840.10008.1.2.4.50').is_compressed

        with pytest.raises(ValueError):
            UID('1.2.840.10008.5.1.4.1.1.2').is_compressed

    def test_is_encapsulated(self):
        """Test that UID.is_encapsulated works."""
        # '1.2.840.10008.1.2' Implicit VR Little Endian
        # '1.2.840.10008.1.2.1' Explicit VR Little Endian
        # '1.2.840.10008.1.2.1.99' Deflated Explicit VR Little Endian
        # '1.2.840.10008.1.2.2' Explicit VR Big Endian
        # '1.2.840.10008.1.2.4.50'JPEG Baseline (Process 1)
        assert not UID('1.2.840.10008.1.2').is_encapsulated
        assert not UID('1.2.840.10008.1.2.1').is_encapsulated
        assert not UID('1.2.840.10008.1.2.1.99').is_encapsulated
        assert not UID('1.2.840.10008.1.2.2').is_encapsulated
        assert UID('1.2.840.10008.1.2.4.50').is_encapsulated

        with pytest.raises(ValueError):
            UID('1.2.840.10008.5.1.4.1.1.2').is_encapsulated

    def test_name(self):
        """Test that UID.name works."""
        assert self.uid.name == 'Implicit VR Little Endian'
        assert UID('1.2.840.10008.5.1.4.1.1.2').name == 'CT Image Storage'

    def test_name_with_equal_hash(self):
        """Test that UID name works for UID with same hash as predefined UID.
        """
        class MockedUID(UID):
            # Force the UID to return the same hash as one of the
            # uid dictionary entries (any will work).
            # The resulting hash collision forces the usage of the `eq`
            # operator while checking for containment in the uid dictionary
            # (regression test for issue #499)
            def __hash__(self):
                return hash(JPEGLSNearLossless)

        uid = MockedUID('1.2.3')
        assert uid.name == '1.2.3'

    def test_type(self):
        """Test that UID.type works."""
        assert self.uid.type == 'Transfer Syntax'
        assert UID('1.2.840.10008.5.1.4.1.1.2').type == 'SOP Class'

    def test_info(self):
        """Test that UID.info works."""
        assert self.uid.info == 'Default Transfer Syntax for DICOM'
        assert UID('1.2.840.10008.5.1.4.1.1.2').info == ''

    def test_is_retired(self):
        """Test that UID.is_retired works."""
        assert not self.uid.is_retired
        assert UID('1.2.840.10008.1.2.2').is_retired

    def test_is_valid(self, disable_value_validation):
        """Test that UID.is_valid works."""
        for invalid_uid in ('1' * 65,
                            '1.' + ('2' * 63),
                            '',
                            '.',
                            '1.',
                            '1.01',
                            '1.a.2'):
            assert not UID(invalid_uid).is_valid

        for valid_uid in ('0',
                          '1',
                          '0.1',
                          '1' * 64,
                          '1.' + ('2' * 62),
                          '1.0.23'):
            assert UID(valid_uid).is_valid

    def test_is_private(self):
        """Test the is_private property"""
        private_uid = UID('1.2.840.10009.1.2')
        assert private_uid.is_private
        assert not self.uid.is_private

    def test_raises(self):
        """Test raises exception if not a str type"""
        with pytest.raises(TypeError):
            UID(1234)

    def test_transitive(self):
        """Test for #256"""
        a = '1.2.840.10008.1.1'
        uid = UID(a)
        b = str(uid)
        assert uid.name == 'Verification SOP Class'
        assert uid == a
        assert uid == b
        assert a == b

    def test_keyword(self):
        """Test the keyword property."""
        assert "ImplicitVRLittleEndian" == self.uid.keyword


class TestUIDPrivate:
    """Test private UIDs"""
    def setup_method(self):
        """Set default UID"""
        self.uid = UID('9.9.999.90009.1.2')

    def test_equality(self):
        """Test that UID.__eq__ works with private UIDs."""
        assert self.uid == UID('9.9.999.90009.1.2')
        assert self.uid == '9.9.999.90009.1.2'
        assert not self.uid == UID('9.9.999.90009.1.3')
        assert not self.uid == '9.9.999.90009.1.3'

    def test_inequality(self):
        """Test that UID.__ne__ works with private UIDs."""
        assert not self.uid != UID('9.9.999.90009.1.2')
        assert not self.uid != '9.9.999.90009.1.2'
        assert self.uid != UID('9.9.999.90009.1.3')
        assert self.uid != '9.9.999.90009.1.3'

    def test_hash(self):
        """Test that UID.__hash_- works with private UIDs."""
        assert hash(self.uid) == hash(self.uid)

    def test_str(self):
        """Test that UID.__str__ works with private UIDs."""
        assert self.uid.__str__() == '9.9.999.90009.1.2'

    def test_is_implicit_vr(self):
        """Test that UID.is_implicit_VR works with private UIDs."""
        with pytest.raises(ValueError):
            self.uid.is_implicit_VR

    def test_is_little_endian(self):
        """Test that UID.is_little_endian works with private UIDs."""
        with pytest.raises(ValueError):
            self.uid.is_little_endian

    def test_is_deflated(self):
        """Test that UID.is_deflated works with private UIDs."""
        with pytest.raises(ValueError):
            self.uid.is_deflated

    def test_is_transfer_syntax(self):
        """Test that UID.is_transfer_syntax works with private UIDs."""
        with pytest.raises(ValueError):
            self.uid.is_transfer_syntax

    def test_is_compressed(self):
        """Test that UID.is_compressed works with private UIDs."""
        with pytest.raises(ValueError):
            self.uid.is_compressed

    def test_is_encapsulated(self):
        """Test that UID.is_encapsulated works with private UIDs."""
        with pytest.raises(ValueError):
            self.uid.is_encapsulated

    def test_name(self):
        """Test that UID.name works with private UIDs."""
        assert self.uid.name == '9.9.999.90009.1.2'

    def test_type(self):
        """Test that UID.type works with private UIDs."""
        assert self.uid.type == ''

    def test_info(self):
        """Test that UID.info works with private UIDs."""
        assert self.uid.info == ''

    def test_is_retired(self):
        """Test that UID.is_retired works with private UIDs."""
        assert not self.uid.is_retired

    def test_is_valid(self):
        """Test that UID.is_valid works with private UIDs."""
        assert self.uid.is_valid

    def test_is_private(self):
        """Test that UID.is_private works with private UIDs."""
        assert self.uid.is_private

    def test_keyword(self):
        """Test the keyword property."""
        assert "" == self.uid.keyword
