#! /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 feedparser
import urllib.parse
import urllib.request
import json
import re
import locale
import gettext

locale.setlocale(locale.LC_MESSAGES, '')

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

countries_to_google = {'CA': 'ca', 'TR': 'tr_tr', 'IE': 'en_ie', 'CU': 'es_cu', 'LB': 'ar_lb', 'KR': 'kr', 'TW': 'tw', 'ET': 'en_et', 'IT': 'it', 'PE': 'es_pe', 'AR': 'es_ar', 'NO': 'no_no', 'NG': 'en_ng', 'CZ': 'cs_cz', 'GH': 'en_gh', 'VN': 'vi_vn', 'IL': 'iw_il', 'AU': 'au', 'SG': 'en_sg', 'VE': 'es_ve', 'JP': 'jp', 'SN': 'fr_sn', 'ZW': 'en_zw', 'CN': 'cn', 'CL': 'es_cl', 'BE': 'fr_be', 'DE': 'de', 'HK': 'hk', 'PH': 'en_ph', 'ES': 'es', 'UA': 'ru_ua', 'IN': 'in', 'NL': 'nl_nl', 'BR': 'pt-BR_br', 'PL': 'pl_pl', 'SA': 'ar_sa', 'US': 'us', 'MA': 'fr_ma', 'SE': 'sv_se', 'NA': 'en_na', 'KE': 'en_ke', 'CH': 'de_ch', 'NZ': 'nz', 'FR': 'fr', 'RU': 'ru_ru', 'PK': 'en_pk', 'CO': 'es_co', 'PT': 'pt-PT_pt', 'MX': 'es_mx', 'EG': 'ar_eg', 'AE': 'ar_ae', 'ZA': 'en_za', 'RS': 'sr_rs', 'GB': 'uk', 'MY': 'en_my', 'AT': 'de_at', 'UG': 'en_ug', 'GR': 'el_gr', 'HU': 'hu_hu', 'TZ': 'en_tz', 'BW': 'en_bw'}

GROUP_NAME = 'com.canonical.Unity.Scope.News.Googlenews'
UNIQUE_PATH = '/com/canonical/unity/scope/news/googlenews'
GEOIPLOOKUP_URI = 'http://geoiplookup.wikimedia.org'
SEARCH_URI = 'https://news.google.com/news/feeds'
SEARCH_HINT = _('Search Google News')
NO_RESULTS_HINT = _('Sorry, there are no Google News articles that match your search.')
PROVIDER_CREDITS = _('Powered by Google News')
SVG_DIR = '/usr/share/icons/unity-icon-theme/places/svg/'
PROVIDER_ICON = SVG_DIR+'service-googlenews.svg'
DEFAULT_RESULT_ICON = SVG_DIR+'group-news.svg'
DEFAULT_RESULT_MIMETYPE = 'text/html'
DEFAULT_RESULT_TYPE = Unity.ResultType.DEFAULT

c1 = {'id'      :'local',
      'name'    :_('Local'),
      'icon'    :SVG_DIR+'group-news.svg',
      'renderer':Unity.CategoryRenderer.VERTICAL_TILE}
c2 = {'id'      :'top',
      'name'    :_('Top Stories'),
      'icon'    :SVG_DIR+'group-news.svg',
      'renderer':Unity.CategoryRenderer.VERTICAL_TILE}
CATEGORIES = [c1, c2]

FILTERS = []

m1 = {'id'   :'published',
      'type' :'s',
      'field':Unity.SchemaFieldType.OPTIONAL}
m2 = {'id'   :'language',
      'type' :'s',
      'field':Unity.SchemaFieldType.OPTIONAL}
EXTRA_METADATA = [m1, m2]

def get_location():
    try:
        #FIXME geoip.ubuntu.com can have format errors,
        # but it should eventually be the source here
        f = urllib.request.urlopen(GEOIPLOOKUP_URI)
        content = f.read()
    except:
        print("Location unavailable")
        content = ''
    try:
        #FIXME tries to make sense from the odd geoloc response
        results = eval(str(content).split('=')[1].replace("}'", "}"))
        try:
            country = countries_to_google[results['country']]
            city = results['city']
            if city == '(null)':
                city = ''
        except Exception as error:
            print(error)
            print("Undefined location, fallback on US")
            country = 'us'
        return [country, city]
    except Exception as error:
        print(error)
        return ['us', '']

country = None
city = None

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)
    '''
    global country, city
    if country is None:
        country, city = get_location()
    results = []
    if search:
        if len(search) < 3:
            return results
        search = urllib.parse.quote(search)
    for cat in range(0,2):
        topic = ''
        scoring = "r"
        if search:
            if cat == 0:
                rss = "%s?output=rss&q=%s&num=25&scoring=%s&ned=%s&geo=%s&topic=%s" % (SEARCH_URI, search, scoring, country, city, topic)
            else:
                rss = "%s?output=rss&q=%s&num=25&scoring=%s&ned=%s&topic=%s" % (SEARCH_URI, search, scoring, country, topic)
        else:
            if cat == 0:
                rss = "%s?output=rss&num=25&scoring=%s&ned=%s&geo=%s&topic=%s" % (SEARCH_URI, scoring, country, city, topic)
            else:
                rss = "%s?output=rss&num=25&scoring=%s&ned=%s&topic=%s" % (SEARCH_URI, scoring, country, topic)
        print(rss)
        feed = feedparser.parse(rss)
        for f in feed['entries']:
            img_search = re.search(' src="([^ ]*)" ', f['summary'])
            if img_search:
                img = img_search.group(1)
                img = img.replace('/0.jpg', '/1.jpg')
            else:
                img = None
            if 'published' not in f:
                # support feedparser == 5.1
                published = f['updated']
            else:
                published = f['published']

            results.append({'uri':f['link'],
                            'icon':img,
                            'category':cat,
                            'comment':f['summary'],
                            'title':f['title'],
                            'language':country,
                            'published':published})
    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']
                result_set.add_result(**i)
        except Exception as error:
            print(error)

class Preview (Unity.ResultPreviewer):

    def do_run(self):
        thumb = Gio.FileIcon.new(Gio.file_new_for_uri(self.result.icon_hint))
        # Extract the source name, which is always using the same color
        source = re.findall('<font color=\"#6f6f6f\">([^<]*)<\/font>', self.result.comment)
        # Turn <br /> into \n
        content = re.sub(r'<[^>]*br[^>]*>', '\n', self.result.comment)
        # Remove other html tags
        content = re.sub(r'<[^>]*>', ' ', content)
        content = re.sub(r'&[^\s]*;', ' ', content)
        # Remove leading spaces left by the previous parsing
        content = re.sub(r'^\s*', ' ', content)
        # Pick the longest paragraph, which is our text body
        body_parts = content.split('\n')
        text = ''
        for b in body_parts:
            if len(b) > len(text):
                text = b
        preview = Unity.GenericPreview.new(self.result.title, text.strip().rstrip(), thumb)
        preview.props.subtitle = self.result.metadata['published'].get_string()
        if source and len(source) > 0:
            preview.add_info(Unity.InfoHint.new("source", _("Source"), None, source[0]))
        icon = Gio.FileIcon.new (Gio.file_new_for_path(PROVIDER_ICON))
        view_action = Unity.PreviewAction.new("open", _("View"), icon)
        preview.add_action(view_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 load_scope():
    return Scope()
