#! /usr/bin/python3
# -*- coding: utf-8 -*-

# Copyright (C) 2013 David Callé <davidc@framli.eu>
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranties of
# MERCHANTABILITY, SATISFACTORY QUALITY, 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/>.

from gi.repository import Unity, UnityExtras
from gi.repository import Gio, GLib
import urllib.parse
import urllib.request
import json
import datetime
import gettext
from random import randrange

APP_NAME = 'unity-scope-openweathermap'
LOCAL_PATH = '/usr/share/locale/'
gettext.bindtextdomain(APP_NAME, LOCAL_PATH)
gettext.textdomain(APP_NAME)
_ = gettext.gettext

GROUP_NAME = 'com.canonical.Unity.Scope.Info.Openweathermap'
UNIQUE_PATH = '/com/canonical/unity/scope/info/openweathermap'
SEARCH_URI = ['http://openweathermap.org/data/2.1/find/',
              'http://openweathermap.org/data/2.1/forecast/city/']
SEARCH_HINT = _('Search OpenWeatherMap')
NO_RESULTS_HINT = _('Sorry, there is no OpenwWeatherMap forecast that matches your search.')
PROVIDER_CREDITS = _('Powered by OpenWeatherMap')
SVG_DIR = '/usr/share/icons/unity-icon-theme/places/svg/'
PROVIDER_ICON = SVG_DIR+'service-openweathermap.svg'
DEFAULT_RESULT_ICON = SVG_DIR+'result-info.svg'
DEFAULT_RESULT_MIMETYPE = 'text/html'
DEFAULT_RESULT_TYPE = Unity.ResultType.DEFAULT

c1 = {'id'      :'weather',
      'name'    :_('Weather Forecast'),
      'icon'    :SVG_DIR+'group-info.svg',
      'renderer':Unity.CategoryRenderer.VERTICAL_TILE}
CATEGORIES = [c1]
FILTERS = []
m1 = {'id'   :'min_temp',
      'type' :'s',
      'field':Unity.SchemaFieldType.OPTIONAL}
m2 = {'id'   :'max_temp',
      'type' :'s',
      'field':Unity.SchemaFieldType.OPTIONAL}
m3 = {'id'   :'pressure',
      'type' :'s',
      'field':Unity.SchemaFieldType.OPTIONAL}
m4 = {'id'   :'humidity',
      'type' :'s',
      'field':Unity.SchemaFieldType.OPTIONAL}
m5 = {'id'   :'wind_speed',
      'type' :'s',
      'field':Unity.SchemaFieldType.OPTIONAL}
m6 = {'id'   :'wind_direction',
      'type' :'s',
      'field':Unity.SchemaFieldType.OPTIONAL}
m7 = {'id'   :'latitude',
      'type' :'s',
      'field':Unity.SchemaFieldType.OPTIONAL}
m8 = {'id'   :'longitude',
      'type' :'s',
      'field':Unity.SchemaFieldType.OPTIONAL}
m9 = {'id'   :'photo_hint',
      'type' :'i',
      'field':Unity.SchemaFieldType.OPTIONAL}
EXTRA_METADATA = [m1, m2, m3, m4, m5, m6, m7, m8, m9]

def weather_search(query, s_type):
    print (query)
    query = urllib.parse.quote(str(query))
    data = None
    if not query or len(query) <= 1:
        return data
    if s_type == 'forecast':
        uri = "%s%s?mode=daily_compact&units=metric" % (SEARCH_URI[1], query)
    else:
        uri = "%sname?units=metric&type=like&q=%s" % (SEARCH_URI[0], query)
    print (uri)
    try:
        response = urllib.request.urlopen(uri).read()
        data = json.loads(response.decode('utf8'))
    except Exception as error:
        print (error)
    return data

def get_icon(code):
    icons_map={'01d':'weather-clear',
               '01n':'weather-clear-night',
               '02d':'weather-few-clouds',
               '02n':'weather-few-clouds-night',
               '03d':'weather-clouds',
               '03n':'weather-clouds-night',
               '04d':'weather-overcast',
               '04n':'weather-overcast',
               '09d':'weather-showers-scattered',
               '09n':'weather-showers-scattered',
               '10d':'weather-showers',
               '10n':'weather-showers',
               '11d':'weather-storm',
               '11n':'weather-storm',
               '13d':'weather-snow',
               '13n':'weather-snow',
               '50d':'weather-fog',
               '50n':'weather-fog'}
    return icons_map[code]

def search(search, filters):
    '''
    Any search method returning results as a list of tuples.
    Available tuple fields:
    uri (string)
    icon (string)
    title (string)
    comment (string)
    dnd_uri (string)
    mimetype (string)
    category (int)
    result_type (Unity ResultType)
    extras metadata fields (variant)
    '''
    results = []
    place_data = weather_search(search, 'place')
    if not place_data or not "list" in place_data:
        return results
    uri, city_id, title, wind, icon, description = None, None, None, None, None, None
    try:
        description = place_data['list'][0]['weather'][0]['description']
        icon = get_icon(place_data['list'][0]['weather'][0]['icon'])
        wind =  place_data['list'][0]['wind']['speed']
        temp_c = float(place_data['list'][0]['main']['temp'])
        temp_f = temp_c*(9.0/5.0)+32
        temp = '%i%sC / %i%sF' % (int(temp_c),  "\u00B0", int(temp_f),  "\u00B0")
        name = place_data['list'][0]['name']
        country = place_data['list'][0]['sys']['country']
        title = name + ', ' + country + '\n' + temp
        city_id = place_data['list'][0]['id']
        uri = place_data['list'][0]['url']
        min_temp_c = float(place_data['list'][0]['main']['temp_min'])
        min_temp_f = min_temp_c*(9.0/5.0)+32
        min_temp = '%i%sC / %i%sF' % (int(min_temp_c),  "\u00B0", int(min_temp_f),  "\u00B0")
        max_temp_c = float(place_data['list'][0]['main']['temp_max'])
        max_temp_f = max_temp_c*(9.0/5.0)+32
        max_temp = '%i%sC / %i%sF' % (int(max_temp_c),  "\u00B0", int(max_temp_f),  "\u00B0")
        try:
            pressure = place_data['list'][0]['main']['pressure']
        except:
            pressure = ''
        try:
            humidity = place_data['list'][0]['main']['humidity']
        except:
            humidity = ''
        wind_speed = place_data['list'][0]['wind']['speed']
        wind_direction = place_data['list'][0]['wind']['deg']
        latitude = place_data['list'][0]['coord']['lat']
        longitude = place_data['list'][0]['coord']['lon']
    except Exception as error:
        print(error)
        return results
    if not uri or not city_id:
        return results
    photo_hint = randrange(20)
    results.append({'uri':uri,
                    'icon':icon,
                    'title':title,
                    'comment':description,
                    'min_temp':min_temp,
                    'max_temp':max_temp,
                    'pressure':str(pressure),
                    'humidity':str(humidity),
                    'wind_speed':str(wind_speed),
                    'wind_direction':str(wind_direction),
                    'latitude':str(latitude),
                    'longitude':str(longitude),
                    'photo_hint':photo_hint})
    forecast = weather_search(city_id, 'forecast')
    if not forecast or not "list" in forecast:
        return results
    today = datetime.datetime.now().today()
    for i in range(0,4):
        if i == 0:
            day_name = _('Tomorrow')
        else:
            day = today + datetime.timedelta(days=i+1)
            day_name = day.strftime("%A")
        title, wind, icon, description = None, None, None, None
        try:
            description = forecast['list'][i]['weather'][0]['description']
            icon = get_icon(forecast['list'][i]['weather'][0]['icon'])
            temp_c = float(forecast['list'][i]['temp'])
            temp_f = temp_c*(9.0/5.0)+32
            temp = '%i%sC / %i%sF' % (int(temp_c),  "\u00B0", int(temp_f),  "\u00B0")
            title = day_name + '\n' + temp
            min_temp_c = float(forecast['list'][i]['morn'])
            min_temp_f = min_temp_c*(9.0/5.0)+32
            min_temp = '%i%sC / %i%sF' % (int(min_temp_c),  "\u00B0", int(min_temp_f),  "\u00B0")
            max_temp_c = float(forecast['list'][i]['eve'])
            max_temp_f = max_temp_c*(9.0/5.0)+32
            max_temp = '%i%sC / %i%sF' % (int(max_temp_c),  "\u00B0", int(max_temp_f),  "\u00B0")
            pressure = forecast['list'][i]['pressure']
            humidity = forecast['list'][i]['humidity']
            wind_speed = forecast['list'][i]['speed']
            wind_direction = forecast['list'][i]['deg']
        except Exception as error:
            print(error)
            return results
        uri += "#%s" % day_name
        photo_hint = randrange(20)
        results.append({'uri':uri,
                        'icon':icon,
                        'title':title,
                        'comment':description,
                        'min_temp':min_temp,
                        'max_temp':max_temp,
                        'pressure':str(pressure),
                        'humidity':str(humidity),
                        'wind_speed':str(wind_speed),
                        'wind_direction':str(wind_direction),
                        'latitude':str(latitude),
                        'longitude':str(longitude),
                        'photo_hint':photo_hint})
    return results


# Classes below this point establish communication
# with Unity, you probably shouldn't modify them.


class MySearch (Unity.ScopeSearchBase):
    def __init__(self, search_context):
        super (MySearch, self).__init__()
        self.set_search_context (search_context)

    def do_run (self):
        '''
        Adds results to the model
        '''
        try:
            result_set = self.search_context.result_set
            for i in search(self.search_context.search_query,
                            self.search_context.filter_state):
                if not 'uri' in i or not i['uri'] or i['uri'] == '':
                    continue
                if not 'icon' in i or not i['icon'] or i['icon'] == '':
                    i['icon'] = DEFAULT_RESULT_ICON
                if not 'mimetype' in i or not i['mimetype'] or i['mimetype'] == '':
                    i['mimetype'] = DEFAULT_RESULT_MIMETYPE
                if not 'result_type' in i or not i['result_type'] or i['result_type'] == '':
                    i['result_type'] = DEFAULT_RESULT_TYPE
                if not 'category' in i or not i['category'] or i['category'] == '':
                    i['category'] = 0
                if not 'title' in i or not i['title']:
                    i['title'] = ''
                if not 'comment' in i or not i['comment']:
                    i['comment'] = ''
                if not 'dnd_uri' in i or not i['dnd_uri'] or i['dnd_uri'] == '':
                    i['dnd_uri'] = i['uri']
                i['provider_credits'] = GLib.Variant('s', PROVIDER_CREDITS)
                result_set.add_result(**i)
        except Exception as error:
            print (error)

class Preview (Unity.ResultPreviewer):

    def get_image(self,rand, q, lat, lon):
        print (lat, lon)
        flick_map={'weather-clear':'clear',
                   'weather-clear-night':'clear',
                   'weather-few-clouds':'cloudy',
                   'weather-few-clouds-night':'cloudy',
                   'weather-clouds':'cloudy',
                   'weather-clouds-night':'cloudy',
                   'weather-overcast':'cloudy',
                   'weather-overcast':'cloudy',
                   'weather-showers-scattered':'rain',
                   'weather-showers-scattered':'rain',
                   'weather-showers':'rain',
                   'weather-showers':'rain',
                   'weather-storm':'storm',
                   'weather-storm':'storm',
                   'weather-snow':'snow',
                   'weather-snow':'snow',
                   'weather-fog':'fog',
                   'weather-fog':'fog'}
        license_list = ["All Rights Reserved",
                       "Attribution-NonCommercial-ShareAlike License",
                       "Attribution-NonCommercial License",
                       "Attribution-NonCommercial-NoDerivs License",
                       "Attribution License",
                       "Attribution-ShareAlike License",
                       "Attribution-NoDerivs License",
                       "No known copyright restrictions",
                       "United States Government Work"]
        image = None
        query = urllib.parse.quote(flick_map[q])
        key = 'd87224f0b467093b2a87fd788d950e27'
        uri = 'http://secure.flickr.com/services/rest/?method=flickr.photos.search&nojsoncallback=1&per_page=20&api_key=%s&extras=url_m,owner_name,license&group_id=1463451@N25&format=json&tag_mode=all&tags=%s&bbox=%f,%f,%f,%f' % (key,
                                                                                                                       query,
                                                                                                                       round(float(lon) - 0.1, 2), round(float(lat) - 0.1, 2),
                                                                                                                       round(float(lon) + 0.1, 2), round(float(lat) +0.1, 2))
        print (uri)
        try:
            response = urllib.request.urlopen(uri).read()
            data = json.loads(response.decode('utf-8'))
            photo = min(rand, int(data['photos']['total'])-1)
            image = data['photos']['photo'][photo]['url_m']
            owner = data['photos']['photo'][photo]['ownername']
            license = license_list[int(data['photos']['photo'][photo]['license'])]
            link = 'https://www.flickr.com/photos/%s/%s' % (data['photos']['photo'][photo]['owner'], data['photos']['photo'][photo]['id'])
        except Exception as error:
            print (error)
            image, owner, license, link = None, None, None, None
        return image, owner, license, link

    def do_run(self):

        preview = Unity.GenericPreview.new(self.result.title.split('\n')[0], '', None)
        preview.props.subtitle = self.result.comment.capitalize()
        image, owner, license, link = self.get_image(self.result.metadata['photo_hint'].get_int32(),
                                                     self.result.icon_hint,
                                                     self.result.metadata['latitude'].get_string(),
                                                     self.result.metadata['longitude'].get_string())

        gfile_icon = Gio.file_new_for_path(PROVIDER_ICON)
        gicon = Gio.FileIcon.new (gfile_icon)
        open_action = Unity.PreviewAction.new("open", _("Open Weather Map"), gicon)
        preview.add_action(open_action)
        preview.add_info(Unity.InfoHint.new("min_temp", _("Minimum Temperature"), None, self.result.metadata['min_temp'].get_string()))
        preview.add_info(Unity.InfoHint.new("max_temp", _("Maximum Temperature"), None, self.result.metadata['max_temp'].get_string()))
        preview.add_info(Unity.InfoHint.new("wind_speed", _("Wind Speed"), None, self.result.metadata['wind_speed'].get_string() + ' m/s'))
        preview.add_info(Unity.InfoHint.new("wind_direction", _("Wind Direction"), None, self.result.metadata['wind_direction'].get_string() + "\u00B0"))
        if self.result.metadata['pressure'].get_string() != '':
            preview.add_info(Unity.InfoHint.new("pressure", _("Pressure"), None, self.result.metadata['pressure'].get_string() + ' hPa'))
        if self.result.metadata['humidity'].get_string() != '':
            preview.add_info(Unity.InfoHint.new("humidity", _("Humidity"), None, self.result.metadata['humidity'].get_string() + '%'))
        if image:
            preview.props.image_source_uri = image
            gfile_icon = Gio.file_new_for_path(SVG_DIR+'service-flickr.svg')
            gicon = Gio.FileIcon.new (gfile_icon)
            preview.add_info(Unity.InfoHint.new("photo_owner", _("Photo source"), gicon, 'Flickr'))
            preview.add_info(Unity.InfoHint.new("photo_owner", _("Photo credit"), None, owner))
            preview.add_info(Unity.InfoHint.new("photo_license", _("Photo license"), None, license))
        else:
            gfile_icon = Gio.file_new_for_path(SVG_DIR+'service-flickr.svg')
            gicon = Gio.FileIcon.new (gfile_icon)
            submit_action = Unity.PreviewAction.new("submit", _("Submit Photo"), gicon)
            preview.add_action(submit_action)
        return preview


class Scope (Unity.AbstractScope):
    def __init__(self):
        Unity.AbstractScope.__init__(self)

    def do_get_search_hint (self):
        return SEARCH_HINT

    def do_get_schema (self):
        '''
        Adds specific metadata fields
        '''
        schema = Unity.Schema.new ()
        if EXTRA_METADATA:
            for m in EXTRA_METADATA:
                schema.add_field(m['id'], m['type'], m['field'])
        #FIXME should be REQUIRED for credits
        schema.add_field('provider_credits', 's', Unity.SchemaFieldType.OPTIONAL)
        return schema

    def do_get_categories (self):
        '''
        Adds categories
        '''
        cs = Unity.CategorySet.new ()
        if CATEGORIES:
            for c in CATEGORIES:
                cat = Unity.Category.new (c['id'], c['name'],
                                          Gio.ThemedIcon.new(c['icon']),
                                          c['renderer'])
                cs.add (cat)
        return cs

    def do_get_filters (self):
        '''
        Adds filters
        '''
        fs = Unity.FilterSet.new ()
#        if FILTERS:
#
        return fs

    def do_get_group_name (self):
        return GROUP_NAME

    def do_get_unique_name (self):
        return UNIQUE_PATH

    def do_create_search_for_query (self, search_context):
        se = MySearch (search_context)
        return se

    def do_create_previewer(self, result, metadata):
        rp = Preview()
        rp.set_scope_result(result)
        rp.set_search_metadata(metadata)
        return rp

    def do_activate(self, result, metadata, id):
        if not id:
            return Unity.ActivationResponse(handled=Unity.HandledType.SHOW_PREVIEW, goto_uri=result.uri)
        if id == 'submit':
            uri = 'https://www.flickr.com/groups/projectweather/'
            return Unity.ActivationResponse(handled=Unity.HandledType.HIDE_DASH, goto_uri=uri)
        return

def load_scope():
    return Scope()
