# Copyright (c) 2019-2022 Manfred Moitzi
# License: MIT License
import pytest
import math

from ezdxf.math import Vec3, Matrix44, arc_angle_span_deg
from ezdxf.entities.arc import Arc
from ezdxf.lldxf.const import DXF12, DXF2000
from ezdxf.lldxf.tagwriter import TagCollector, basic_tags_from_text
from ezdxf.path import make_path

TEST_CLASS = Arc
TEST_TYPE = "ARC"

ENTITY_R12 = """0
ARC
5
0
8
0
10
0.0
20
0.0
30
0.0
40
1.0
50
0
51
360
"""

ENTITY_R2000 = """0
ARC
5
0
330
0
100
AcDbEntity
8
0
100
AcDbCircle
10
0.0
20
0.0
30
0.0
40
1.0
100
AcDbArc
50
0
51
360
"""


@pytest.fixture(params=[ENTITY_R12, ENTITY_R2000])
def entity(request):
    return TEST_CLASS.from_text(request.param)


def test_registered():
    from ezdxf.entities.factory import ENTITY_CLASSES

    assert TEST_TYPE in ENTITY_CLASSES


def test_default_init():
    entity = TEST_CLASS()
    assert entity.dxftype() == TEST_TYPE
    assert entity.dxf.handle is None
    assert entity.dxf.owner is None


def test_default_new():
    entity = TEST_CLASS.new(
        handle="ABBA",
        owner="0",
        dxfattribs={
            "color": "7",
            "center": (1, 2, 3),
            "radius": 2.5,
            "start_angle": 30,
            "end_angle": 290,
        },
    )
    assert entity.dxf.layer == "0"
    assert entity.dxf.color == 7
    assert entity.dxf.linetype == "BYLAYER"

    assert entity.dxf.center == (1, 2, 3)
    assert entity.dxf.center.x == 1, "is not Vec3 compatible"
    assert entity.dxf.center.y == 2, "is not Vec3 compatible"
    assert entity.dxf.center.z == 3, "is not Vec3 compatible"
    assert entity.dxf.radius == 2.5
    assert entity.dxf.start_angle == 30
    assert entity.dxf.end_angle == 290
    # can set DXF R2007 value
    entity.dxf.shadow_mode = 1
    assert entity.dxf.shadow_mode == 1
    assert entity.dxf.extrusion == (0.0, 0.0, 1.0)
    assert entity.dxf.hasattr("extrusion") is False, "just the default value"


def test_get_start_and_end_vertices_with_ocs():
    arc = TEST_CLASS.new(
        handle="ABBA",
        owner="0",
        dxfattribs={
            "center": (1, 2, 3),
            "radius": 2.5,
            "start_angle": 90,
            "end_angle": 180,
            "extrusion": (0, 0, -1),
        },
    )
    # convenient properties
    assert arc.start_point.isclose(Vec3(-1, 4.5, -3), abs_tol=1e-6)
    assert arc.end_point.isclose(Vec3(1.5, 2, -3), abs_tol=1e-6)

    # more efficient method:
    start, end = list(arc.vertices([arc.dxf.start_angle, arc.dxf.end_angle]))
    assert start.isclose(Vec3(-1, 4.5, -3), abs_tol=1e-6)
    assert end.isclose(Vec3(1.5, 2, -3), abs_tol=1e-6)


def test_load_from_text(entity):
    assert entity.dxf.layer == "0"
    assert entity.dxf.color == 256, "default color is 256 (by layer)"
    assert entity.dxf.center == (0, 0, 0)
    assert entity.dxf.radius == 1
    assert entity.dxf.start_angle == 0
    assert entity.dxf.end_angle == 360


@pytest.mark.parametrize(
    "txt,ver", [(ENTITY_R2000, DXF2000), (ENTITY_R12, DXF12)]
)
def test_write_dxf(txt, ver):
    expected = basic_tags_from_text(txt)
    arc = TEST_CLASS.from_text(txt)
    collector = TagCollector(dxfversion=ver, optional=True)
    arc.export_dxf(collector)
    assert collector.tags == expected

    collector2 = TagCollector(dxfversion=ver, optional=False)
    arc.export_dxf(collector2)
    assert collector.has_all_tags(collector2)


def test_angles():
    arc = Arc.new(dxfattribs={"radius": 1, "start_angle": 30, "end_angle": 60})
    assert tuple(arc.angles(2)) == (30, 60)
    assert tuple(arc.angles(3)) == (30, 45, 60)

    arc.dxf.start_angle = 180
    arc.dxf.end_angle = 0
    assert tuple(arc.angles(2)) == (180, 0)
    assert tuple(arc.angles(3)) == (180, 270, 0)

    arc.dxf.start_angle = -90
    arc.dxf.end_angle = -180
    assert tuple(arc.angles(2)) == (270, 180)
    assert tuple(arc.angles(4)) == (270, 0, 90, 180)


def test_arc_default_ocs():
    arc = Arc.new(
        dxfattribs={
            "center": (2, 3, 4),
            "thickness": 2,
            "start_angle": 30,
            "end_angle": 60,
        }
    )
    # 1. rotation - 2. scaling - 3. translation
    m = Matrix44.chain(Matrix44.scale(2, 2, 3), Matrix44.translate(1, 1, 1))
    # default extrusion is (0, 0, 1), therefore scale(2, 2, ..) is a uniform scaling in the xy-play of the OCS
    arc.transform(m)

    assert arc.dxf.center == (5, 7, 13)
    assert arc.dxf.extrusion == (0, 0, 1)
    assert arc.dxf.thickness == 6
    assert math.isclose(arc.dxf.start_angle, 30, abs_tol=1e-9)
    assert math.isclose(arc.dxf.end_angle, 60, abs_tol=1e-9)

    arc.transform(Matrix44.z_rotate(math.radians(30)))
    assert math.isclose(arc.dxf.start_angle, 60, abs_tol=1e-9)
    assert math.isclose(arc.dxf.end_angle, 90, abs_tol=1e-9)


# See also ConstructionArc(): test suite 645 - test_flattening()
@pytest.mark.parametrize(
    "r, s, e, sagitta, count",
    [
        (1, 0, 180, 0.10, 5),
        (0, 0, 360, 0.10, 0),  # radius 0 works but yields nothing
        (-1, 0, 180, 0.35, 3),  # negative radius same as positive radius
        (1, 270, 90, 0.10, 5),  # start angle > end angle
    ],
)
def test_arc_flattening(r, s, e, sagitta, count):
    arc = Arc.new(
        dxfattribs={
            "radius": r,
            "start_angle": s,
            "end_angle": e,
        }
    )
    points = list(arc.flattening(sagitta))
    assert len(points) == count


def test_arc_3d_flattening_parallel_to_xy_plane():
    arc = Arc.new(
        dxfattribs={
            "center": Vec3(1, 1, 1),
            "radius": 1,
            "start_angle": 0,
            "end_angle": 90,
        }
    )
    points = list(arc.flattening(0.01))
    expected = [
        Vec3(2.0, 1.0, 1.0),
        Vec3(1.9659258262890682, 1.2588190451025207, 1.0),
        Vec3(1.8660254037844388, 1.5, 1.0),
        Vec3(1.7071067811865475, 1.7071067811865475, 1.0),
        Vec3(1.5, 1.8660254037844386, 1.0),
        Vec3(1.2588190451025207, 1.9659258262890682, 1.0),
        Vec3(1.0, 2.0, 1.0),
    ]
    for p, e in zip(points, expected):
        assert p.isclose(e)


def test_arc_3d_flattening():
    arc = Arc.new(
        dxfattribs={
            "center": Vec3(1, 1, 1),
            "radius": 1,
            "start_angle": 30,
            "end_angle": 120,
            "extrusion": Vec3(1, 0, 0),
        }
    )
    points = list(arc.flattening(0.01))
    expected = [
        Vec3(1.0, 1.866025403784, 1.5),
        Vec3(1.0, 1.707106781187, 1.707106781187),
        Vec3(1.0, 1.5, 1.866025403784),
        Vec3(1.0, 1.258819045103, 1.965925826289),
        Vec3(1.0, 1.0, 2.0),
        Vec3(1.0, 0.741180954897, 1.965925826289),
        Vec3(1.0, 0.5, 1.866025403784),
    ]
    for p, e in zip(points, expected):
        assert p.isclose(e)


def test_arc_flattening_returns_Vec3():
    arc = Arc.new(
        dxfattribs={
            "radius": 1,
            "start_angle": 0,
            "end_angle": 180,
        }
    )
    points = list(arc.flattening(0.1))
    assert isinstance(points[0], Vec3), "must return Vec3() instances"


def test_360_deg_arc_transformation():
    arc = Arc.new(
        dxfattribs={
            "radius": 1,
            "start_angle": 0,
            "end_angle": 360,
        }
    )
    count1 = len(list(make_path(arc).flattening(0.01)))
    arc.transform(Matrix44.translate(1, 0, 0))
    count2 = len(list(make_path(arc).flattening(0.01)))
    assert count1 == count2

    arc.transform(Matrix44.z_rotate(math.pi / 2))
    p = make_path(arc)
    count3 = len(list(p.flattening(0.01)))
    assert count1 == count3


@pytest.mark.parametrize("angle", [30, 180, 360])
@pytest.mark.parametrize(
    "reflexion", [(-1, 1, 1), (1, -1, 1), (1, 1, -1)], ids=["x", "y", "z"]
)
def test_30_deg_arc_reflexion(reflexion, angle):
    arc = Arc.new(
        dxfattribs={
            "radius": 1,
            "start_angle": 0,
            "end_angle": angle,
        }
    )
    x, y, z = reflexion
    arc.transform(Matrix44.scale(x, y, z))
    assert arc_angle_span_deg(
        arc.dxf.start_angle, arc.dxf.end_angle
    ) == pytest.approx(angle)


MALFORMED_ARC = """0
ARC
5
0
62
7
330
0
6
LT_EZDXF
8
LY_EZDXF
100
AcDbCircle
10
1.0
20
2.0
30
3.0
100
AcDbEntity
40
2.0
50
30
51
330
"""


def test_load_malformed_circle():
    arc = Arc.from_text(MALFORMED_ARC)
    assert arc.dxf.layer == "LY_EZDXF"
    assert arc.dxf.linetype == "LT_EZDXF"
    assert arc.dxf.color == 7
    assert arc.dxf.center.isclose((1, 2, 3))
    assert arc.dxf.radius == 2.0
    assert arc.dxf.start_angle == 30.0
    assert arc.dxf.end_angle == 330.0
