#!/usr/bin/python2
# coding: utf8

from __future__ import absolute_import
import six
from geocoder.base import OneResult, MultipleResultsQuery
from geocoder.keys import google_key, google_client, google_client_secret
from collections import OrderedDict
import ratelim


class GoogleResult(OneResult):

    def __init__(self, json_content):
        # flatten geometry
        geometry = json_content.get('geometry', {})
        self._location = geometry.get('location', {})
        self._location_type = geometry.get('location_type', {})
        self._viewport = geometry.get('viewport', {})

        # Parse address components with short & long names
        for item in json_content['address_components']:
            for category in item['types']:
                json_content.setdefault(category, {})
                json_content[category]['long_name'] = item['long_name']
                json_content[category]['short_name'] = item['short_name']

        # proceed with super.__init__
        super(GoogleResult, self).__init__(json_content)

    @property
    def lat(self):
        return self._location.get('lat')

    @property
    def lng(self):
        return self._location.get('lng')

    @property
    def place(self):
        return self.raw.get('place_id')

    @property
    def quality(self):
        quality = self.raw.get('types')
        if quality:
            return quality[0]

    @property
    def accuracy(self):
        return self._location_type

    @property
    def bbox(self):
        south = self._viewport.get('southwest', {}).get('lat')
        west = self._viewport.get('southwest', {}).get('lng')
        north = self._viewport.get('northeast', {}).get('lat')
        east = self._viewport.get('northeast', {}).get('lng')
        return self._get_bbox(south, west, north, east)

    @property
    def address(self):
        return self.raw.get('formatted_address')

    @property
    def postal(self):
        return self.raw.get('postal_code', {}).get('short_name')

    @property
    def subpremise(self):
        return self.raw.get('subpremise', {}).get('short_name')

    @property
    def housenumber(self):
        return self.raw.get('street_number', {}).get('short_name')

    @property
    def street(self):
        return self.raw.get('route', {}).get('short_name')

    @property
    def street_long(self):
        return self.raw.get('route', {}).get('long_name')

    @property
    def road_long(self):
        return self.street_long

    @property
    def neighborhood(self):
        return self.raw.get('neighborhood', {}).get('short_name')

    @property
    def sublocality(self):
        return self.raw.get('sublocality', {}).get('short_name')

    @property
    def city(self):
        return self.raw.get('locality', {}).get('short_name') or self.postal_town

    @property
    def city_long(self):
        return self.raw.get('locality', {}).get('long_name') or self.postal_town_long

    @property
    def postal_town(self):
        return self.raw.get('postal_town', {}).get('short_name')

    @property
    def postal_town_long(self):
        return self.raw.get('postal_town', {}).get('long_name')

    @property
    def county(self):
        return self.raw.get('administrative_area_level_2', {}).get('short_name')

    @property
    def state(self):
        return self.raw.get('administrative_area_level_1', {}).get('short_name')

    @property
    def state_long(self):
        return self.raw.get('administrative_area_level_1', {}).get('long_name')

    @property
    def province_long(self):
        return self.state_long

    @property
    def country(self):
        return self.raw.get('country', {}).get('short_name')

    @property
    def country_long(self):
        return self.raw.get('country', {}).get('long_name')


class GoogleQuery(MultipleResultsQuery):
    """
    Google Geocoding API
    ====================
    Geocoding is the process of converting addresses into geographic
    coordinates (like latitude 37.423021 and longitude -122.083739),
    which you can use to place markers or position the map.
    API Reference
    -------------
    https://developers.google.com/maps/documentation/geocoding

    For ambiguous queries or 'nearby' type queries, use the Places Text Search instead.
    https://developers.google.com/maps/documentation/geocoding/best-practices#automated-system

    Parameters
    ----------
    :param location: Your search location you want geocoded.
    :param components: Component Filtering
    :param method: (default=geocode) Use the following:
        > geocode
        > places
        > reverse
        > timezone
        > elevation
    :param key: Your Google developers free key.
    :param language: 2-letter code of preferred language of returned address elements.
    :param client: Google for Work client ID. Use with client_secret. Cannot use with key parameter
    :param client_secret: Google for Work client secret. Use with client.
    """
    provider = 'google'
    method = 'geocode'

    _URL = 'https://maps.googleapis.com/maps/api/geocode/json'
    _RESULT_CLASS = GoogleResult
    _KEY = google_key
    _KEY_MANDATORY = False

    def _build_params(self, location, provider_key, **kwargs):
        params = self._location_init(location, **kwargs)
        params['language'] = kwargs.get('language', '')
        self.rate_limit = kwargs.get('rate_limit', True)

        # adapt params to authentication method
        # either with client / secret
        self.client = kwargs.get('client', google_client)
        self.client_secret = kwargs.get('client_secret', google_client_secret)

        if self.client and self.client_secret:
            params['client'] = self.client
            return self._encode_params(params)
        # or API key
        else:
            # provider_key is computed in base.py:
            # either cls._KEY (google_key) or kwargs['key'] if provided
            params['key'] = provider_key
            return params

    def _location_init(self, location, **kwargs):
        return {
            'address': location,
            'bounds': kwargs.get('bounds', ''),
            'components': kwargs.get('components', ''),
            'region': kwargs.get('region', ''),
        }

    def _encode_params(self, params):
        # turn non-empty params into sorted list in order to maintain signature validity.
        # Requests will honor the order.
        ordered_params = sorted([(k, v)
                                 for (k, v) in params.items() if v])
        params = OrderedDict(ordered_params)

        # the signature parameter needs to come in the end of the url
        params['signature'] = self._sign_url(
            self.url, ordered_params, self.client_secret)

        return params

    def _sign_url(self, base_url=None, params=None, client_secret=None):
        """ Sign a request URL with a Crypto Key.
        Usage:
        from urlsigner import sign_url
        signed_url = sign_url(base_url=my_url,
                              params=url_params,
                              client_secret=CLIENT_SECRET)
        Args:
        base_url - The trunk of the URL to sign. E.g. https://maps.googleapis.com/maps/api/geocode/json
        params - List of tuples of URL parameters INCLUDING YOUR CLIENT ID ('client','gme-...')
        client_secret - Your Crypto Key from Google for Work
        Returns:
        The signature as a dictionary #signed request URL
        """
        import hashlib
        import hmac
        import base64
        if six.PY3:
            from urllib.parse import urlparse, urlencode
        else:
            from urllib import urlencode
            from urlparse import urlparse

        # Return if any parameters aren't given
        if not base_url or not self.client_secret or not self.client:
            return None

        # assuming parameters will be submitted to Requests in identical order!
        url = urlparse(base_url + "?" + urlencode(params))

        # We only need to sign the path+query part of the string
        url_to_sign = (url.path + "?" + url.query).encode('utf-8')

        # Decode the private key into its binary format
        # We need to decode the URL-encoded private key
        decoded_key = base64.urlsafe_b64decode(client_secret)

        # Create a signature using the private key and the URL-encoded
        # string using HMAC SHA1. This signature will be binary.
        signature = hmac.new(decoded_key, url_to_sign, hashlib.sha1)

        # Encode the binary signature into base64 for use within a URL
        encoded_signature = base64.urlsafe_b64encode(signature.digest())

        # Return signature (to be appended as a 'signature' in params)
        return encoded_signature

    def rate_limited_get(self, *args, **kwargs):
        if not self.rate_limit:
            return super(GoogleQuery, self).rate_limited_get(*args, **kwargs)
        elif self.client and self.client_secret:
            return self.rate_limited_get_for_work(*args, **kwargs)
        else:
            return self.rate_limited_get_for_dev(*args, **kwargs)

    @ratelim.greedy(2500, 60 * 60 * 24)
    @ratelim.greedy(10, 1)
    def rate_limited_get_for_dev(self, *args, **kwargs):
        return super(GoogleQuery, self).rate_limited_get(*args, **kwargs)

    @ratelim.greedy(100000, 60 * 60 * 24)  # Google for Work daily limit
    @ratelim.greedy(50, 1)  # Google for Work limit per second
    def rate_limited_get_for_work(self, *args, **kwargs):
        return super(GoogleQuery, self).rate_limited_get(*args, **kwargs)

    def _catch_errors(self, json_response):
        status = json_response.get('status')
        if not status == 'OK':
            self.error = status

        return self.error

    def _adapt_results(self, json_response):
        return json_response.get('results', [])


if __name__ == '__main__':
    g = GoogleQuery('11 Wall Street, New York')
    g.debug()
