import alphashape
import geopandas as gpd
from shapely.geometry import Polygon
import rasterio
from rasterio.transform import from_origin
import math
import numpy as np

from ..plotting import subplots, show

def concave_hull(coords, alpha, plot=True):
    """
    Generate a concave hull (alphashape) from a set of coordinates.
    Parameters:
    - coords: List of (x, y) tuples representing the points.
    - alpha: Alpha parameter controlling the concavity. Smaller values result in a more detailed hull, while larger values produce a more convex hull.
    - plot: Boolean indicating whether to display the plot of the hull.
    Returns:
    - A shapely geometry representing the concave hull.
    """
    hull = alphashape.alphashape(coords, alpha)
    if plot:
        hull_gdf = gpd.GeoDataFrame(geometry=[hull])
        fig, ax = subplots(figwidth=10, figheight=10)
        ax.scatter(
            [x for x, y in coords],
            [y for x, y in coords],
            s=1, color="steelblue", alpha=0.5, zorder=1
        )

        # Alphashape
        hull_gdf.plot(ax=ax, facecolor="none", edgecolor="crimson", linewidth=2, linestyle="--", zorder=2)
        show()

    return hull


def bbox2shp(xmin, xmax, ymin, ymax, output_fname, epsg=4326):
    """
    Create a shapefile representing a bounding box.
    Parameters:
    - xmin, xmax, ymin, ymax: Coordinates defining the bounding box.
    - output_fname: Path to the output shapefile.
    """
    bbox = Polygon([
        (xmin, ymin),
        (xmin, ymax),
        (xmax, ymax),
        (xmax, ymin),
        (xmin, ymin)
    ])
    gdf = gpd.GeoDataFrame(geometry=[bbox], crs=f"EPSG:{epsg}")
    gdf.to_file(output_fname)


def make_snap_raster(xmin, xmax, ymin, ymax, epsg, res, output_fname, verbose=True):
    """
    Create a GeoTIFF whose pixels are all 1, to serve as a snap/reference raster.
 
    The snap grid is defined by the origin (xmin, ymax) and the cell size, so the
    extent is expanded up/right to the next whole cell if the bbox is not an exact
    multiple of the resolution (the raster always fully covers the requested bbox).
    """
    if xmax <= xmin or ymax <= ymin:
        raise ValueError("Require xmax > xmin and ymax > ymin.")
    if res <= 0:
        raise ValueError("Resolution must be positive.")
 
    # Cover the bbox with a whole number of cells, anchored at (xmin, ymax).
    ncols = int(math.ceil((xmax - xmin) / res))
    nrows = int(math.ceil((ymax - ymin) / res))
 
    transform = from_origin(xmin, ymax, res, res)  # top-left origin, N-up
    data = np.ones((nrows, ncols), dtype="uint8")
 
    profile = {
        "driver": "GTiff",
        "height": nrows,
        "width": ncols,
        "count": 1,
        "dtype": "uint8",
        "crs": rasterio.crs.CRS.from_epsg(epsg),
        "transform": transform,
        "compress": "lzw",
        "tiled": True,
    }
 
    with rasterio.open(output_fname, "w", **profile) as dst:
        dst.write(data, 1)

    if verbose:
        print(f"Wrote {output_fname}")
        print(f"  size   : {ncols} cols x {nrows} rows")
        print(f"  origin : ({xmin}, {ymax})  cell size: {res}")
        print(f"  extent : x [{xmin}, {xmin + ncols * res}]  "
              f"y [{ymax - nrows * res}, {ymax}]")
        print(f"  EPSG   : {epsg}")