# Copyright (c) 2019 Manfred Moitzi
# License: MIT License
# created 2019-02-14
import pytest
import ezdxf

from ezdxf.lldxf.const import DXFAttributeError, DXF12, DXFValueError
from ezdxf.lldxf.tagwriter import TagCollector
from ezdxf.entities import DXFEntity, is_graphic_entity, Insert
from ezdxf.lldxf.extendedtags import DXFTag
from ezdxf.entities.line import Line

ENTITY = """0
DXFENTITY
5
FFFF
330
ABBA
"""


@pytest.fixture
def entity():
    return DXFEntity.from_text(ENTITY)


def test_is_not_graphic_entity(entity):
    assert is_graphic_entity(entity) is False


def test_default_constructor():
    entity = DXFEntity()
    assert entity.dxftype() == "DXFENTITY"
    assert entity.dxf.handle is None
    assert entity.dxf.owner is None
    assert entity == entity
    assert entity != DXFEntity()


def test_init_with_tags(entity):
    assert entity.dxftype() == "DXFENTITY"
    assert entity.dxf.handle == "FFFF"
    assert entity.dxf.owner == "ABBA"
    assert str(entity) == "DXFENTITY(#FFFF)"
    assert (
        repr(entity)
        == "<class 'ezdxf.entities.dxfentity.DXFEntity'> DXFENTITY(#FFFF)"
    )


def test_invalid_dxf_attrib(entity):
    with pytest.raises(DXFAttributeError):
        _ = entity.dxf.color


def test_get_all_dxf_attribs(entity):
    dxfattribs = entity.dxfattribs()
    assert len(dxfattribs) == 2
    assert dxfattribs["handle"] == "FFFF"
    assert dxfattribs["owner"] == "ABBA"


def test_get_dxf_attribs_and_discard_some(entity):
    dxfattribs = entity.dxfattribs(drop={"owner"})
    assert len(dxfattribs) == 1
    assert dxfattribs["handle"] == "FFFF"
    assert "owner" not in dxfattribs


def test_write_r12_dxf(entity):
    tagwriter = TagCollector(dxfversion=DXF12)
    entity.export_dxf(tagwriter)
    tag = tagwriter.tags
    assert len(tag) == 2
    assert tag[0] == (0, "DXFENTITY")
    assert tag[1] == (5, "FFFF")


def test_write_latest_dxf(entity):
    tagwriter = TagCollector()
    entity.export_dxf(tagwriter)
    tag = tagwriter.tags
    assert len(tag) == 3
    assert tag[0] == (0, "DXFENTITY")
    assert tag[1] == (5, "FFFF")
    assert tag[2] == (330, "ABBA")


def test_is_alive(entity):
    assert entity.is_alive is True
    entity.destroy()
    assert entity.is_alive is False


def test_calling_destroy_multiple_times(entity):
    entity.destroy()
    entity.destroy(), "2nd call should do nothing"
    entity.destroy(), "3rd call should do nothing"
    assert entity.is_alive is False


def test_dont_write_handles_for_R12(entity):
    from ezdxf.lldxf.tagwriter import TagWriter
    from io import StringIO

    s = StringIO()
    t = TagWriter(s)
    t.dxfversion = DXF12
    t.write_handles = False
    entity.export_dxf(t)
    result = s.getvalue()
    assert "5\nFFFF\n" not in result


def test_uuid():
    e1 = DXFEntity()
    e2 = DXFEntity()
    assert e1.dxf.handle == e2.dxf.handle, "can't distinguish by handle"
    assert e1.uuid != e2.uuid, "can distinguish by uuid"


def test_source_of_copy_is_none_for_a_new_entity():
    e = DXFEntity()
    assert e.source_of_copy is None


def test_set_source_of_copy():
    e = DXFEntity()
    e.set_source_of_copy(e)
    assert e.source_of_copy is e


def test_delete_missing_source_of_copy_without_exception():
    e = DXFEntity()
    e.del_source_of_copy()
    assert True is True


def test_source_block_reference_is_none_for_a_new_entity():
    e = DXFEntity()
    assert e.has_source_block_reference is False
    assert e.source_block_reference is None


def test_set_source_block_reference():
    e = DXFEntity()
    insert = Insert()
    e.set_source_block_reference(insert)
    assert e.has_source_block_reference is True
    assert e.source_block_reference is insert


def test_setting_source_block_reference_twice_without_exception():
    e = DXFEntity()
    e.set_source_block_reference(Insert())
    e.set_source_block_reference(Insert())
    assert True is True


def test_setting_source_block_reference_a_second_time_has_no_effect():
    e = DXFEntity()
    insert = Insert()
    e.set_source_block_reference(insert)
    e.set_source_block_reference(Insert())
    assert (
        e.source_block_reference is insert
    ), "source block reference should not change"


def test_do_not_copy_source_block_reference():
    e = DXFEntity()
    insert = Insert()
    e.set_source_block_reference(insert)
    copy = e.copy()
    assert copy.has_source_block_reference is False
    assert copy.source_block_reference is None


def test_delete_missing_source_block_reference_without_exception():
    e = DXFEntity()
    e.del_source_block_reference()
    assert True is True


LINE_DATA = """  0
LINE
  5
0
330
0
100
AcDbEntity
  8
0
100
AcDbLine
 10
0.0
 20
0.0
 30
0.0
 11
1.0
 21
1.0
 31
1.0
"""


@pytest.fixture
def line():
    return Line.from_text(LINE_DATA)


def test_str(line):
    assert str(line) == "LINE(#0)"
    assert repr(line) == "<class 'ezdxf.entities.line.Line'> LINE(#0)"


def test_get_dxf_defaul(line):
    # get_dxf_default returns the DXF default value for unset attributes
    assert line.dxf.get_default("thickness") == 0
    # get returns the given default for unset attributes, which is None by default :)
    assert line.dxf.get("thickness") is None


def test_ocs(line):
    ocs = line.ocs()
    assert ocs.uz == (0, 0, 1)
    assert ocs.transform is False


class TestAppData:
    @pytest.fixture
    def entity(self):
        return Line.from_text(LINE_DATA)

    def test_new_app_data(self, entity):
        assert entity.has_app_data("{MOZMAN") is False
        entity.set_app_data("{MOZMAN", tags=[DXFTag(330, "DEAD")])
        assert entity.has_app_data("{MOZMAN") is True

    def test_get_app_data(self, entity):
        entity.set_app_data("{MOZMAN", tags=[DXFTag(330, "DEAD")])

        app_data = entity.get_app_data("{MOZMAN")
        assert len(app_data) == 1
        assert DXFTag(330, "DEAD") == app_data[0]

    def test_set_app_data(self, entity):
        entity.set_app_data("{MOZMAN", tags=[DXFTag(330, "DEAD")])
        app_data = entity.get_app_data("{MOZMAN")
        assert 1 == len(app_data)
        assert DXFTag(330, "DEAD") == app_data[0]
        app_data.append(DXFTag(360, "DEAD2"))
        entity.set_app_data("{MOZMAN", app_data)

        app_data = entity.get_app_data("{MOZMAN")
        assert 2 == len(app_data)
        assert DXFTag(330, "DEAD") == app_data[0]
        assert DXFTag(360, "DEAD2") == app_data[1]

    def test_not_existing_appid(self, entity):
        with pytest.raises(DXFValueError):
            entity.get_app_data("XYZ")


class TestXData:
    @pytest.fixture
    def entity(self):
        return Line.from_text(LINE_DATA)

    def test_new_app_data(self, entity):
        assert entity.has_xdata("MOZMAN") is False
        entity.set_xdata("MOZMAN", tags=[DXFTag(1000, "Extended Data String")])
        assert entity.has_xdata("MOZMAN") is True

    def test_get_xdata(self, entity):
        entity.set_xdata("MOZMAN", tags=[DXFTag(1000, "Extended Data String")])

        xdata = entity.get_xdata("MOZMAN")
        assert len(xdata) == 1
        assert DXFTag(1000, "Extended Data String") == xdata[0]

    def test_set_xdata(self, entity):
        entity.set_xdata("MOZMAN", tags=[DXFTag(1000, "Extended Data String")])
        xdata = entity.get_xdata("MOZMAN")
        assert 1 == len(xdata)
        assert DXFTag(1000, "Extended Data String") == xdata[0]
        xdata.append(DXFTag(1000, "Extended Data String2"))
        entity.set_xdata("MOZMAN", xdata)

        xdata = entity.get_xdata("MOZMAN")
        assert 2 == len(xdata)
        assert DXFTag(1000, "Extended Data String") == xdata[0]
        assert DXFTag(1000, "Extended Data String2") == xdata[1]

    def test_not_existing_appid(self, entity):
        with pytest.raises(DXFValueError):
            entity.get_xdata("XYZ")

    def test_get_xdata_list_exception(self, entity):
        with pytest.raises(DXFValueError):
            _ = entity.get_xdata_list("ACAD", "DSTYLE")
        entity.set_xdata("ACAD", tags=[DXFTag(1000, "Extended Data String")])

        with pytest.raises(DXFValueError):
            _ = entity.get_xdata_list("ACAD", "DSTYLE")

    def test_has_xdata_list(self, entity):
        assert entity.has_xdata_list("ACAD", "DSTYLE") is False
        entity.set_xdata("ACAD", tags=[DXFTag(1000, "Extended Data String")])
        assert entity.has_xdata_list("ACAD", "DSTYLE") is False

    def test_set_xdata_list(self, entity):
        entity.set_xdata_list("ACAD", "DSTYLE", [(1070, 1), (1000, "String")])
        xdata_list = entity.get_xdata_list("ACAD", "DSTYLE")
        assert len(xdata_list) == 5
        assert xdata_list == [
            (1000, "DSTYLE"),
            (1002, "{"),
            (1070, 1),
            (1000, "String"),
            (1002, "}"),
        ]
        # add another list to ACAD
        entity.set_xdata_list("ACAD", "MOZMAN", [(1070, 2), (1000, "mozman")])
        xdata = entity.get_xdata_list("ACAD", "MOZMAN")
        assert len(xdata) == 5
        assert xdata == [
            (1000, "MOZMAN"),
            (1002, "{"),
            (1070, 2),
            (1000, "mozman"),
            (1002, "}"),
        ]
        xdata = entity.get_xdata("ACAD")
        assert len(xdata) == 10

    def test_discard_xdata_list(self, entity):
        entity.set_xdata_list("ACAD", "DSTYLE", [(1070, 1), (1000, "String")])
        xdata_list = entity.get_xdata_list("ACAD", "DSTYLE")
        assert len(xdata_list) == 5
        entity.discard_xdata_list("ACAD", "DSTYLE")
        with pytest.raises(DXFValueError):
            _ = entity.get_xdata_list("ACAD", "DSTYLE")

        entity.discard_xdata_list("ACAD", "DSTYLE")

    def test_replace_xdata_list(self, entity):
        entity.set_xdata_list("ACAD", "DSTYLE", [(1070, 1), (1000, "String")])
        xdata_list = entity.get_xdata_list("ACAD", "DSTYLE")
        assert len(xdata_list) == 5
        assert xdata_list == [
            (1000, "DSTYLE"),
            (1002, "{"),
            (1070, 1),
            (1000, "String"),
            (1002, "}"),
        ]
        entity.set_xdata_list(
            "ACAD", "DSTYLE", [(1070, 2), (1000, "mozman"), (1000, "data")]
        )
        xdata_list = entity.get_xdata_list("ACAD", "DSTYLE")
        assert len(xdata_list) == 6
        assert xdata_list == [
            (1000, "DSTYLE"),
            (1002, "{"),
            (1070, 2),
            (1000, "mozman"),
            (1000, "data"),
            (1002, "}"),
        ]
        # replace not existing list -> append list
        entity.replace_xdata_list("ACAD", "MOZMAN", [(1070, 3), (1000, "new")])
        xdata_list = entity.get_xdata_list("ACAD", "MOZMAN")
        assert len(xdata_list) == 5
        assert xdata_list == [
            (1000, "MOZMAN"),
            (1002, "{"),
            (1070, 3),
            (1000, "new"),
            (1002, "}"),
        ]
        xdata = entity.get_xdata("ACAD")
        assert len(xdata) == 6 + 5


class TestReactors:
    @pytest.fixture
    def entity(self):
        return Line.from_text(LINE_DATA)

    def test_has_no_reactors(self, entity):
        assert entity.has_reactors() is False

    def test_set_reactors(self, entity):
        entity.set_reactors(["A000", "B000", "C000"])
        assert entity.has_reactors() is True
        handles = entity.get_reactors()
        assert ["A000", "B000", "C000"] == handles

    def test_append_handle(self, entity):
        entity.set_reactors([])
        assert 0 == len(entity.get_reactors())
        entity.append_reactor_handle("A000")
        assert "A000" in entity.get_reactors()
        entity.append_reactor_handle("B000")
        assert "B000" in entity.get_reactors()
        assert 2 == len(entity.get_reactors())

        entity.append_reactor_handle("B000")  # same handle again
        assert "B000" in entity.get_reactors()
        assert 2 == len(
            entity.get_reactors()
        ), "handle entries should be unique"

        entity.append_reactor_handle(
            "FF"
        )  # smallest handle, should be first handle in reactors
        assert entity.get_reactors()[0] == "FF"

        entity.append_reactor_handle(
            "FFFF"
        )  # biggest handle, should be last handle in reactors
        assert "FFFF" == entity.get_reactors()[-1]

    def test_discard_handle(self, entity):
        entity.set_reactors(["A000", "B000", "C000"])
        entity.discard_reactor_handle("A000")
        assert 2 == len(entity.get_reactors()), "Handle not deleted"
        entity.discard_reactor_handle("FFFF")  # ignore not existing handles
        assert 2 == len(entity.get_reactors())


class TestGetLayout:
    @pytest.fixture(scope="class")
    def doc(self):
        return ezdxf.new()

    def test_get_layout_model_space(self, doc):
        msp = doc.modelspace()
        circle = msp.add_circle(center=(0, 0), radius=1)
        layout = circle.get_layout()
        assert msp.name == layout.name

    def test_get_layout_paper_space(self, doc):
        psp = doc.layout()
        circle = psp.add_circle(center=(0, 0), radius=1)
        layout = circle.get_layout()
        assert psp.name == layout.name

    def test_get_layout_block(self, doc):
        block = doc.blocks.new("TEST")
        circle = block.add_circle(center=(0, 0), radius=1)
        layout = circle.get_layout()
        assert block.name == layout.name

    def test_get_layout_without_layout(self, doc):
        msp = doc.modelspace()
        circle = msp.add_circle(center=(0, 0), radius=1)
        msp.unlink_entity(circle)
        assert circle.get_layout() is None
