# -*- coding: utf-8 -*-
# Copyright 2008-2018 pydicom authors. See LICENSE file for details.
"""Unit tests for the pydicom.dataelem module."""

# Many tests of DataElement class are implied in test_dataset also
import math

import pytest

from pydicom import filewriter, config, dcmread
from pydicom.charset import default_encoding
from pydicom.data import get_testdata_file
from pydicom.datadict import add_private_dict_entry
from pydicom.dataelem import (
    DataElement,
    RawDataElement,
    DataElement_from_raw,
)
from pydicom.dataset import Dataset
from pydicom.errors import BytesLengthException
from pydicom.filebase import DicomBytesIO
from pydicom.multival import MultiValue
from pydicom.tag import Tag, BaseTag
from pydicom.uid import UID
from pydicom.valuerep import DSfloat


class TestDataElement:
    """Tests for dataelem.DataElement."""
    def setup(self):
        self.data_elementSH = DataElement((1, 2), "SH", "hello")
        self.data_elementIS = DataElement((1, 2), "IS", "42")
        self.data_elementDS = DataElement((1, 2), "DS", "42.00001")
        self.data_elementMulti = DataElement((1, 2), "DS",
                                             ['42.1', '42.2', '42.3'])
        self.data_elementCommand = DataElement(0x00000000, 'UL', 100)
        self.data_elementPrivate = DataElement(0x00090000, 'UL', 101)
        self.data_elementRetired = DataElement(0x00080010, 'SH', 102)
        config.use_none_as_empty_text_VR_value = False

    def teardown(self):
        config.use_none_as_empty_text_VR_value = False

    @pytest.fixture
    def replace_un_with_known_vr(self):
        old_value = config.replace_un_with_known_vr
        config.replace_un_with_known_vr = True
        yield
        config.replace_un_with_known_vr = old_value

    def test_AT(self):
        """VR of AT takes Tag variants when set"""
        elem1 = DataElement("OffendingElement", "AT", 0x100010)
        elem2 = DataElement("OffendingElement", "AT", (0x10, 0x10))
        elem3 = DataElement(
            "FrameIncrementPointer", "AT", [0x540010, 0x540020]
        )
        elem4 = DataElement("OffendingElement", "AT", "PatientName")
        assert isinstance(elem1.value, BaseTag)
        assert isinstance(elem2.value, BaseTag)
        assert elem1.value == elem2.value == elem4.value
        assert elem1.value == 0x100010
        assert isinstance(elem3.value, MultiValue)
        assert len(elem3.value) == 2

        # Test also using Dataset, and check 0x00000000 works
        ds = Dataset()
        ds.OffendingElement = 0
        assert isinstance(ds.OffendingElement, BaseTag)
        ds.OffendingElement = (0x0000, 0x0000)
        assert isinstance(ds.OffendingElement, BaseTag)
        assert ds.OffendingElement == 0

        # An invalid Tag should throw an error
        with pytest.raises(OverflowError):
            _ = DataElement("OffendingElement", "AT", 0x100000000)

    def test_VM_1(self):
        """DataElement: return correct value multiplicity for VM > 1"""
        assert 3 == self.data_elementMulti.VM

    def test_VM_2(self):
        """DataElement: return correct value multiplicity for VM = 1"""
        assert 1 == self.data_elementIS.VM

    def test_DSFloat_conversion(self):
        """Test that strings are correctly converted if changing the value."""
        assert isinstance(self.data_elementDS.value, DSfloat)
        assert isinstance(self.data_elementMulti.value[0], DSfloat)
        assert DSfloat('42.1') == self.data_elementMulti.value[0]

        # multi-value append/insert
        self.data_elementMulti.value.append('42.4')
        assert isinstance(self.data_elementMulti.value[3], DSfloat)
        assert DSfloat('42.4') == self.data_elementMulti.value[3]

        self.data_elementMulti.value.insert(0, '42.0')
        assert isinstance(self.data_elementMulti.value[0], DSfloat)
        assert DSfloat('42.0') == self.data_elementMulti.value[0]

        # change single value of multi-value
        self.data_elementMulti.value[3] = '123.4'
        assert isinstance(self.data_elementMulti.value[3], DSfloat)
        assert DSfloat('123.4') == self.data_elementMulti.value[3]

    def test_DSFloat_conversion_auto_format(self):
        """Test that strings are being auto-formatted correctly."""
        data_element = DataElement((1, 2), "DS",
                                   DSfloat(math.pi, auto_format=True))
        assert math.pi == data_element.value
        assert '3.14159265358979' == str(data_element.value)

    def test_backslash(self):
        """DataElement: String with '\\' sets multi-valued data_element."""
        data_element = DataElement((1, 2), "DS", r"42.1\42.2\42.3")
        assert 3 == data_element.VM

    def test_UID(self):
        """DataElement: setting or changing UID results in UID type."""
        ds = Dataset()
        ds.TransferSyntaxUID = "1.2.3"
        assert isinstance(ds.TransferSyntaxUID, UID)
        ds.TransferSyntaxUID += ".4.5.6"
        assert isinstance(ds.TransferSyntaxUID, UID)

    def test_keyword(self):
        """DataElement: return correct keyword"""
        assert 'CommandGroupLength' == self.data_elementCommand.keyword
        assert '' == self.data_elementPrivate.keyword

    def test_retired(self):
        """DataElement: return correct is_retired"""
        assert self.data_elementCommand.is_retired is False
        assert self.data_elementRetired.is_retired is True
        assert self.data_elementPrivate.is_retired is False

    def test_description_group_length(self):
        """Test DataElement.description for Group Length element"""
        elem = DataElement(0x00100000, 'LO', 12345)
        assert 'Group Length' == elem.description()

    def test_description_unknown_private(self):
        """Test DataElement.description with an unknown private element"""
        elem = DataElement(0x00110010, 'LO', 12345)
        elem.private_creator = 'TEST'
        assert 'Private tag data' == elem.description()
        elem = DataElement(0x00110F00, 'LO', 12345)
        assert elem.tag.is_private
        assert elem.private_creator is None
        assert 'Private tag data' == elem.description()

    def test_description_unknown(self):
        """Test DataElement.description with an unknown element"""
        elem = DataElement(0x00000004, 'LO', 12345)
        assert '' == elem.description()

    def test_equality_standard_element(self):
        """DataElement: equality returns correct value for simple elements"""
        dd = DataElement(0x00100010, 'PN', 'ANON')
        assert dd == dd
        ee = DataElement(0x00100010, 'PN', 'ANON')
        assert dd == ee

        # Check value
        ee.value = 'ANAN'
        assert not dd == ee

        # Check tag
        ee = DataElement(0x00100011, 'PN', 'ANON')
        assert not dd == ee

        # Check VR
        ee = DataElement(0x00100010, 'SH', 'ANON')
        assert not dd == ee

        dd = DataElement(0x00080018, 'UI', '1.2.3.4')
        ee = DataElement(0x00080018, 'UI', '1.2.3.4')
        assert dd == ee

        ee = DataElement(0x00080018, 'PN', '1.2.3.4')
        assert not dd == ee

    def test_equality_private_element(self):
        """DataElement: equality returns correct value for private elements"""
        dd = DataElement(0x01110001, 'PN', 'ANON')
        assert dd == dd
        ee = DataElement(0x01110001, 'PN', 'ANON')
        assert dd == ee

        # Check value
        ee.value = 'ANAN'
        assert not dd == ee

        # Check tag
        ee = DataElement(0x01110002, 'PN', 'ANON')
        assert not dd == ee

        # Check VR
        ee = DataElement(0x01110001, 'SH', 'ANON')
        assert not dd == ee

    def test_equality_sequence_element(self):
        """DataElement: equality returns correct value for sequence elements"""
        dd = DataElement(0x300A00B0, 'SQ', [])
        assert dd == dd
        ee = DataElement(0x300A00B0, 'SQ', [])
        assert dd == ee

        # Check value
        e = Dataset()
        e.PatientName = 'ANON'
        ee.value = [e]
        assert not dd == ee

        # Check tag
        ee = DataElement(0x01110002, 'SQ', [])
        assert not dd == ee

        # Check VR
        ee = DataElement(0x300A00B0, 'SH', [])
        assert not dd == ee

        # Check with dataset
        dd = DataElement(0x300A00B0, 'SQ', [Dataset()])
        dd.value[0].PatientName = 'ANON'
        ee = DataElement(0x300A00B0, 'SQ', [Dataset()])
        ee.value[0].PatientName = 'ANON'
        assert dd == ee

        # Check uneven sequences
        dd.value.append(Dataset())
        dd.value[1].PatientName = 'ANON'
        assert not dd == ee

        ee.value.append(Dataset())
        ee.value[1].PatientName = 'ANON'
        assert dd == ee
        ee.value.append(Dataset())
        ee.value[2].PatientName = 'ANON'
        assert not dd == ee

    def test_equality_not_rlement(self):
        """DataElement: equality returns correct value when not same class"""
        dd = DataElement(0x00100010, 'PN', 'ANON')
        ee = {'0x00100010': 'ANON'}
        assert not dd == ee

    def test_equality_inheritance(self):
        """DataElement: equality returns correct value for subclasses"""

        class DataElementPlus(DataElement):
            pass

        dd = DataElement(0x00100010, 'PN', 'ANON')
        ee = DataElementPlus(0x00100010, 'PN', 'ANON')
        assert ee == ee
        assert dd == ee
        assert ee == dd

        ee = DataElementPlus(0x00100010, 'PN', 'ANONY')
        assert not dd == ee
        assert not ee == dd

    def test_equality_class_members(self):
        """Test equality is correct when ignored class members differ."""
        dd = DataElement(0x00100010, 'PN', 'ANON')
        dd.showVR = False
        dd.file_tell = 10
        dd.maxBytesToDisplay = 0
        dd.descripWidth = 0
        assert DataElement(0x00100010, 'PN', 'ANON') == dd

    def test_inequality_standard(self):
        """Test DataElement.__ne__ for standard element"""
        dd = DataElement(0x00100010, 'PN', 'ANON')
        assert not dd != dd
        assert DataElement(0x00100010, 'PN', 'ANONA') != dd

        # Check tag
        assert DataElement(0x00100011, 'PN', 'ANON') != dd

        # Check VR
        assert DataElement(0x00100010, 'SH', 'ANON') != dd

    def test_inequality_sequence(self):
        """Test DataElement.__ne__ for sequence element"""
        dd = DataElement(0x300A00B0, 'SQ', [])
        assert not dd != dd
        assert not DataElement(0x300A00B0, 'SQ', []) != dd
        ee = DataElement(0x300A00B0, 'SQ', [Dataset()])
        assert ee != dd

        # Check value
        dd.value = [Dataset()]
        dd[0].PatientName = 'ANON'
        ee[0].PatientName = 'ANON'
        assert not ee != dd
        ee[0].PatientName = 'ANONA'
        assert ee != dd

    def test_hash(self):
        """Test hash(DataElement) raises TypeError"""
        with pytest.raises(TypeError, match=r"unhashable"):
            hash(DataElement(0x00100010, 'PN', 'ANON'))

    def test_repeater_str(self):
        """Test a repeater group element displays the element name."""
        elem = DataElement(0x60023000, 'OB', b'\x00')
        assert 'Overlay Data' in elem.__str__()

    def test_str_no_vr(self):
        """Test DataElement.__str__ output with no VR"""
        elem = DataElement(0x00100010, 'PN', 'ANON')
        assert "(0010, 0010) Patient's Name" in str(elem)
        assert "PN: 'ANON'" in str(elem)
        elem.showVR = False
        assert "(0010, 0010) Patient's Name" in str(elem)
        assert 'PN' not in str(elem)

    def test_repr_seq(self):
        """Test DataElement.__repr__ with a sequence"""
        elem = DataElement(0x300A00B0, 'SQ', [Dataset()])
        elem[0].PatientID = '1234'
        assert repr(elem) == repr(elem.value)

    def test_getitem_raises(self):
        """Test DataElement.__getitem__ raise if value not indexable"""
        elem = DataElement(0x00100010, 'LO', 12345)
        with pytest.raises(TypeError):
            elem[0]

    def test_repval_large_elem(self):
        """Test DataElement.repval doesn't return a huge string for a large
        value"""
        elem = DataElement(0x00820003, 'UT', 'a' * 1000)
        assert len(elem.repval) < 100

    def test_repval_large_vm(self):
        """Test DataElement.repval doesn't return a huge string for a large
        vm"""
        elem = DataElement(0x00080054, 'AE', 'a\\' * 1000 + 'a')
        assert len(elem.repval) < 100

    def test_repval_strange_type(self):
        """Test DataElement.repval doesn't break with bad types"""
        elem = DataElement(0x00020001, 'OB', 0)
        assert len(elem.repval) < 100

    def test_private_tag_in_repeater_range(self):
        """Test that an unknown private tag (e.g. a tag not in the private
        dictionary) in the repeater range is not handled as a repeater tag
        if using Implicit Little Endian transfer syntax."""
        # regression test for #689
        ds = Dataset()
        ds[0x50f10010] = RawDataElement(
            Tag(0x50f10010), None, 8, b'FDMS 1.0', 0, True, True)
        ds[0x50f1100a] = RawDataElement(
            Tag(0x50f1100a), None, 6, b'ACC0.6', 0, True, True)
        private_creator_data_elem = ds[0x50f10010]
        assert 'Private Creator' == private_creator_data_elem.name
        assert 'LO' == private_creator_data_elem.VR

        private_data_elem = ds[0x50f1100a]
        assert '[FNC Parameters]' == private_data_elem.name
        assert 'SH' == private_data_elem.VR

    def test_private_repeater_tag(self):
        """Test that a known private tag in the repeater range is correctly
        handled using Implicit Little Endian transfer syntax."""
        ds = Dataset()
        ds[0x60210012] = RawDataElement(
            Tag(0x60210012), None, 12, b'PAPYRUS 3.0 ', 0, True, True)
        ds[0x60211200] = RawDataElement(
            Tag(0x60211200), None, 6, b'123456', 0, True, True)
        private_creator_data_elem = ds[0x60210012]
        assert 'Private Creator' == private_creator_data_elem.name
        assert 'LO' == private_creator_data_elem.VR

        private_data_elem = ds[0x60211200]
        assert '[Overlay ID]' == private_data_elem.name
        assert 'IS' == private_data_elem.VR

    def test_known_tags_with_UN_VR(self, replace_un_with_known_vr):
        """Known tags with VR UN are correctly decoded."""
        ds = Dataset()
        ds[0x00080005] = DataElement(0x00080005, 'UN', b'ISO_IR 126')
        ds[0x00100010] = DataElement(0x00100010, 'UN',
                                     'Διονυσιος'.encode('iso_ir_126'))
        ds.decode()
        assert 'CS' == ds[0x00080005].VR
        assert 'PN' == ds[0x00100010].VR
        assert 'Διονυσιος' == ds[0x00100010].value

        ds = Dataset()
        ds[0x00080005] = DataElement(0x00080005, 'UN',
                                     b'ISO 2022 IR 100\\ISO 2022 IR 126')
        ds[0x00100010] = DataElement(0x00100010, 'UN',
                                     b'Dionysios=\x1b\x2d\x46'
                                     + 'Διονυσιος'.encode('iso_ir_126'))
        ds.decode()
        assert 'CS' == ds[0x00080005].VR
        assert 'PN' == ds[0x00100010].VR
        assert 'Dionysios=Διονυσιος' == ds[0x00100010].value

    def test_reading_ds_with_known_tags_with_UN_VR(
            self, replace_un_with_known_vr):
        """Known tags with VR UN are correctly read."""
        test_file = get_testdata_file('explicit_VR-UN.dcm')
        ds = dcmread(test_file)
        assert 'CS' == ds[0x00080005].VR
        assert 'TM' == ds[0x00080030].VR
        assert 'PN' == ds[0x00100010].VR
        assert 'PN' == ds[0x00100010].VR
        assert 'DA' == ds[0x00100030].VR

    def test_unknown_tags_with_UN_VR(self):
        """Unknown tags with VR UN are not decoded."""
        ds = Dataset()
        ds[0x00080005] = DataElement(0x00080005, 'CS', b'ISO_IR 126')
        ds[0x00111010] = DataElement(0x00111010, 'UN',
                                     'Διονυσιος'.encode('iso_ir_126'))
        ds.decode()
        assert 'UN' == ds[0x00111010].VR
        assert 'Διονυσιος'.encode('iso_ir_126') == ds[0x00111010].value

    def test_tag_with_long_value_UN_VR(self):
        """Tag with length > 64kb with VR UN is not changed."""
        ds = Dataset()
        ds[0x00080005] = DataElement(0x00080005, 'CS', b'ISO_IR 126')

        single_value = b'123456.789012345'
        large_value = b'\\'.join([single_value] * 4500)
        ds[0x30040058] = DataElement(0x30040058, 'UN',
                                     large_value,
                                     is_undefined_length=False)
        ds.decode()
        assert 'UN' == ds[0x30040058].VR

    @pytest.mark.parametrize('use_none, empty_value',
                             ((True, None), (False, '')))
    def test_empty_text_values(self, use_none, empty_value,
                               no_datetime_conversion):
        """Test that assigning an empty value behaves as expected."""
        def check_empty_text_element(value):
            setattr(ds, tag_name, value)
            elem = ds[tag_name]
            assert bool(elem.value) is False
            assert 0 == elem.VM
            assert elem.value == value
            fp = DicomBytesIO()
            filewriter.write_dataset(fp, ds)
            ds_read = dcmread(fp, force=True)
            assert empty_value == ds_read[tag_name].value

        text_vrs = {
            'AE': 'RetrieveAETitle',
            'AS': 'PatientAge',
            'CS': 'QualityControlSubject',
            'DA': 'PatientBirthDate',
            'DT': 'AcquisitionDateTime',
            'LO': 'DataSetSubtype',
            'LT': 'ExtendedCodeMeaning',
            'PN': 'PatientName',
            'SH': 'CodeValue',
            'ST': 'InstitutionAddress',
            'TM': 'StudyTime',
            'UC': 'LongCodeValue',
            'UI': 'SOPClassUID',
            'UR': 'CodingSchemeURL',
            'UT': 'StrainAdditionalInformation',
        }
        config.use_none_as_empty_text_VR_value = use_none
        ds = Dataset()
        ds.is_little_endian = True
        # set value to new element
        for tag_name in text_vrs.values():
            check_empty_text_element(None)
            del ds[tag_name]
            check_empty_text_element('')
            del ds[tag_name]
            check_empty_text_element([])
            del ds[tag_name]

        # set value to existing element
        for tag_name in text_vrs.values():
            check_empty_text_element(None)
            check_empty_text_element('')
            check_empty_text_element([])
            check_empty_text_element(None)

    def test_empty_binary_values(self):
        """Test that assigning an empty value behaves as expected for
        non-text VRs."""
        def check_empty_binary_element(value):
            setattr(ds, tag_name, value)
            elem = ds[tag_name]
            assert bool(elem.value) is False
            assert 0 == elem.VM
            assert elem.value == value
            fp = DicomBytesIO()
            filewriter.write_dataset(fp, ds)
            ds_read = dcmread(fp, force=True)
            assert ds_read[tag_name].value is None

        non_text_vrs = {
            'AT': 'OffendingElement',
            'DS': 'PatientWeight',
            'IS': 'BeamNumber',
            'SL': 'RationalNumeratorValue',
            'SS': 'SelectorSSValue',
            'UL': 'SimpleFrameList',
            'US': 'SourceAcquisitionBeamNumber',
            'FD': 'RealWorldValueLUTData',
            'FL': 'VectorAccuracy',
            'OB': 'FillPattern',
            'OD': 'DoubleFloatPixelData',
            'OF': 'UValueData',
            'OL': 'TrackPointIndexList',
            'OW': 'TrianglePointIndexList',
            'UN': 'SelectorUNValue',
        }
        ds = Dataset()
        ds.is_little_endian = True
        # set value to new element
        for tag_name in non_text_vrs.values():
            check_empty_binary_element(None)
            del ds[tag_name]
            check_empty_binary_element([])
            del ds[tag_name]
            check_empty_binary_element(MultiValue(int, []))
            del ds[tag_name]

        # set value to existing element
        for tag_name in non_text_vrs.values():
            check_empty_binary_element(None)
            check_empty_binary_element([])
            check_empty_binary_element(MultiValue(int, []))
            check_empty_binary_element(None)

    def test_empty_sequence_is_handled_as_array(self):
        ds = Dataset()
        ds.AcquisitionContextSequence = []
        elem = ds['AcquisitionContextSequence']
        assert bool(elem.value) is False
        assert 0 == elem.VM
        assert elem.value == []

        fp = DicomBytesIO()
        fp.is_little_endian = True
        fp.is_implicit_VR = True
        filewriter.write_dataset(fp, ds)
        ds_read = dcmread(fp, force=True)
        elem = ds_read['AcquisitionContextSequence']
        assert 0 == elem.VM
        assert elem.value == []

    def test_is_private(self):
        """Test the is_private property."""
        elem = DataElement(0x00090010, 'UN', None)
        assert elem.is_private
        elem = DataElement(0x00080010, 'UN', None)
        assert not elem.is_private


class TestRawDataElement:

    """Tests for dataelem.RawDataElement."""
    def test_invalid_tag_warning(self, allow_invalid_values):
        """RawDataElement: conversion of unknown tag warns..."""
        raw = RawDataElement(Tag(0x88880088), None, 4, b'unknown',
                             0, True, True)

        with pytest.warns(UserWarning, match=r"\(8888, 0088\)"):
            element = DataElement_from_raw(raw)
            assert element.VR == 'UN'

    def test_key_error(self, enforce_valid_values):
        """RawDataElement: conversion of unknown tag throws KeyError..."""
        # raw data element -> tag VR length value
        #                       value_tell is_implicit_VR is_little_endian'
        # Unknown (not in DICOM dict), non-private, non-group 0 for this test
        raw = RawDataElement(Tag(0x88880002), None, 4, b'unknown',
                             0, True, True)

        with pytest.raises(KeyError, match=r"\(8888, 0002\)"):
            DataElement_from_raw(raw)

    def test_valid_tag(self, no_datetime_conversion):
        """RawDataElement: conversion of known tag succeeds..."""
        raw = RawDataElement(Tag(0x00080020), 'DA', 8, b'20170101',
                             0, False, True)
        element = DataElement_from_raw(raw, default_encoding)
        assert 'Study Date' == element.name
        assert 'DA' == element.VR
        assert '20170101' == element.value

        raw = RawDataElement(Tag(0x00080000), None, 4, b'\x02\x00\x00\x00',
                             0, True, True)
        elem = DataElement_from_raw(raw, default_encoding)
        assert 'UL' == elem.VR

    def test_data_element_without_encoding(self):
        """RawDataElement: no encoding needed."""
        raw = RawDataElement(Tag(0x00104000), 'LT', 23,
                             b'comment\\comment2\\comment3',
                             0, False, True)
        element = DataElement_from_raw(raw)
        assert 'Patient Comments' == element.name

    def test_unknown_vr(self):
        """Test converting a raw element with unknown VR"""
        raw = RawDataElement(Tag(0x00080000), 'AA', 8, b'20170101',
                             0, False, True)
        with pytest.raises(NotImplementedError):
            DataElement_from_raw(raw, default_encoding)

    @pytest.fixture
    def accept_wrong_length(self, request):
        old_value = config.convert_wrong_length_to_UN
        config.convert_wrong_length_to_UN = request.param
        yield
        config.convert_wrong_length_to_UN = old_value

    @pytest.mark.parametrize("accept_wrong_length", [False], indirect=True)
    def test_wrong_bytes_length_exception(self, accept_wrong_length):
        """Check exception when number of raw bytes is not correct."""
        raw = RawDataElement(Tag(0x00190000), 'FD', 1, b'1', 0, False, True)
        with pytest.raises(BytesLengthException):
            DataElement_from_raw(raw)

    @pytest.mark.parametrize("accept_wrong_length", [True], indirect=True)
    def test_wrong_bytes_length_convert_to_UN(self, accept_wrong_length):
        """Check warning and behavior for incorrect number of raw bytes."""
        value = b'1'
        raw = RawDataElement(Tag(0x00190000), 'FD', 1, value, 0, False, True)
        msg = (
            r"Expected total bytes to be an even multiple of bytes per value. "
            r"Instead received b'1' with length 1 and struct format 'd' which "
            r"corresponds to bytes per value of 8. This occurred while trying "
            r"to parse \(0019, 0000\) according to VR 'FD'. "
            r"Setting VR to 'UN'."
        )
        with pytest.warns(UserWarning, match=msg):
            raw_elem = DataElement_from_raw(raw)
            assert 'UN' == raw_elem.VR
            assert value == raw_elem.value

    def test_read_known_private_tag_implicit(self):
        fp = DicomBytesIO()
        ds = Dataset()
        ds.is_implicit_VR = True
        ds.is_little_endian = True
        ds[0x00410010] = RawDataElement(
            Tag(0x00410010), "LO", 8, b"ACME 3.2", 0, True, True)
        ds[0x00411001] = RawDataElement(
            Tag(0x00411001), "US", 2, b"\x2A\x00", 0, True, True)
        ds[0x00431001] = RawDataElement(
            Tag(0x00431001), "SH", 8, b"Unknown ", 0, True, True)
        ds.save_as(fp)
        ds = dcmread(fp, force=True)
        elem = ds[0x00411001]
        assert elem.VR == "UN"
        assert elem.name == "Private tag data"
        assert elem.value == b"\x2A\x00"

        add_private_dict_entry("ACME 3.2", 0x00410001, "US", "Some Number")
        ds = dcmread(fp, force=True)
        elem = ds[0x00411001]
        assert elem.VR == "US"
        assert elem.name == "[Some Number]"
        assert elem.value == 42

        # Unknown private tag is handled as before
        elem = ds[0x00431001]
        assert elem.VR == "UN"
        assert elem.name == "Private tag data"
        assert elem.value == b"Unknown "

    def test_read_known_private_tag_explicit(self):
        fp = DicomBytesIO()
        ds = Dataset()
        ds.is_implicit_VR = False
        ds.is_little_endian = True
        ds[0x00410010] = RawDataElement(
            Tag(0x00410010), "LO", 8, b"ACME 3.2", 0, False, True)
        ds[0x00411002] = RawDataElement(
            Tag(0x00411002), "UN", 8, b"SOME_AET", 0, False, True)
        ds.save_as(fp)
        ds = dcmread(fp, force=True)
        elem = ds[0x00411002]
        assert elem.VR == "UN"
        assert elem.name == "Private tag data"
        assert elem.value == b"SOME_AET"

        add_private_dict_entry("ACME 3.2", 0x00410002, "AE", "Some AET")
        ds = dcmread(fp, force=True)
        elem = ds[0x00411002]
        assert elem.VR == "AE"
        assert elem.name == "[Some AET]"
        assert elem.value == "SOME_AET"

    def test_read_known_private_tag_explicit_no_lookup(
            self, dont_replace_un_with_known_vr):
        add_private_dict_entry("ACME 3.2", 0x00410003, "IS", "Another Number")
        fp = DicomBytesIO()
        ds = Dataset()
        ds.is_implicit_VR = False
        ds.is_little_endian = True
        ds[0x00410010] = RawDataElement(
            Tag(0x00410010), "LO", 8, b"ACME 3.2", 0, False, True)
        ds[0x00411003] = RawDataElement(
            Tag(0x00411003), "UN", 8, b"12345678", 0, False, True)
        ds.save_as(fp)
        ds = dcmread(fp, force=True)
        elem = ds[0x00411003]
        assert elem.VR == "UN"
        assert elem.name == "[Another Number]"
        assert elem.value == b"12345678"
