# encoding: utf-8
# api: streamtuner2
# title: Search feature
# description: Provides the quick search box, and server/cache search window.
# version: 1.0
# type: feature
# category: ui
# config: -
# priority: core
#
# The search dialog implementes a "cache" and a live
# "server" search (only some plugins). It scans through
# all available, or just the last active channel tab.
# Found entries are displayed in „bookmarks›search“.
#
# With the little search box atop the main window, you
# can alternatively highlight entries in the currently
# selected category.


from uikit import *
import channels
from config import *
from copy import copy


# Search window, and quicksearch box handler.
#
# Aux win: search dialog - keeps search text in self.q
# Quick search textbox - uses main.q instead
#
class search (AuxiliaryWindow):

    # either current channel, or last channel (avoid searching in bookmarks)
    current = None

    # show search dialog   
    def menu_search(self, w):
        self.search_dialog.show_all();
        # Update (x) current_channel checkbox
        if not self.current or self.main.current_channel != "bookmarks":
            self.current = self.main.current_channel
            self.search_dialog_current.set_label("just %s" % self.main.channels[self.current].meta["title"])
        # Update icon
        if "finder" in dir(self) and self.finder:
            self.search_img.set_from_pixbuf(uikit.pixbuf(self.finder))
            self.finder = None


    # hide dialog box again
    def cancel(self, *args):
        self.search_dialog.hide()
        return True  # stop any other gtk handlers
        

    # Prepare self.q and self.targets, empty streams{search} result store
    def prepare_search(self):
        self.cancel()
        self.q = self.search_full.get_text().lower()
        if not len(self.q):
            self.main.status("No search terms given.")
            return False
        else:
            self.main.status("Searching... Stand back.")
        if self.search_dialog_all.get_active():
            self.targets = self.main.channels.keys()
        else:
            self.targets = [self.current]
        self.main.bookmarks.streams["search"] = []
        return True
        
    # perform search
    def cache_search(self, *w):
        if not self.prepare_search():
            return
        entries = []
        # which fields?
        fields = ["title", "playing", "homepage"]
        # traverse all channels modules
        for c in self.targets:
            cn = self.main.channels[c]
            if cn.streams:  # skip disabled plugins
                # categories
                for cat in cn.streams.keys():
                    # stations
                    for row in cn.streams[cat]:
                        # assemble text fields to compare
                        text = " ".join([str(row.get(f, " ")) for f in fields])
                        if text.lower().find(self.q) >= 0:
                            row = copy(row)
                            row["genre"] = "%s %s" % (c or "", row.get("genre")  or "")
                            entries.append(row)
        uikit.do(self.show_results, entries)
        
    # live search on directory server homepages
    def server_search(self, w):
        if not self.prepare_search():
            return
        entries = []
        for i,cn in enumerate([self.main.channels[c] for c in self.targets]):
            if cn.has_search:  # "search" in cn.update_streams.func_code.co_varnames:
                self.main.status("Server searching: " + cn.module)
                log.PROC("has_search:", cn.module)
                try:
                    add = cn.update_streams(cat=None, search=self.q)
                    for row in add:
                        row["genre"] = cn.meta["title"] + " " + row.get("genre", "")
                    entries += add
                except Exception as e:
                    log.WARN("server_search: update_streams error in {}:".format(cn.module), e)
                    continue
            #main.status(main, 1.0 * i / 15)
        uikit.do(self.show_results, entries)

    # display "search" in "bookmarks"
    def show_results(self, entries):
        self.main.status(1.0)
        self.main.status("")
        # set contents right away
        self.main.channels["bookmarks"].streams["search"] = entries
        # switch to bookmarks›search tab
        uikit.do(self.main.channel_switch_by_name, "bookmarks")
        uikit.do(self.main.bookmarks.set_category, "search")
        # insert data and show
        self.main.bookmarks.load("search")


    # search text edited in text entry box
    def quicksearch_set(self, w, *eat, **up):
        
        # keep query string
        self.main.q = self.search_quick.get_text().lower()

        # get streams
        c = self.main.channel()
        rows = c.stations()
        col = c.rowmap.index("search_col") # this is the gtk.ListStore index # which contains the highlighting color

        # callback to compare (+highlight) rows
        m = c.gtk_list.get_model()
        m.foreach(self.quicksearch_treestore, (rows, self.main.q, col, col+1))
    search_set = quicksearch_set
        
    # callback that iterates over whole gtk treelist,
    # looks for search string and applies TreeList color and flag if found
    def quicksearch_treestore(self, model, path, iter, extra_data):
        i = path[0]
        (rows, q, color, flag) = extra_data

        # compare against interesting content fields:
        text = rows[i].get("title", "") + " " + rows[i].get("homepage", "")
        # config.quicksearch_fields
        text = text.lower()

        # simple string match (probably doesn't need full search expression support)
        if len(q) and text.find(q) >= 0:
           model.set_value(iter, color, "#fe9")  # highlighting color
           model.set_value(iter, flag, True) # background-set flag
        # color = 12 in liststore, flag = 13th position
        else:
           model.set_value(iter, color, "")   # for some reason the cellrenderer colors get applied to all rows, even if we specify an iter (=treelist position?, not?)
           model.set_value(iter, flag, False)   # that's why we need the secondary -set option

        #??
        return False


    
