# Created: 30.04.2011, 2018 rewritten for pytest
# Copyright (C) 2011-2019, Manfred Moitzi
# License: MIT License
import pytest
from io import StringIO

from ezdxf.lldxf.tags import Tags, DXFTag
from ezdxf.lldxf.extendedtags import ExtendedTags
from ezdxf import DXFKeyError, DXFValueError
from ezdxf.lldxf.tagwriter import TagWriter


@pytest.fixture
def xtags1():
    return ExtendedTags.from_text(XTAGS1)


def test_init_appdata(xtags1):
    assert xtags1.get_app_data('{ACAD_XDICTIONARY') is not None


def test_init_with_tags():
    tags = Tags.from_text(XTAGS1)
    xtags = ExtendedTags(tags)
    assert 3 == len(xtags.subclasses)
    assert 1 == len(xtags.xdata)


def test_init_xdata(xtags1):
    assert xtags1.get_xdata('RAK') is not None


def test_init_one_tag():
    xtags = ExtendedTags([DXFTag(0, 'SECTION')])
    assert xtags.noclass[0] == (0, 'SECTION')


def test_getitem(xtags1):
    assert xtags1[0] == xtags1.noclass[0]


def test_appdata_content_count(xtags1):
    xdict = xtags1.get_app_data('{ACAD_XDICTIONARY')
    assert 3 == len(xdict)


def test_appdata_content(xtags1):
    xdict = xtags1.get_app_data('{ACAD_XDICTIONARY')
    assert xdict.get_first_value(360) == "63D5"


def test_tags_skips_appdata_content(xtags1):
    with pytest.raises(DXFValueError):
        xtags1.noclass.get_first_value(360)


def test_xdata_content_count(xtags1):
    rak = xtags1.get_xdata('RAK')
    assert 17 == len(rak)


def test_tags_skips_xdata_content(xtags1):
    with pytest.raises(DXFValueError):
        xtags1.noclass.get_first_value(1000)


def test_copy(xtags1):
    stream = StringIO()
    tagwriter = TagWriter(stream)
    tagwriter.write_tags(xtags1)
    assert XTAGS1 == stream.getvalue()
    stream.close()


def test_getitem_layer(xtags1):
    assert xtags1.noclass[0] == (0, 'LAYER')


def test_getitem_xdict(xtags1):
    assert xtags1.noclass[2] == (102, 0)


def test_getitem_parent(xtags1):
    assert xtags1.noclass[3] == (330, '18')


def test_get_last_item(xtags1):
    assert xtags1.noclass[-1] == (330, '18')


def test_tagscount(xtags1):
    """ apdata counts as one tag and xdata counts as one tag. """
    assert 4 == len(xtags1.noclass)


def test_subclass_AcDbSymbolTableRecord(xtags1):
    subclass = xtags1.get_subclass('AcDbSymbolTableRecord')
    assert 1 == len(subclass)


def test_subclass_AcDbLayerTableRecord(xtags1):
    subclass = xtags1.get_subclass('AcDbLayerTableRecord')
    assert 8 == len(subclass)


def test_clone_is_equal(xtags1):
    clone = xtags1.clone()
    assert xtags1 is not clone
    assert xtags1.appdata is not clone.appdata
    assert xtags1.subclasses is not clone.subclasses
    assert xtags1.xdata is not clone.xdata
    assert list(xtags1) == list(clone)


def test_replace_handle(xtags1):
    xtags1.replace_handle('AA')
    assert 'AA' == xtags1.get_handle()


XTAGS1 = """  0
LAYER
  5
7
102
{ACAD_XDICTIONARY
360
63D5
102
}
330
18
100
AcDbSymbolTableRecord
100
AcDbLayerTableRecord
  2
0
 70
0
 62
7
  6
CONTINUOUS
370
-3
390
8
347
805
1001
RAK
1000
{75-LÄNGENSCHNITT-14
1070
0
1070
7
1000
CONTINUOUS
1071
-3
1071
1
1005
8
1000
75-LÄNGENSCHNITT-14}
1000
{75-LÄNGENSCHNITT-2005
1070
0
1070
7
1000
CONTINUOUS
1071
-3
1071
1
1005
8
1000
75-LÄNGENSCHNITT-2005}
"""


@pytest.fixture
def xtags2():
    return ExtendedTags.from_text(XTAGS2)


def test_xdata_count(xtags2):
    assert 3 == len(xtags2.xdata)


def test_tags_count(xtags2):
    """ 3 xdata chunks and two 'normal' tag. """
    assert 2 == len(xtags2.noclass)


def test_xdata3_tags(xtags2):
    xdata = xtags2.get_xdata('XDATA3')
    assert xdata[0] == (1001, 'XDATA3')
    assert xdata[1] == (1000, 'TEXT-XDATA3')
    assert xdata[2] == (1070, 2)
    assert xdata[3] == (1070, 3)


def test_new_data(xtags2):
    xtags2.new_xdata('NEWXDATA', [(1000, 'TEXT')])
    assert xtags2.has_xdata('NEWXDATA') is True

    xdata = xtags2.get_xdata('NEWXDATA')
    assert xdata[0] == (1001, 'NEWXDATA')
    assert xdata[1] == (1000, 'TEXT')


def test_set_new_data(xtags2):
    xtags2.new_xdata('NEWXDATA', tags=[(1000, "Extended Data String")])
    assert xtags2.has_xdata('NEWXDATA') is True

    xdata = xtags2.get_xdata('NEWXDATA')
    assert (1001, 'NEWXDATA') == xdata[0]
    assert (1000, "Extended Data String") == xdata[1]


def test_append_xdata(xtags2):
    xdata = xtags2.get_xdata('MOZMAN')
    assert 4 == len(xdata)

    xdata.append(DXFTag(1000, "Extended Data String"))
    xdata = xtags2.get_xdata('MOZMAN')
    assert 5 == len(xdata)

    assert DXFTag(1000, "Extended Data String") == xdata[4]


XTAGS2 = """  0
LAYER
  5
7
1001
RAK
1000
TEXT-RAK
1070
1
1070
1
1001
MOZMAN
1000
TEXT-MOZMAN
1070
2
1070
2
1001
XDATA3
1000
TEXT-XDATA3
1070
2
1070
3
"""


@pytest.fixture
def xtags3():
    return ExtendedTags.from_text(SPECIALCASE_TEXT)


def test_read_tags(xtags3):
    subclass2 = xtags3.get_subclass('AcDbText')
    assert (100, 'AcDbText') == subclass2[0]


def test_read_tags_2(xtags3):
    subclass2 = xtags3.get_subclass('AcDbText')
    assert (100, 'AcDbText') == subclass2[0]
    assert (1, 'Title:') == subclass2[3]


def test_read_tags_3(xtags3):
    subclass2 = xtags3.get_subclass('AcDbText', 3)
    assert (100, 'AcDbText') == subclass2[0]
    assert (73, 2) == subclass2[1]


def test_key_error(xtags3):
    with pytest.raises(DXFKeyError):
        xtags3.get_subclass('AcDbText', pos=4)


def test_skip_empty_subclass(xtags3):
    xtags3.subclasses[1] = Tags()  # create empty subclass
    subclass2 = xtags3.get_subclass('AcDbText')
    assert (100, 'AcDbText') == subclass2[0]


SPECIALCASE_TEXT = """  0
TEXT
5
8C9
330
6D
100
AcDbEntity
8
0
100
AcDbText
10
4.30
20
1.82
30
0.0
40
0.125
1
Title:
41
0.85
7
ARIALNARROW
100
AcDbText
73
2
"""

ACAD_REACTORS = '{ACAD_REACTORS'


@pytest.fixture
def xtags4():
    return ExtendedTags.from_text(NO_REACTORS)


def test_get_not_existing_reactor(xtags4):
    with pytest.raises(DXFValueError):
        xtags4.get_app_data(ACAD_REACTORS)


def test_new_reactors(xtags4):
    xtags4.new_app_data(ACAD_REACTORS)
    assert (102, 0) == xtags4.noclass[-1]  # code = 102, value = index in appdata list


def test_append_not_existing_reactors(xtags4):
    xtags4.new_app_data(ACAD_REACTORS, [DXFTag(330, 'DEAD')])
    reactors = xtags4.get_app_data_content(ACAD_REACTORS)
    assert 1 == len(reactors)
    assert DXFTag(330, 'DEAD') == reactors[0]


def test_append_to_existing_reactors(xtags4):
    xtags4.new_app_data(ACAD_REACTORS, [DXFTag(330, 'DEAD')])
    reactors = xtags4.get_app_data_content(ACAD_REACTORS)
    reactors.append(DXFTag(330, 'DEAD2'))
    xtags4.set_app_data_content(ACAD_REACTORS, reactors)

    reactors = xtags4.get_app_data_content(ACAD_REACTORS)
    assert DXFTag(330, 'DEAD') == reactors[0]
    assert DXFTag(330, 'DEAD2') == reactors[1]


NO_REACTORS = """  0
TEXT
  5
8C9
330
6D
100
AcDbEntity
  8
0
100
AcDbText
 10
4.30
 20
1.82
 30
0.0
 40
0.125
  1
Title:
 41
0.85
  7
ARIALNARROW
"""


def test_legacy_mode():
    """ Legacy mode does the same job as filter_subclass_markers(). """
    tags = ExtendedTags.from_text(LEICA_DISTO_TAGS, legacy=True)
    assert 9 == len(tags.noclass)
    assert 1 == len(tags.subclasses)
    assert tags.noclass[0] == (0, 'LINE')
    assert tags.noclass[1] == (8, 'LEICA_DISTO_3D')
    assert tags.noclass[-1] == (210, (0, 0, 1))


LEICA_DISTO_TAGS = """0
LINE
100
AcDbEntity
8
LEICA_DISTO_3D
62
256
6
ByLayer
5
75
100
AcDbLine
10
0.819021
20
-0.633955
30
-0.273577
11
0.753216
21
-0.582009
31
-0.276937
39
0
210
0
220
0
230
1
"""


def test_group_code_1000_outside_XDATA():
    tags = ExtendedTags(Tags.from_text(BLOCKBASEPOINTPARAMETER_CVIL_3D_2018))
    assert tags.dxftype() == 'BLOCKBASEPOINTPARAMETER'
    assert len(tags.subclasses) == 6
    block_base_point_parameter = tags.get_subclass('AcDbBlockBasepointParameter')
    assert len(block_base_point_parameter) == 3
    assert block_base_point_parameter[0] == (100, 'AcDbBlockBasepointParameter')
    assert block_base_point_parameter[1] == (1011, (0., 0., 0.))
    assert block_base_point_parameter[2] == (1012, (0., 0., 0.))

    block_element = tags.get_subclass('AcDbBlockElement')
    assert block_element[4] == (1071, 0)

    stream = StringIO()
    tagwriter = TagWriter(stream)
    tagwriter.write_tags(tags)
    lines = stream.getvalue()
    stream.close()
    assert len(lines.split('\n')) == len(BLOCKBASEPOINTPARAMETER_CVIL_3D_2018.split('\n'))


BLOCKBASEPOINTPARAMETER_CVIL_3D_2018 = """0
BLOCKBASEPOINTPARAMETER
5
4C25
330
4C23
100
AcDbEvalExpr
90
1
98
33
99
4
100
AcDbBlockElement
300
Base Point
98
33
99
4
1071
0
100
AcDbBlockParameter
280
1
281
0
100
AcDbBlock1PtParameter
1010
-3.108080399920343
1020
-0.9562299080084814
1030
0.0
93
0
170
0
171
0
100
AcDbBlockBasepointParameter
1011
0.0
1021
0.0
1031
0.0
1012
0.0
1022
0.0
1032
0.0
"""


def test_xrecord_with_group_code_102():
    tags = ExtendedTags(Tags.from_text(XRECORD_WITH_GROUP_CODE_102))
    assert tags.dxftype() == 'XRECORD'
    assert len(tags.appdata) == 1
    assert tags.noclass[2] == (102, 0)  # 0 == index in appdata list
    assert tags.appdata[0][0] == (102, '{ACAD_REACTORS')

    xrecord = tags.get_subclass('AcDbXrecord')
    assert xrecord[2] == (102, 'ACAD_ROUNDTRIP_PRE2007_TABLESTYLE')
    assert len(list(tags)) * 2 + 1 == len(XRECORD_WITH_GROUP_CODE_102.split('\n'))  # +1 == appending '\n'


XRECORD_WITH_GROUP_CODE_102 = """0
XRECORD
5
D9B071D01A0CB6A5
102
{ACAD_REACTORS
330
D9B071D01A0CB69D
102
}
330
D9B071D01A0CB69D
100
AcDbXrecord
280
 1
102
ACAD_ROUNDTRIP_PRE2007_TABLESTYLE
90
    4
91
    0
1

92
    4
93
    0
2

94
    4
95
    0
3

"""


def test_xrecord_with_long_closing_tag():
    tags = ExtendedTags(Tags.from_text(XRECORD_APP_DATA_LONG_CLOSING_TAG))
    assert tags.dxftype() == 'XRECORD'
    # real app data just exists only in the base class, app data marker in AcDbXrecord are just tags, interpreted by
    # the associated application
    assert len(tags.appdata) == 1
    assert len(tags.subclasses[1]) == 35


XRECORD_APP_DATA_LONG_CLOSING_TAG = """  0
XRECORD
5
2F9
102
{ACAD_REACTORS
330
2FF
102
}
330
2FF
100
AcDbXrecord
280
1
1
AcDb_Thumbnail_Schema
102
{ATTRRECORD
341
2FA
102
USUAL_102_TAG_INSIDE_APP_DATA
2
AcDbDs::TreatedAsObjectData
280
1
291
1
102
ATTRRECORD}
102
USUAL_102_TAG_OUTSIDE_APP_DATA
102
{ATTRRECORD
341
2FB
2
AcDbDs::Legacy
280
1
291
1
102
ATTRRECORD}
2
AcDbDs::ID
280
10
91
8
102
{ATTRRECORD
341
2FC
2
AcDs:Indexable
280
1
291
1
102
ATTRRECORD}
102
{ATTRRECORD
341
2FD
2
AcDbDs::HandleAttribute
280
7
282
1
102
ATTRRECORD}
2
Thumbnail_Data
280
15
91
0
"""
