from __future__ import division
import requests
import json
import pandas as pd
import numpy as np
from itertools import chain
from shapely.geometry import LineString, Point


"""
If you wish to retrieve route data directly into a DataFrame

df['travel_time_valhalla'], df['valhalla_route'] = zip(*
    df.apply(route_valhalla, args=(origin_tuple), axis=1))

"""

def query_route_valhalla(
    key,
    start,
    end,
    costing,
    language="en_US",
    out_format="json",
    direction_params=None,
    costing_params=None):
    """
    Query a Valhalla instance for a route
    Returns travel time and list of route geometries
    
    key: Valhalla API key
    start: route start coords as a lon, lat iterable
    end: route end coords as a lon, lat iterable

    Not all options have been implemented here.
    See: https://github.com/valhalla/valhalla-docs/blob/gh-pages/api-reference.md#inputs-of-a-valhalla-route
    """
    allowed = ('pedestrian', 'bicycle', 'bus', 'auto', 'auto_shorter')
    if costing not in allowed:
        raise Exception(
            "Unknown travel method. Must be one of %s. Christ." % ', '.join(allowed))
    
    # build routing JSON
    initial_route = {
        "locations": [{"lat":start[1] ,"lon": start[0]}, {"lat":end[1] ,"lon":end[0]}],
        "costing": costing,
        "language": language,
        "out_format": out_format
    }
    route = initial_route.copy()
    if direction_params:
        route.update({'directions_options': direction_params})
    if costing_params:
        route.update({'costing_options': costing_params})
    endpoint = "https://valhalla.mapzen.com/route"
    params = {"json": json.dumps(route), "api_key": key}
    req = requests.get(endpoint, params=params)
    try:
        req.raise_for_status()
    except requests.exceptions.HTTPError:
        return (np.nan, np.nan)
    if req.json()['trip']['status'] == 207:
        return (np.nan, np.nan)
    return req.json()['trip']['summary']['time'], [leg['shape'] for leg in req.json()['trip']['legs']][0]


def query_route_osrm(start, end, method):
    """
    Get a route back from MapZen's OSRM
    start, end: lon, lat tuples
    method: foot, car, bicycle
    returns encoded Polyline
    TODO: bounds checking for coords
    """
    allowed = ('foot', 'car', 'bicycle')
    if method not in allowed:
        raise Exception(
            "Unknown method. Must be one of %s. Christ." % ', '.join(allowed))
    endpoint = 'http://osrm.mapzen.com'
    method = '/{m}/viaroute'.format(m=method)
    # should be properly encoding second loc, but dict keys are unique!
    # reverse lon, lat because ugh    
    params = {'loc': '{1},{0}&loc={3},{2}'.format(*chain(start, end))}
    req = requests.get(endpoint + method, params=params)
    try:
        req.raise_for_status()
    except requests.exceptions.HTTPError:
        return (np.nan, np.nan)
    if req.json().get('status') == 207:
        return np.nan, np.nan
    return req.json()['route_summary']['total_time'], req.json()['route_geometry']


def query_route_gmaps(start, end, method, key):
    """ retrieve a bike route from GMaps """
    url = "https://maps.googleapis.com/maps/api/directions/json"
    params = {
        "origin": "%s, %s" % (start[1], start[0]),
        "destination": "%s, %s" % (end[1], end[0]),
        "mode": method,
        "units": "metric",
        "region": "uk",
        "key": key

    }
    req = requests.get(url, params=params)
    try:
        req.raise_for_status()
    except requests.exceptions.HTTPError:
        return (np.nan, np.nan)
    # currently one route, containing one leg
    try:
        route = req.json()['routes'][0]
        leg = req.json()['routes'][0]['legs'][0]
        duration = sum([step['duration']['value'] for step in leg['steps']])
        overview_polyline = route['overview_polyline']['points']
    except (KeyError, IndexError):
        return (np.nan, np.nan)
    return duration, overview_polyline


def query_elevation(polyline, key):
    """ retrieve elevations for a polyline from GMaps """
    url = "https://maps.googleapis.com/maps/api/elevation/json"
    params = {
        "locations": "enc:%s" % polyline,
        "key": key
    }
    req = requests.get(url, params=params)
    try:
        req.raise_for_status()
    except requests.exceptions.HTTPError:
        return np.nan
    try:
        elevations = req.json()['results']
    except (KeyError, IndexError):
        return np.nan
    return elevations


def route_valhalla(df, start):
    return query_route_valhalla(api_key, start, (df['lon'], df['lat']), 'bicycle')


def route_gmaps(df, start):
    return query_route_gmaps(start, (df['lon'], df['lat']), 'bicycling', gmaps_key)


def route_osrm(df, start):
    return query_route_osrm(start, (df['lon'], df['lat']), 'bicycle')


def decode_polyline(point_str, gmaps=False):
    """
    Decodes a polyline that has been encoded using Google's algorithm
    http://code.google.com/apis/maps/documentation/polylinealgorithm.html
    
    This is a generic method that returns a list of (lon, lat) 
    tuples, which are used as input to a Shapely LineString
    
    point_str: encoded polyline string