import unittest

import pytest

from shapely import geometry
from shapely.constructive import BufferCapStyle, BufferJoinStyle
from shapely.geometry.base import CAP_STYLE, JOIN_STYLE


@pytest.mark.parametrize("distance", [float("nan"), float("inf")])
def test_non_finite_distance(distance):
    g = geometry.Point(0, 0)
    with pytest.raises(ValueError, match="distance must be finite"):
        g.buffer(distance)


class BufferTests(unittest.TestCase):
    """Test Buffer Point/Line/Polygon with and without single_sided params"""

    def test_empty(self):
        g = geometry.Point(0, 0)
        h = g.buffer(0)
        assert h.is_empty

    def test_point(self):
        g = geometry.Point(0, 0)
        h = g.buffer(1, quad_segs=1)
        assert h.geom_type == "Polygon"
        expected_coord = [(1.0, 0.0), (0, -1.0), (-1.0, 0), (0, 1.0), (1.0, 0.0)]
        for index, coord in enumerate(h.exterior.coords):
            assert coord[0] == pytest.approx(expected_coord[index][0])
            assert coord[1] == pytest.approx(expected_coord[index][1])

    def test_point_single_sidedd(self):
        g = geometry.Point(0, 0)
        h = g.buffer(1, quad_segs=1, single_sided=True)
        assert h.geom_type == "Polygon"
        expected_coord = [(1.0, 0.0), (0, -1.0), (-1.0, 0), (0, 1.0), (1.0, 0.0)]
        for index, coord in enumerate(h.exterior.coords):
            assert coord[0] == pytest.approx(expected_coord[index][0])
            assert coord[1] == pytest.approx(expected_coord[index][1])

    def test_line(self):
        g = geometry.LineString([[0, 0], [0, 1]])
        h = g.buffer(1, quad_segs=1)
        assert h.geom_type == "Polygon"
        expected_coord = [
            (-1.0, 1.0),
            (0, 2.0),
            (1.0, 1.0),
            (1.0, 0.0),
            (0, -1.0),
            (-1.0, 0.0),
            (-1.0, 1.0),
        ]
        for index, coord in enumerate(h.exterior.coords):
            assert coord[0] == pytest.approx(expected_coord[index][0])
            assert coord[1] == pytest.approx(expected_coord[index][1])

    def test_line_single_sideded_left(self):
        g = geometry.LineString([[0, 0], [0, 1]])
        h = g.buffer(1, quad_segs=1, single_sided=True)
        assert h.geom_type == "Polygon"
        expected_coord = [(0.0, 1.0), (0.0, 0.0), (-1.0, 0.0), (-1.0, 1.0), (0.0, 1.0)]
        for index, coord in enumerate(h.exterior.coords):
            assert coord[0] == pytest.approx(expected_coord[index][0])
            assert coord[1] == pytest.approx(expected_coord[index][1])

    def test_line_single_sideded_right(self):
        g = geometry.LineString([[0, 0], [0, 1]])
        h = g.buffer(-1, quad_segs=1, single_sided=True)
        assert h.geom_type == "Polygon"
        expected_coord = [(0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0), (0.0, 0.0)]
        for index, coord in enumerate(h.exterior.coords):
            assert coord[0] == pytest.approx(expected_coord[index][0])
            assert coord[1] == pytest.approx(expected_coord[index][1])

    def test_polygon(self):
        g = geometry.Polygon([[0, 0], [1, 0], [1, 1], [0, 1], [0, 0]])
        h = g.buffer(1, quad_segs=1)
        assert h.geom_type == "Polygon"
        expected_coord = [
            (-1.0, 0.0),
            (-1.0, 1.0),
            (0.0, 2.0),
            (1.0, 2.0),
            (2.0, 1.0),
            (2.0, 0.0),
            (1.0, -1.0),
            (0.0, -1.0),
            (-1.0, 0.0),
        ]
        for index, coord in enumerate(h.exterior.coords):
            assert coord[0] == pytest.approx(expected_coord[index][0])
            assert coord[1] == pytest.approx(expected_coord[index][1])

    def test_polygon_single_sideded(self):
        g = geometry.Polygon([[0, 0], [1, 0], [1, 1], [0, 1], [0, 0]])
        h = g.buffer(1, quad_segs=1, single_sided=True)
        assert h.geom_type == "Polygon"
        expected_coord = [
            (-1.0, 0.0),
            (-1.0, 1.0),
            (0.0, 2.0),
            (1.0, 2.0),
            (2.0, 1.0),
            (2.0, 0.0),
            (1.0, -1.0),
            (0.0, -1.0),
            (-1.0, 0.0),
        ]
        for index, coord in enumerate(h.exterior.coords):
            assert coord[0] == pytest.approx(expected_coord[index][0])
            assert coord[1] == pytest.approx(expected_coord[index][1])

    def test_enum_values(self):
        assert CAP_STYLE.round == 1
        assert CAP_STYLE.round == BufferCapStyle.round
        assert CAP_STYLE.flat == 2
        assert CAP_STYLE.flat == BufferCapStyle.flat
        assert CAP_STYLE.square == 3
        assert CAP_STYLE.square == BufferCapStyle.square

        assert JOIN_STYLE.round == 1
        assert JOIN_STYLE.round == BufferJoinStyle.round
        assert JOIN_STYLE.mitre == 2
        assert JOIN_STYLE.mitre == BufferJoinStyle.mitre
        assert JOIN_STYLE.bevel == 3
        assert JOIN_STYLE.bevel == BufferJoinStyle.bevel

    def test_cap_style(self):
        g = geometry.LineString([[0, 0], [1, 0]])
        h = g.buffer(1, cap_style=BufferCapStyle.round)
        assert h == g.buffer(1, cap_style=CAP_STYLE.round)
        assert h == g.buffer(1, cap_style="round")

        h = g.buffer(1, cap_style=BufferCapStyle.flat)
        assert h == g.buffer(1, cap_style=CAP_STYLE.flat)
        assert h == g.buffer(1, cap_style="flat")

        h = g.buffer(1, cap_style=BufferCapStyle.square)
        assert h == g.buffer(1, cap_style=CAP_STYLE.square)
        assert h == g.buffer(1, cap_style="square")

    def test_buffer_style(self):
        g = geometry.LineString([[0, 0], [1, 0]])
        h = g.buffer(1, join_style=BufferJoinStyle.round)
        assert h == g.buffer(1, join_style=JOIN_STYLE.round)
        assert h == g.buffer(1, join_style="round")

        h = g.buffer(1, join_style=BufferJoinStyle.mitre)
        assert h == g.buffer(1, join_style=JOIN_STYLE.mitre)
        assert h == g.buffer(1, join_style="mitre")

        h = g.buffer(1, join_style=BufferJoinStyle.bevel)
        assert h == g.buffer(1, join_style=JOIN_STYLE.bevel)
        assert h == g.buffer(1, join_style="bevel")


def test_deprecated_quadsegs():
    point = geometry.Point(0, 0)
    with pytest.warns(FutureWarning):
        result = point.buffer(1, quadsegs=1)
    expected = point.buffer(1, quad_segs=1)
    assert result.equals(expected)


def test_resolution_alias():
    point = geometry.Point(0, 0)
    result = point.buffer(1, resolution=1)
    expected = point.buffer(1, quad_segs=1)
    assert result.equals(expected)
