#!/usr/bin/env python3

# Copyright 2013 David Walker
#
# This file is part of Pollution Music.
#
# Pollution Music is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# Pollution Music is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# Pollution Music. If not, see <http://www.gnu.org/licenses/>.

import calendar
from collections import defaultdict
import datetime
import json
import time
import urllib.request, urllib.error, urllib.parse


class WeatherData(object):
    '''Caching front-end to weather data API.'''
    # minimum time to wait, in seconds, between calls to the weather api
    MIN_TIME_BETWEEN_CALLS = 12.0

    def __init__(self, location, api_key):
        '''Init with location about which to retrieve weather data.

        Args:
            location: Must be a string in form "state_or_country_code/city",
              e.g., "CA/San_Francisco" or "China/Beijing".
            api_key: issued by wunderground
        '''
        # this object stores weather data for only one location
        self._location = location
        self._url_prefix = 'http://api.wunderground.com/api/' + api_key + '/'
        # for self-throttling to avoid exceeding calls/min limit
        self._time_of_last_call = None
        # cached weather observations. When populated, the expression
        # self._wx[year][month][day][hour] (keys are of type int) will give a
        # comma-separated-value string with the readings for temperature, wind
        # speed, and so on.
        self._wx = defaultdict(
            lambda: defaultdict(
                lambda: defaultdict(
                    lambda: defaultdict(str))))
        # empty csv to return when an observation is not available
        self._no_data = ',,,,'

    def _get_history(self, dt):
        '''Ask the weather API for the history of observations for the given
        date, and return the observation for the given hour on that date.

        Args:
            dt: datetime instance with values for year, month, day, and hour

        Returns:
            A string containing weather observations separated by commas.
        '''
        self._throttle()
        url = (self._url_prefix +
               'history_{}{:02}{:02}/q/{}.json'.format(
                   dt.year, dt.month, dt.day, self._location))
        h = json.loads(urllib.request.urlopen(url).read().decode())
        for ob in h['history']['observations']:
            if ob['date']['min'] == '00':
                hour = int(ob['date']['hour'])
                self._wx[dt.year][dt.month][dt.day][hour] = ','.join([
                    ob['tempm'],
                    ob['hum'],
                    ob['wspdm'],
                    ob['wdird'],
                    ob['pressurem']])
        ob = self._wx[dt.year][dt.month][dt.day][dt.hour]
        if ob == '':
            ob = self._wx[dt.year][dt.month][dt.day][dt.hour] = self._no_data
        return ob

    def _throttle(self):
        '''Record the time of this call, first sleeping if necessary to avoid
        exceeding the call/minute limit for the weather api.'''
        if self._time_of_last_call is not None:
            time_since_last_call = time.time() - self._time_of_last_call
            if time_since_last_call < WeatherData.MIN_TIME_BETWEEN_CALLS:
                time.sleep(WeatherData.MIN_TIME_BETWEEN_CALLS -
                           time_since_last_call)
        self._time_of_last_call = time.time()

    def get_observation(self, dt):
        '''Return a comma-delimited string containing weather observations for
        the date and hour specified by dt.'''
        ob = self._wx[dt.year][dt.month][dt.day][dt.hour]
        if ob == '':
            # Never fetched this data, ask the API for it.
            ob = self._get_history(dt)
        return ob

    def get_cur_conditions(self):
        self._throttle()
        url = (self._url_prefix + 'geolookup/conditions/q/' + self._location +
               '.json')
        return json.loads(urllib.request.urlopen(url).read().decode())


def main():
    WeatherData('China/Beijing', WUNDERGROUND_API_KEY)


if __name__ == '__main__':
    main()
