"""
Geometry factories based on the geo interface
"""
import numpy as np

from shapely.errors import GeometryTypeError
from shapely.geometry.collection import GeometryCollection
from shapely.geometry.linestring import LineString
from shapely.geometry.multilinestring import MultiLineString
from shapely.geometry.multipoint import MultiPoint
from shapely.geometry.multipolygon import MultiPolygon
from shapely.geometry.point import Point
from shapely.geometry.polygon import LinearRing, Polygon


def _is_coordinates_empty(coordinates):
    """Helper to identify if coordinates or subset of coordinates are empty"""

    if coordinates is None:
        return True

    if isinstance(coordinates, (list, tuple, np.ndarray)):
        if len(coordinates) == 0:
            return True
        return all(map(_is_coordinates_empty, coordinates))
    else:
        return False


def _empty_shape_for_no_coordinates(geom_type):
    """Return empty counterpart for geom_type"""
    if geom_type == "point":
        return Point()
    elif geom_type == "multipoint":
        return MultiPoint()
    elif geom_type == "linestring":
        return LineString()
    elif geom_type == "multilinestring":
        return MultiLineString()
    elif geom_type == "polygon":
        return Polygon()
    elif geom_type == "multipolygon":
        return MultiPolygon()
    else:
        raise GeometryTypeError(f"Unknown geometry type: {geom_type!r}")


def box(minx, miny, maxx, maxy, ccw=True):
    """Returns a rectangular polygon with configurable normal vector"""
    coords = [(maxx, miny), (maxx, maxy), (minx, maxy), (minx, miny)]
    if not ccw:
        coords = coords[::-1]
    return Polygon(coords)


def shape(context):
    """
    Returns a new, independent geometry with coordinates *copied* from the
    context. Changes to the original context will not be reflected in the
    geometry object.

    Parameters
    ----------
    context :
        a GeoJSON-like dict, which provides a "type" member describing the type
        of the geometry and "coordinates" member providing a list of coordinates,
        or an object which implements __geo_interface__.

    Returns
    -------
    Geometry object

    Examples
    --------
    Create a Point from GeoJSON, and then create a copy using __geo_interface__.

    >>> context = {'type': 'Point', 'coordinates': [0, 1]}
    >>> geom = shape(context)
    >>> geom.geom_type == 'Point'
    True
    >>> geom.wkt
    'POINT (0 1)'
    >>> geom2 = shape(geom)
    >>> geom == geom2
    True
    """
    if hasattr(context, "__geo_interface__"):
        ob = context.__geo_interface__
    else:
        ob = context
    geom_type = ob.get("type").lower()
    if "coordinates" in ob and _is_coordinates_empty(ob["coordinates"]):
        return _empty_shape_for_no_coordinates(geom_type)
    elif geom_type == "point":
        return Point(ob["coordinates"])
    elif geom_type == "linestring":
        return LineString(ob["coordinates"])
    elif geom_type == "linearring":
        return LinearRing(ob["coordinates"])
    elif geom_type == "polygon":
        return Polygon(ob["coordinates"][0], ob["coordinates"][1:])
    elif geom_type == "multipoint":
        return MultiPoint(ob["coordinates"])
    elif geom_type == "multilinestring":
        return MultiLineString(ob["coordinates"])
    elif geom_type == "multipolygon":
        return MultiPolygon([[c[0], c[1:]] for c in ob["coordinates"]])
    elif geom_type == "geometrycollection":
        geoms = [shape(g) for g in ob.get("geometries", [])]
        return GeometryCollection(geoms)
    else:
        raise GeometryTypeError(f"Unknown geometry type: {geom_type!r}")


def mapping(ob):
    """
    Returns a GeoJSON-like mapping from a Geometry or any
    object which implements __geo_interface__

    Parameters
    ----------
    ob :
        An object which implements __geo_interface__.

    Returns
    -------
    dict

    Examples
    --------
    >>> pt = Point(0, 0)
    >>> mapping(pt)
    {'type': 'Point', 'coordinates': (0.0, 0.0)}
    """
    return ob.__geo_interface__
