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

"""
#    Copyright (C) 2011  Stefano Palazzo <stefano.palazzo@gmail.com>
#                  2011  Mark Tully <markjtully@gmail.com>

#    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 sys
import locale
import subprocess
import random
import os.path
import ConfigParser
import gettext

# Unity imports
from gi.repository import GLib
from gi.repository import GObject
from gi.repository import Gio
from gi.repository import Unity

_ = gettext.gettext

BUS_NAME = "net.launchpad.lens.help"

EVERYTHING = 0
OFFICIAL = 1
COMMUNITY = 2
TECHNICAL = 3
TAGS = 4


class Daemon (object):
    """ Yelp Daemon sets up the lens & searches Ubuntu Help for results 
    matching query
    """

    special_searches = ["me", "chat", "meta", "au", "help", "lens"]

    def __init__(self):
        """ Sets up the lens and default scope
        The lens has 6 categories and a number of filters.  The first is
        a filter to show and hide results for specific scopes, the second
        is a list of stackexchange sites, as defined in askubuntu_bridge.py
        The filters are a list of different stackexchange sites where searches
        can be done.
        """
        locale.setlocale(locale.LC_ALL, '')
        self.data = []
        self.setup_yelp()
        self.lens = Unity.Lens.new("/net/launchpad/lens/help", "help")
        self.scope = Unity.Scope.new("/net/launchpad/lens/help/scope/yelp")

        self.lens.props.search_hint = "Search for help with Ubuntu"
        self.lens.props.visible = True
        self.lens.props.search_in_global = False
        self.scope.props.search_in_global = False

        # Set up categories. Categories for all scopes must be set up here.
        # They can not be done from within the scopes themselves
        cats = []
        cats.append(Unity.Category.new(_("Actions"),
                                        Gio.ThemedIcon.new("/usr/lib/unity-lens-help/icons/help-question.svg"),
                                        Unity.CategoryRenderer.HORIZONTAL_TILE))
        cats.append(Unity.Category.new(_("Official Help Documents"),
                                        Gio.ThemedIcon.new("/usr/lib/unity-lens-help/icons/help-answer.svg"),
                                        Unity.CategoryRenderer.HORIZONTAL_TILE))
        cats.append(Unity.Category.new(_("Community Help Documents"),
                                        Gio.ThemedIcon.new("/usr/lib/unity-lens-help/icons/help-answer.svg"),
                                        Unity.CategoryRenderer.HORIZONTAL_TILE))
        cats.append(Unity.Category.new(_("Technical Documents"),
                                        Gio.ThemedIcon.new("/usr/lib/unity-lens-help/icons/help-answer.svg"),
                                        Unity.CategoryRenderer.HORIZONTAL_TILE))
        cats.append(Unity.Category.new(_("Tags"),
                                        Gio.ThemedIcon.new("/usr/lib/unity-lens-help/icons/help-answer.svg"),
                                        Unity.CategoryRenderer.VERTICAL_TILE))
        self.lens.props.categories = cats

        # Set up filters. All filters must be set up here.
        # They can not be done from within the scopes themselves
        filters = []

        # Add filters to show & hide sources (without having to
        # install and uninstall scopes)
        f = Unity.CheckOptionFilter.new("sources",
                                        _("Help sources"),
                                        Gio.ThemedIcon.new("input-keyboard-symbolic"),
                                        False)

        for filename in os.listdir("/usr/share/unity/lenses/help"):
            config = ConfigParser.ConfigParser()
            config.read("/usr/share/unity/lenses/help/" + filename)
            f.add_option(config.get("Scope", "ScopeIdentifier"), config.get("Scope", "ScopeName"), None)
        filters.append(f)

        # Only add Stackexchange filters if the AskUbuntu scope is installed
        if os.path.exists("/usr/share/unity/lenses/help/extras-unity-scope-askubuntu.scope"):
            import askubuntu_bridge
            f = Unity.RadioOptionFilter.new("sites",
                                         _("Other Stack Exchange Sites"),
                                         Gio.ThemedIcon.new("input-keyboard-symbolic"),
                                         False)
            for site in askubuntu_bridge.SITES:
                if site[2] != "":
                    f.add_option(site[0], site[2], None)
            filters.append(f)

        self.lens.props.filters = filters

        # Listen for changes and requests
        self.scope.connect("search-changed", self.on_search_changed)
        self.scope.connect("filters-changed", self.on_filters_changed)
        self.scope.connect("notify::active", self.on_lens_active)
        self.scope.connect("activate-uri", self.activate_uri)
        self.lens.add_local_scope(self.scope)
        self.lens.export()

    def setup_yelp(self):
        """ Parses local help files for <desc> and 1st <title> tags and associates
        them with the page's uri in self.data as: {uri, page description, page title}
        If a help file has no <desc> or <title> tag, it is excluded.
        """
        from xml.dom import minidom
        mylocale = locale.getlocale()

        data = []
        help_location = "/usr/share/help/" + mylocale[0]
        if not os.path.exists(help_location):
            help_location = "/usr/share/help/C"
        for dirname in os.listdir(help_location):
            directory = help_location + "/" + dirname
            for filename in os.listdir(directory):
                filename = directory + "/" + filename
                if not os.path.isdir(filename):
                    if os.path.isfile(filename):
                        xmldoc = minidom.parse(filename)
                        try:
                            desc = ""
                            nodes = xmldoc.getElementsByTagName('desc').item(0).childNodes
                            for node in nodes:
                                try:
                                    desc += str(node.wholeText)
                                except:
                                    desc += str(node.childNodes[0].wholeText)
                            desc = desc.strip(' \t\n\r')
                            title = xmldoc.getElementsByTagName('title').item(0).childNodes[0].data
                            if desc == "":
                                desc = title
                            record = []
                            record.append(filename)
                            record.append(desc)
                            record.append(title)
                            record.append(dirname)
                            data.append(record)
                        except:
                            pass
        random.shuffle(data)
        self.data = data

    def on_filters_changed(self, *_):
        """ Called when a filter is clicked.  Queue's a new search
        """
        self.scope.queue_search_changed(Unity.SearchType.DEFAULT)

    def on_lens_active(self, *_):
        """ Called when the lens is activated.  Queue's a new search
        """
        if self.scope.props.active:
            self.scope.queue_search_changed(Unity.SearchType.DEFAULT)

    def on_search_changed(self, scope, search=None, search_type=0, cancellable=None):
        """ Called when the search is changed.  Gets the search string and search
        type and passes it to update_results_model()
        Args:
          scope: a scope object
          search: the search string
          search_type: the search type (global or local)
          cancellable: cancellable object
        """
        if hasattr(search, "props"):
            search_string = search.props.search_string
        else:
            search_string = ""

        if search_type == Unity.SearchType.DEFAULT:
            results = scope.props.results_model
        else:
            results = scope.props.global_results_model

        print "Search changed to: '%s'" % search_string
        self.update_results_model(search_string, results)
        if hasattr(search, "finished"):
            search.finished()

    def update_results_model(self, search, model):
        """ Takes the search string and determines the active filters, then
        gets questions, tags, badges and users for the search
        Args:
          search: the search string
          model: the model object that results are added to
        """
        model.clear()

        f = self.scope.get_filter("sites")
        o = None
        if f != None:
            o = f.get_active_option()
        if o != None:
            search = o.props.id + " " + search

        scope_active = False
        e = self.scope.get_filter("sources")
        if not e.props.filtering:
            scope_active = True
        for source in e.options:
            if source.props.id == "yelp":
                if source.props.active:
                    scope_active = True

        if scope_active:
            icon_hint = Gio.ThemedIcon.new("help").to_string()
            for data in self.data:
                try:
                    if data[3] == "ubuntu-help":
                        icon_hint = Gio.ThemedIcon.new("distributor-logo").to_string()
                    else:
                        icon_hint = Gio.ThemedIcon.new(data[3]).to_string()
                except:
                    pass

                if not data[1].lower().find(unicode(search.lower(), "utf-8")) == -1:
                    model.append(data[0], icon_hint, OFFICIAL, "text/html", data[1], "", data[0])
                elif not data[2].lower().find(unicode(search.lower(), "utf-8")) == -1:
                    model.append(data[0], icon_hint, OFFICIAL, "text/html", data[2], "", data[0])
                elif not data[3].lower().find(unicode(search.lower(), "utf-8")) == -1:
                    model.append(data[0], icon_hint, OFFICIAL, "text/html", data[1], "", data[0])
                else:
                    continue

    def activate_uri(self, scope, uri):
        """ Called when a result is clicked.
        Opens the selected uri with yelp
        """
        parameters = ["yelp", uri]
        subprocess.Popen(parameters)
        return Unity.ActivationResponse(handled=Unity.HandledType.HIDE_DASH, goto_uri='')

if __name__ == "__main__":
    session_bus_connection = Gio.bus_get_sync(Gio.BusType.SESSION, None)
    session_bus = Gio.DBusProxy.new_sync(session_bus_connection, 0, None,
        'org.freedesktop.DBus', '/org/freedesktop/DBus',
        'org.freedesktop.DBus', None)
    result = session_bus.call_sync('RequestName',
        GLib.Variant("(su)", (BUS_NAME, 0x4)), 0, -1, None)
    result = result.unpack()[0]
    # We could try to do some automated rescue when this happens:
    if result != 1:
        print >> sys.stderr, "Failed to own name %s. Bailing out." % BUS_NAME
        print >> sys.stderr, "Do you have another instance running?"
        raise SystemExit(1)
    daemon = Daemon()
    print "entering the main loop"
    GObject.MainLoop().run()
