from collections.abc import Iterator

from shapely.geometry import shape, MultiPoint, MultiLineString, MultiPolygon, GeometryCollection

from geo.render.canvas import Canvas
from geo.render.viewport import Viewport


def bresenham(x0: int, y0: int, x1: int, y1: int) -> Iterator[tuple[int, int]]:
    """Yield integer pixel coordinates along a line (Bresenham's algorithm)."""
    dx = abs(x1 - x0)
    dy = -abs(y1 - y0)
    sx = 1 if x0 < x1 else -1
    sy = 1 if y0 < y1 else -1
    err = dx + dy
    while True:
        yield x0, y0
        if x0 == x1 and y0 == y1:
            break
        e2 = 2 * err
        if e2 >= dy:
            err += dy
            x0 += sx
        if e2 <= dx:
            err += dx
            y0 += sy


def _draw_point(canvas: Canvas, viewport: Viewport, x: float, y: float) -> None:
    """Draw a point as a small cross."""
    px, py = viewport.geo_to_pixel(x, y)
    canvas.set_pixel(px, py)
    canvas.set_pixel(px - 1, py)
    canvas.set_pixel(px + 1, py)
    canvas.set_pixel(px, py - 1)
    canvas.set_pixel(px, py + 1)


def _draw_linestring(canvas: Canvas, viewport: Viewport,
                     coords: list[tuple[float, ...]]) -> None:
    """Draw a linestring by connecting consecutive vertices."""
    if len(coords) < 2:
        return
    prev_px, prev_py = viewport.geo_to_pixel(coords[0][0], coords[0][1])
    for i in range(1, len(coords)):
        cur_px, cur_py = viewport.geo_to_pixel(coords[i][0], coords[i][1])
        for bx, by in bresenham(prev_px, prev_py, cur_px, cur_py):
            canvas.set_pixel(bx, by)
        prev_px, prev_py = cur_px, cur_py


def _draw_polygon(canvas: Canvas, viewport: Viewport,
                  exterior_coords: list[tuple[float, ...]]) -> None:
    """Draw a polygon outline (exterior ring only for speed)."""
    _draw_linestring(canvas, viewport, exterior_coords)


def rasterize_geometry(geom, canvas: Canvas, viewport: Viewport) -> None:
    """Draw a Shapely geometry onto the canvas."""
    geom_type = geom.geom_type

    if geom_type == "Point":
        _draw_point(canvas, viewport, geom.x, geom.y)

    elif geom_type == "MultiPoint":
        for point in geom.geoms:
            _draw_point(canvas, viewport, point.x, point.y)

    elif geom_type == "LineString":
        _draw_linestring(canvas, viewport, list(geom.coords))

    elif geom_type == "MultiLineString":
        for line in geom.geoms:
            _draw_linestring(canvas, viewport, list(line.coords))

    elif geom_type == "LinearRing":
        _draw_linestring(canvas, viewport, list(geom.coords))

    elif geom_type == "Polygon":
        _draw_polygon(canvas, viewport, list(geom.exterior.coords))

    elif geom_type == "MultiPolygon":
        for poly in geom.geoms:
            _draw_polygon(canvas, viewport, list(poly.exterior.coords))

    elif geom_type == "GeometryCollection":
        for sub_geom in geom.geoms:
            rasterize_geometry(sub_geom, canvas, viewport)


def rasterize_feature(feature: dict, canvas: Canvas, viewport: Viewport) -> None:
    """Rasterize a single Fiona/GeoJSON feature dict."""
    geom = shape(feature["geometry"])
    rasterize_geometry(geom, canvas, viewport)
