#!/usr/bin/python

#    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 os.path
import commands

# Unity imports
from gi.repository import GLib
from gi.repository import GObject
from gi.repository import Gio
from gi.repository import Dee
# FIXME: Some weird bug in Dee or PyGI makes Dee fail unless we probe
#        it *before* we import the Unity module... ?!
_m = dir(Dee.SequenceModel)
from gi.repository import Unity

import askubuntu_bridge

#    TODO
#  - Get rid of Loading cursor on launcher after opening a link
#  - Test this on a slower internet connection!

#
# The primary bus name we grab *must* match what we specify in our .lens file
#
BUS_NAME = "net.launchpad.Lens.AskUbuntu"

# Group ids. Must match the offset into the group models
# These are the groups of results, separated by a horizontal
# rule. They have their own icons, which should be monochrome(?).
EVERYTHING = 0
SOLVED = 1
ANSWERED = 2
UNANSWERED = 3
TAGS = 4
USERS = 5
BADGES = 6

class Daemon (object):

    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 filter defined in askubuntu_bridge.py
        The filters are a list of different stackexchange sites where searches
        can be done.
        """
        # Safe for the signal handlers, I don't know what any of the things
        # in this method mean. Should have to ask someone.

        self.lens = Unity.Lens.new ("/net/launchpad/lens/askubuntu", "askubuntu")
        self.scope = Unity.Scope.new ("/net/launchpad/lens/askubuntu/scope/askubuntu")
    
        self.lens.props.search_hint = "Search Ask Ubuntu"
        self.lens.props.visible = True
        self.lens.props.search_in_global = False
        self.scope.props.search_in_global = False
        
        # Populate categories
        cats = []
        cats.append (Unity.Category.new ("Actions",
                                         Gio.ThemedIcon.new("help"),
                                         Unity.CategoryRenderer.HORIZONTAL_TILE))
        cats.append (Unity.Category.new ("Question With Accepted Answers",
                                         Gio.ThemedIcon.new("gtk-ok"),
                                         Unity.CategoryRenderer.HORIZONTAL_TILE))
        cats.append (Unity.Category.new ("Answered Questions",
                                         Gio.ThemedIcon.new("help"),
                                         Unity.CategoryRenderer.HORIZONTAL_TILE))
        cats.append (Unity.Category.new ("Unanswered Questions",
                                         Gio.ThemedIcon.new("help"),
                                         Unity.CategoryRenderer.HORIZONTAL_TILE))
        cats.append (Unity.Category.new ("Tags",
                                         Gio.ThemedIcon.new("help"),
                                         Unity.CategoryRenderer.VERTICAL_TILE))
        cats.append (Unity.Category.new ("Users",
                                         Gio.ThemedIcon.new("stock-person"),
                                         Unity.CategoryRenderer.VERTICAL_TILE))
        cats.append (Unity.Category.new ("Badges",
                                         Gio.ThemedIcon.new("help"),
                                         Unity.CategoryRenderer.VERTICAL_TILE))
        
        self.lens.props.categories = cats
        
        filters = []
        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.lens.add_local_scope (self.scope);
        self.lens.export ();

    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 add_default_results(self, search, model):
        """ Adds specific results to the model based on the search string
        """
        if not search.startswith("@"):
            icon_hint = Gio.ThemedIcon.new("distributor-logo").to_string()

            if search.lower() not in self.special_searches:
                model.append("http://askubuntu.com/questions/ask",
                            Gio.ThemedIcon.new("help-faq").to_string(),
                            EVERYTHING,
                            "text/html", 
                            "Ask your own question",
                            "Ask a Question", 
                "")
    
            if search and search.lower() not in self.special_searches:
                model.append("http://askubuntu.com/search?q=" + search.strip(),
                            Gio.ThemedIcon.new("search").to_string(), 
                            EVERYTHING,
                            "text/html", 
                            "Search on Ask Ubuntu",
                            "Find '%s' on Ask Ubuntu" % search, 
                "")

            if "chat" in search.lower():
                model.append("http://chat.stackexchange.com/rooms/201/",
                        Gio.ThemedIcon.new("applications-chat").to_string(),
                            EVERYTHING,
                            "text/html", 
                            "Join the Ask Ubuntu Chat",
                            "Join the Ask Ubuntu General Chat Room", 
                "")

            if "meta" in search.lower():
                model.append("http://meta.askubuntu.com/",
                        Gio.ThemedIcon.new("gnome-fs-bookmark-missing").to_string(),
                            EVERYTHING,
                            "text/html", 
                            "Visit Ask Ubuntu - Meta",
                            "Visit Ask Ubuntu - Meta", 
                "")

            if (any(i in search.lower() for i in ["askubuntu", "ask"]) or
                search.lower() == "au"):
                model.append("http://askubuntu.com/",
                            Gio.ThemedIcon.new("distributor-logo").to_string(),
                            EVERYTHING,
                            "text/html", 
                            "Ask Ubuntu",
                            "Ask Ubuntu", 
                "")

            if (any(i in search.lower() for i in ["profile", "recent"]) or
                search.lower() == "me"):
                model.append("http://askubuntu.com/users/recent",
                            Gio.ThemedIcon.new("user-info").to_string(),
                            EVERYTHING,
                            "text/html", 
                            "Your Responses",
                            "Your Responses",
                "")

            if ("askubuntu" in search.lower() and "lens" in search.lower() or
                search.lower() == "help" or search.lower() == "wtf"):
                model.append("http://askubuntu.com/questions/31712",
                            Gio.ThemedIcon.new("help").to_string(),
                            EVERYTHING,
                            "text/html", 
                            "Help about this Lens",
                            "", 
                            "")

    # These next four methods should be merged into one, but only after
    # it's clear that there are no more changes to the API, requiring me
    # to display the results differently for the different types of data.

    def add_questions_results(self, search, model):
        """ Searches a specific stackexchange site for questions and adds 
        results to the questions categories based on the solved
        state of the result  solved, answered or not answered
        Args:
          search: the search string
          model: the model object that results are added to
        """
        for url, icon, title, text, category in askubuntu_bridge.get_questions(search):
            # No need to be including questions marked as [closed] in the results
            if title.lower().find("[closed]") == -1: 
                model.append(url, icon, category, "text/html", title, text, url)

    def add_tags_results(self, search, model, _max=None):
        """ Searches a specific stackexchange site for tags and adds 
        results to the tags category 
        Args:
          search: the search string
          model: the model object that results are added to
        """
        icon_hint = Gio.ThemedIcon.new("tag-new").to_string()  # Tags Icon
        for n, (url, title) in enumerate(askubuntu_bridge.get_tags(search)):
            model.append(url, icon_hint, TAGS, "text/html", title, title, url)
            if _max and n + 1 == _max:
                break

    def add_badges_results(self, search, model):
        """ Searches a specific stackexchange site for badges and adds 
        results to the badges category 
        Args:
          search: the search string
          model: the model object that results are added to
        """
        icon_hint = Gio.ThemedIcon.new("help").to_string()  # Badge Icon
        for url, title in askubuntu_bridge.get_badges(search):
            model.append(url, icon_hint, BADGES, "text/html", title, title, url)

    def add_users_results(self, search, model):
        """ Searches a specific stackexchange site for users and adds 
        results to the users category 
        Args:
          search: the search string
          model: the model object that results are added to
        """
        for url, icon, title in askubuntu_bridge.get_users(search):
            model.append(url, icon, USERS, "text/html", title, title, url)

    def update_results_model(self, search, model, _global=False):
        """ 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
        """
        if _global and search:
            if search != "au":  # for the "au" search, we *do* show a result
                model.clear()   # in the global dash. Why not? ;-)
                return
        model.clear()

        f = self.scope.get_filter("sites")
        if f != None:
            o = f.get_active_option()
        if o != None:
            search = o.props.id + " " +  search
        
        if _global == False or search in self.special_searches:
            self.add_default_results(search, model)
            
        if search and not search.lower() in self.special_searches:
            self.add_questions_results(search, model)
            self.add_tags_results(search, model)
            self.add_badges_results(search, model)
            self.add_users_results(search, model)

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?"
        print >> sys.stderr, "Try grepping unity-askubuntu-deamon in ps aux"
        print >> sys.stderr, "and kill the process. "
        raise SystemExit(1)
    daemon = Daemon()
    print "entering the main loop"
    GObject.MainLoop().run()
