import json

from .errors import InvalidFeatureError
import polyline


def _geom_points(geom):
    """GeoJSON geometry to a sequence of point tuples
    """
    if geom['type'] == 'Point':
        yield tuple(geom['coordinates'])
    elif geom['type'] in ('MultiPoint', 'LineString'):
        for position in geom['coordinates']:
            yield tuple(position)
    else:
        raise InvalidFeatureError(
            "Unsupported geometry type:{0}".format(geom['type']))


def read_points(features):
    """ Iterable of features to a sequence of point tuples
    Where "features" can be either GeoJSON mappings
    or objects implementing the geo_interface
    """
    for feature in features:

        if isinstance(feature, (tuple, list)) and len(feature) == 2:
            yield feature

        elif hasattr(feature, '__geo_interface__'):
            # An object implementing the geo_interface
            try:
                # Could be a Feature...
                geom = feature.__geo_interface__['geometry']
                for pt in _geom_points(geom):
                    yield pt
            except KeyError:
                # ... or a geometry directly
                for pt in _geom_points(feature.__geo_interface__):
                    yield pt

        elif 'type' in feature and feature['type'] == 'Feature':
            # A GeoJSON-like mapping
            geom = feature['geometry']
            for pt in _geom_points(geom):
                yield pt

        elif 'coordinates' in feature:
            geom = feature
            for pt in _geom_points(geom):
                yield pt

        else:
            raise InvalidFeatureError(
                "Unknown object: Not a GeoJSON Point feature or "
                "an object with __geo_interface__:\n{0}".format(feature))


def encode_waypoints(features, min_limit=None, max_limit=None, precision=6):
    """Given an iterable of features
    return a string encoded in waypoint-style used by certain mapbox APIs
    ("lon,lat" pairs separated by ";")
    """
    coords = ['{lon},{lat}'.format(
                  lon=float(round(lon, precision)),
                  lat=float(round(lat, precision)))
              for lon, lat in read_points(features)]

    if min_limit is not None and len(coords) < min_limit:
        raise InvalidFeatureError(
            "Not enough features to encode coordinates, "
            "need at least {0}".format(min_limit))
    if max_limit is not None and len(coords) > max_limit:
        raise InvalidFeatureError(
            "Too many features to encode coordinates, "
            "need at most {0}".format(max_limit))

    return ';'.join(coords)


def encode_polyline(features):
    """Encode and iterable of features as a polyline
    """
    points = list(read_points(features))
    latlon_points = [(x[1], x[0]) for x in points]
    return polyline.encode(latlon_points)


def encode_coordinates_json(features):
    """Given an iterable of features
    return a JSON string to be used as the request body for the distance API:
    a JSON object, with a key coordinates,
    which has an array of [ Longitude, Lattitude ] pairs
    """
    coords = {
        'coordinates': list(read_points(features))}
    return json.dumps(coords)
