# coding: utf-8
"""
Weather - query weather underground for info
Copyright (C) 2010  Brian S. Stephan

This program 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.

This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
"""

import json
import MySQLdb as mdb
import re
import urllib2

from Module import Module

class Weather(Module):
    """Provide weather lookup services to the bot."""

    def __init__(self, irc, config):
        """Set up regexes and wunderground API."""

        Module.__init__(self, irc, config)

        self.weatherre = re.compile("^!weather\s+(.*)$")

        # get the API key from config
        api_key = self._get_api_key_from_db()
        if api_key is None:
            raise Exception("weather underground API key not found!")

        self.wu_base = 'http://api.wunderground.com/api/{0:s}/'.format(api_key)

    def db_init(self):
        """Initialize the tiny table that stores the API key."""

        version = self.db_module_registered(self.__class__.__name__)
        if version == None:
            db = self.get_db()
            try:
                version = 1
                cur = db.cursor(mdb.cursors.DictCursor)
                cur.execute('''
                    CREATE TABLE weather_settings (
                        api_key VARCHAR(256) NOT NULL
                    )
                    ''')

                db.commit()
                self.db_register_module_version(self.__class__.__name__, version)
            except mdb.Error as e:
                db.rollback()
                self.log.error("database error trying to create tables")
                self.log.exception(e)
                raise
            finally: cur.close()

    def do(self, connection, event, nick, userhost, what, admin_unlocked):
        """Handle IRC input for a weather queries."""

        match = self.weatherre.search(what)
        if match:
            # see if there was a specific command given in the query, first
            query = match.group(1)
            queryitems = query.split(" ")
            if len(queryitems) <= 0:
                return

            # search for commands
            if queryitems[0] == "conditions":
                # current weather query
                results = self.get_conditions_for_query(queryitems[1:])
                return self.irc.reply(event, results)
            elif queryitems[0] == "forecast":
                # forecast query
                results = self.get_forecast_for_query(queryitems[1:])
                return self.irc.reply(event, results)
            else:
                # assume they wanted current weather
                results = self.get_conditions_for_query(queryitems)
                return self.irc.reply(event, results)

    def get_conditions_for_query(self, queryitems):
        """Make a wunderground conditions call, return as string."""

        # recombine the query into a string
        query = ' '.join(queryitems)
        query = query.replace(' ', '_')

        try:
            url = self.wu_base + ('{0:s}/q/{1:s}.json'.format('conditions',
                                                              query))
            json_resp = urllib2.urlopen(url)
            condition_data = json.load(json_resp)
        except IOError as e:
            self.log.error("error while making conditions query")
            self.log.exception(e)
            raise

        # condition data is loaded. the rest of this is obviously specific to
        # http://www.wunderground.com/weather/api/d/docs?d=data/conditions
        self.log.debug(json.dumps(condition_data, sort_keys=True, indent=4))

        try:
            # just see if we have current_observation data
            current = condition_data['current_observation']
        except KeyError as e:
            # ok, try to see if the ambiguous results stuff will help
            self.log.debug("potentially ambiguous results, checking")
            try:
                results = condition_data['response']['results']
                reply = "Multiple results, try one of the following zmw codes:"
                for res in results[:-1]:
                    q = res['l'].strip('/q/')
                    reply += " {0:s} ({1:s}, {2:s}),".format(q, res['name'],
                                                             res['country_name'])
                q = results[-1]['l'].strip('/q/')
                reply += " or {0:s} ({1:s}, {2:s}).".format(q, results[-1]['name'],
                                                            results[-1]['country_name'])
                return reply
            except KeyError as e:
                # now we really know something is wrong
                self.log.error("error or bad query in conditions lookup")
                self.log.exception(e)
                return "No results."
        else:
            try:
                location = current['display_location']['full']
                reply = "Conditions for {0:s}: ".format(location)

                weather_str = current['weather']
                if weather_str != '':
                    reply += "{0:s}, ".format(weather_str)

                temp_f = current['temp_f']
                temp_c = current['temp_c']
                temp_str = current['temperature_string']
                if temp_f != '' and temp_c != '':
                    reply += "{0:.1f}°F ({1:.1f}°C)".format(temp_f, temp_c)
                elif temp_str != '':
                    reply += "{0:s}".format(temp_str)

                # append feels like if we have it
                feelslike_f = current['feelslike_f']
                feelslike_c = current['feelslike_c']
                feelslike_str = current['feelslike_string']
                if feelslike_f != '' and feelslike_c != '':
                    reply += ", feels like {0:s}°F ({1:s}°C)".format(feelslike_f, feelslike_c)
                elif feelslike_str != '':
                    reply += ", feels like {0:s}".format(feelslike_str)

                # whether this is current or current + feelslike, terminate sentence
                reply += ". "

                humidity_str = current['relative_humidity']
                if humidity_str != '':
                    reply += "Humidity: {0:s}. ".format(humidity_str)

                wind_str = current['wind_string']
                if wind_str != '':
                    reply += "Wind: {0:s}. ".format(wind_str)

                pressure_in = current['pressure_in']
                pressure_trend = current['pressure_trend']
                if pressure_in != '':
                    reply += "Pressure: {0:s}\"".format(pressure_in)
                    if pressure_trend != '':
                        reply += " and {0:s}".format("dropping" if pressure_trend == '-' else "rising")
                    reply += ". "

                heat_index_str = current['heat_index_string']
                if heat_index_str != '' and heat_index_str != 'NA':
                    reply += "Heat index: {0:s}. ".format(heat_index_str)

                windchill_str = current['windchill_string']
                if windchill_str != '' and windchill_str != 'NA':
                    reply += "Wind chill: {0:s}. ".format(windchill_str)

                visibility_mi = current['visibility_mi']
                if visibility_mi != '':
                    reply += "Visibility: {0:s} miles. ".format(visibility_mi)

                precip_in = current['precip_today_in']
                if precip_in != '':
                    reply += "Precipitation today: {0:s}\". ".format(precip_in)

                observation_time = current['observation_time']
                if observation_time != '':
                    reply += "{0:s}. ".format(observation_time)

                return self._prettify_weather_strings(reply.rstrip())
            except KeyError as e:
                self.log.error("error or unexpected results in conditions reply")
                self.log.exception(e)
                return "Error parsing results."

    def get_forecast_for_query(self, queryitems):
        """Make a wunderground forecast call, return as string."""

        # recombine the query into a string
        query = ' '.join(queryitems)
        query = query.replace(' ', '_')

        try:
            url = self.wu_base + ('{0:s}/q/{1:s}.json'.format('forecast',
                                                              query))
            json_resp = urllib2.urlopen(url)
            forecast_data = json.load(json_resp)
        except IOError as e:
            self.log.error("error while making forecast query")
            self.log.exception(e)
            raise

        # forecast data is loaded. the rest of this is obviously specific to
        # http://www.wunderground.com/weather/api/d/docs?d=data/forecast
        self.log.debug(json.dumps(forecast_data, sort_keys=True, indent=4))

        try:
            # just see if we have forecast data
            forecasts = forecast_data['forecast']['txt_forecast']
        except KeyError as e:
            # ok, try to see if the ambiguous results stuff will help
            self.log.debug("potentially ambiguous results, checking")
            try:
                results = forecast_data['response']['results']
                reply = "Multiple results, try one of the following zmw codes:"
                for res in results[:-1]:
                    q = res['l'].strip('/q/')
                    reply += " {0:s} ({1:s}, {2:s}),".format(q, res['name'],
                                                             res['country_name'])
                q = results[-1]['l'].strip('/q/')
                reply += " or {0:s} ({1:s}, {2:s}).".format(q, results[-1]['name'],
                                                            results[-1]['country_name'])
                return reply
            except KeyError as e:
                # now we really know something is wrong
                self.log.error("error or bad query in forecast lookup")
                self.log.exception(e)
                return "No results."
        else:
            try:
                reply = "Forecast: "
                for forecast in forecasts['forecastday'][0:5]:
                    reply += "{0:s}: {1:s} ".format(forecast['title'],
                                                        forecast['fcttext'])

                return self._prettify_weather_strings(reply.rstrip())
            except KeyError as e:
                self.log.error("error or unexpected results in forecast reply")
                self.log.exception(e)
                return "Error parsing results."

    def _get_api_key_from_db(self):
        """Get the API key string from the database, or None if unset."""

        api_key = None
        db = self.get_db()
        try:
            cur = db.cursor(mdb.cursors.DictCursor)
            query = '''SELECT api_key FROM weather_settings'''
            cur.execute(query)
            value = cur.fetchone()
            if (value != None):
                api_key = value['api_key']
        except mdb.Error as e:
            self.log.error("database error during api key retrieval")
            self.log.exception(e)
        finally: cur.close()

        return api_key

    def _prettify_weather_strings(self, weather_str):
        """
        Clean up output strings.

        For example, turn 32F into 32°F in input string.

        Input:
            weather_str --- the string to clean up

        """

        return re.sub(r'(\d+)\s*([FC])', r'\1°\2', weather_str)

# vi:tabstop=4:expandtab:autoindent
# kate: indent-mode python;indent-width 4;replace-tabs on;