"""
Plot single geometries using Matplotlib.

Note: this module is experimental, and mainly targetting (interactive)
exploration, debugging and illustration purposes.

"""

import numpy as np

import shapely


def _default_ax():
    import matplotlib.pyplot as plt

    ax = plt.gca()
    ax.grid(True)
    ax.set_aspect("equal")
    return ax


def _path_from_polygon(polygon):
    from matplotlib.path import Path

    if isinstance(polygon, shapely.MultiPolygon):
        return Path.make_compound_path(
            *[_path_from_polygon(poly) for poly in polygon.geoms]
        )
    else:
        return Path.make_compound_path(
            Path(np.asarray(polygon.exterior.coords)[:, :2]),
            *[Path(np.asarray(ring.coords)[:, :2]) for ring in polygon.interiors],
        )


def patch_from_polygon(polygon, **kwargs):
    """
    Gets a Matplotlib patch from a (Multi)Polygon.

    Note: this function is experimental, and mainly targetting (interactive)
    exploration, debugging and illustration purposes.

    Parameters
    ----------
    polygon : shapely.Polygon or shapely.MultiPolygon
    **kwargs
        Additional keyword arguments passed to the matplotlib Patch.

    Returns
    -------
    Matplotlib artist (PathPatch)
    """
    from matplotlib.patches import PathPatch

    return PathPatch(_path_from_polygon(polygon), **kwargs)


def plot_polygon(
    polygon,
    ax=None,
    add_points=True,
    color=None,
    facecolor=None,
    edgecolor=None,
    linewidth=None,
    **kwargs
):
    """
    Plot a (Multi)Polygon.

    Note: this function is experimental, and mainly targetting (interactive)
    exploration, debugging and illustration purposes.

    Parameters
    ----------
    polygon : shapely.Polygon or shapely.MultiPolygon
    ax : matplotlib Axes, default None
        The axes on which to draw the plot. If not specified, will get the
        current active axes or create a new figure.
    add_points : bool, default True
        If True, also plot the coordinates (vertices) as points.
    color : matplotlib color specification
        Color for both the polygon fill (face) and boundary (edge). By default,
        the fill is using an alpha of 0.3. You can specify `facecolor` and
        `edgecolor` separately for greater control.
    facecolor : matplotlib color specification
        Color for the polygon fill.
    edgecolor : matplotlib color specification
        Color for the polygon boundary.
    linewidth : float
        The line width for the polygon boundary.
    **kwargs
        Additional keyword arguments passed to the matplotlib Patch.

    Returns
    -------
    Matplotlib artist (PathPatch), if `add_points` is false.
    A tuple of Matplotlib artists (PathPatch, Line2D), if `add_points` is true.
    """
    from matplotlib import colors

    if ax is None:
        ax = _default_ax()

    if color is None:
        color = "C0"
    color = colors.to_rgba(color)

    if facecolor is None:
        facecolor = list(color)
        facecolor[-1] = 0.3
        facecolor = tuple(facecolor)

    if edgecolor is None:
        edgecolor = color

    patch = patch_from_polygon(
        polygon, facecolor=facecolor, edgecolor=edgecolor, linewidth=linewidth, **kwargs
    )
    ax.add_patch(patch)
    ax.autoscale_view()

    if add_points:
        line = plot_points(polygon, ax=ax, color=color)
        return patch, line

    return patch


def plot_line(line, ax=None, add_points=True, color=None, linewidth=2, **kwargs):
    """
    Plot a (Multi)LineString/LinearRing.

    Note: this function is experimental, and mainly targetting (interactive)
    exploration, debugging and illustration purposes.

    Parameters
    ----------
    line : shapely.LineString or shapely.LinearRing
    ax : matplotlib Axes, default None
        The axes on which to draw the plot. If not specified, will get the
        current active axes or create a new figure.
    add_points : bool, default True
        If True, also plot the coordinates (vertices) as points.
    color : matplotlib color specification
        Color for the line (edgecolor under the hood) and pointes.
    linewidth : float, default 2
        The line width for the polygon boundary.
    **kwargs
        Additional keyword arguments passed to the matplotlib Patch.

    Returns
    -------
    Matplotlib artist (PathPatch)
    """
    from matplotlib.patches import PathPatch
    from matplotlib.path import Path

    if ax is None:
        ax = _default_ax()

    if color is None:
        color = "C0"

    if isinstance(line, shapely.MultiLineString):
        path = Path.make_compound_path(
            *[Path(np.asarray(mline.coords)[:, :2]) for mline in line.geoms]
        )
    else:
        path = Path(np.asarray(line.coords)[:, :2])

    patch = PathPatch(
        path, facecolor="none", edgecolor=color, linewidth=linewidth, **kwargs
    )
    ax.add_patch(patch)
    ax.autoscale_view()

    if add_points:
        line = plot_points(line, ax=ax, color=color)
        return patch, line

    return patch


def plot_points(geom, ax=None, color=None, marker="o", **kwargs):
    """
    Plot a Point/MultiPoint or the vertices of any other geometry type.

    Parameters
    ----------
    geom : shapely.Geometry
        Any shapely Geometry object, from which all vertices are extracted
        and plotted.
    ax : matplotlib Axes, default None
        The axes on which to draw the plot. If not specified, will get the
        current active axes or create a new figure.
    color : matplotlib color specification
        Color for the filled points. You can use `markeredgecolor` and
        `markeredgecolor` to have different edge and fill colors.
    marker : str, default "o"
        The matplotlib marker for the points.
    **kwargs
        Additional keyword arguments passed to matplotlib `plot` (Line2D).

    Returns
    -------
    Matplotlib artist (Line2D)
    """
    if ax is None:
        ax = _default_ax()

    coords = shapely.get_coordinates(geom)
    (line,) = ax.plot(
        coords[:, 0], coords[:, 1], linestyle="", marker=marker, color=color, **kwargs
    )
    return line
