##
# 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 2 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 Library General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
##

import gtk
import re, string
import Constants
import DSApp

class DlgSearch:
    """This class contains the implementation of the Search dialog and the search
    functionality."""
    
    def __init__ (self):
        """Constructor of DlgSearch class."""

        # load dialog widget tree, get widgets and connect signals 
        dlg_search_widget_tree = gtk.glade.XML (Constants.RESOURCE_DIR + "/disksearch.glade", "dlg_search")
        self.dlg_search = dlg_search_widget_tree.get_widget ("dlg_search")
        self.tf_search_string = dlg_search_widget_tree.get_widget ("tf_search_string")
        self.rb_normal_search = dlg_search_widget_tree.get_widget ("rb_normal_search")
        self.rb_regexp_search = dlg_search_widget_tree.get_widget ("rb_regexp_search")
        self.cb_case_sensitive = dlg_search_widget_tree.get_widget ("cb_case_sensitive")
        dlg_search_widget_tree.signal_connect ("on_close_clicked", self.search_files_close)
        dlg_search_widget_tree.signal_connect ("on_search_clicked", self.search_files_search)
        dlg_search_widget_tree.signal_connect ("on_search_mode_toggled", self.search_mode_toggled)

        # init treeview and add columns for name and size
        # (the cell renderer of the size column get's the xalign value from the third column)
        self.tv_results = dlg_search_widget_tree.get_widget ("tv_results")
        column1 = gtk.TreeViewColumn (_("Name"), gtk.CellRendererText (), text = 0)
        cellRendererSize = gtk.CellRendererText ()
        column2 = gtk.TreeViewColumn (_("Size"), cellRendererSize, text = 1)
        column2.add_attribute (cellRendererSize, "xalign", 2)
        column1.set_resizable (True)
        column1.set_expand (True)
        
        self.tv_results.append_column (column1)
        self.tv_results.append_column (column2)
        self.tv_results.get_selection ().set_mode (gtk.SELECTION_NONE)

        store = gtk.TreeStore (str, str, float)
        self.tv_results.set_model (store)
        
        # set initial values
        self.tf_search_string.set_text (DSApp.DSApp.doc.searchString)
        self.tf_search_string.select_region (0, len (self.tf_search_string.get_text ()))
        self.rb_regexp_search.set_active (DSApp.DSApp.doc.searchRegExpMode)
        self.cb_case_sensitive.set_active (DSApp.DSApp.doc.searchCaseSensitive)

        self.dlg_search.set_transient_for (DSApp.DSApp.view.app_disksearch)
        self.dlg_search.set_icon (DSApp.DSApp.view.pxb_appicon)
        self.dlg_search.show ()        

    def search_mode_toggled (self, *args):
        """The search mode has been changed by the user. Case sensitivity can only be 
        choosen in normal search mode."""
        
        self.cb_case_sensitive.set_sensitive (self.rb_normal_search.get_active ())
        

    def search_files_close (self, *args):
        """Closes the "Search" dialog without any action."""
        
        self.dlg_search.destroy ()

    def search_files_search (self, *args):
        """Prepares a new search action (the "Search" button was pressed)."""

        # get search string and modes
        # (store in document, so they can be displayed next time again)      
        DSApp.DSApp.doc.searchString = self.tf_search_string.get_text ()
        DSApp.DSApp.doc.searchRegExpMode = self.rb_regexp_search.get_active ()
        DSApp.DSApp.doc.searchCaseSensitive = self.cb_case_sensitive.get_active ()
        
        # are we in regular expression search mode ? => check validity
        searchRegExpression = None
        if DSApp.DSApp.doc.searchRegExpMode:
            try:
                searchRegExpression = re.compile (DSApp.DSApp.doc.searchString)
            except Exception, args:
                print "Exception: " + str (args)
                self.tf_search_string.select_region (0, -1)
                DSApp.DSApp.view.display_message_dialog_with_parrent (self.dlg_search, gtk.MESSAGE_ERROR, _("The search string is not a valid regular expression!"))
                self.tf_search_string.grab_focus ()
                return

        # start search
        DSApp.DSApp.view.show_wait_cursor (self.dlg_search.window)
        self.start_search (DSApp.DSApp.doc.searchString, DSApp.DSApp.doc.searchCaseSensitive, DSApp.DSApp.doc.searchRegExpMode, searchRegExpression)
        DSApp.DSApp.view.show_default_cursor (self.dlg_search.window)
        
    def start_search (self, searchString, searchCaseSensitive, searchRegExpMode, regularExpression):
        """Start the search after preparing using the specified parameters and
        displays all the results in the treeview."""

        # create a dictionary for fast mapping of media type id to text
        dictTypes = dict (DSApp.DSApp.doc.diskList.media_types)
        
        # set an empty treestore during searching
        self.tv_results.set_model (gtk.TreeStore (str, str, float))
        
        # create a new treestore and iterator variables (will be reseted for each disk and dir)
        store = gtk.TreeStore (str, str, float)
        iterDisk = None
        iterDir = None
        
        # process all disk, dirs and files
        for disk in DSApp.DSApp.doc.diskList.content:
            iterDisk = None
                        
            # process GTK event queue (otherwise the GUI looks dead while searching huge archives)
            gtk.main_iteration (False)
            
            for dir in disk.content:
                iterDir = None

                # is the substring in the directory name ? (normal or RegExp mode)
                found = False
                if searchRegExpMode == False:                    
                    found = self.is_substring_contained (dir[0], searchString, searchCaseSensitive)
                else:
                    found = regularExpression.search (dir[0]) != None
                
                # append directory (and disk if not allready added) to results tree
                if found:
                    if iterDisk == None:
                        iterDisk = self.create_disk_iter (store, disk, dictTypes)
                    iterDir = self.create_dir_iter (store, dir, iterDisk)
                        
                for file in (dir[1]):
                    
                    # is the substring in one of the directories files ? (normal or RegExp mode)
                    found = False
                    if searchRegExpMode == False:                    
                        found = self.is_substring_contained (file[0], searchString, searchCaseSensitive)
                    else:
                        found = regularExpression.search (file[0]) != None

                    if found:
                        # append file (and disk and directory if not allready added) to results tree
                        if iterDisk == None:
                            iterDisk = self.create_disk_iter (store, disk, dictTypes)
                        if iterDir == None:
                            iterDir = self.create_dir_iter (store, dir, iterDisk)
                        self.create_file_iter (store, file, iterDir)
                        
        # set model and expand all directories
        self.tv_results.set_model (store)
        self.tv_results.expand_all ()
            
    def create_disk_iter (self, store, disk, dictTypes):
        """Creates an iterator for the specified disk and returns it."""
        
        # append disk type and name (no size)
        iterDisk = store.append (None)
        store.set (iterDisk, 0, dictTypes[disk.type] + " '" + disk.name + "'")
        store.set (iterDisk, 1, "")
        store.set (iterDisk, 2, 1.0)
        return iterDisk
            
    def create_dir_iter (self, store, dir, iterDisk):        
        """Creates an iterator for the specified directory and returns it."""
        
        # append directory name (no size)
        iterDir = store.append (iterDisk)
        store.set (iterDir, 0, dir[0])
        store.set (iterDir, 1, "")
        store.set (iterDir, 2, 1.0)
        return iterDir
            
    def create_file_iter (self, store, file, iterDir):
        """Creates an iterator for the specified file and returns it."""
        
        # append file name and size (optional)
        iterFile = store.append (iterDir)
        store.set (iterFile, 0, file[0])
        if file[1] != None:
            store.set (iterFile, 1, str (file[1]))
        else:
            store.set (iterFile, 1, "")
        store.set (iterFile, 2, 1.0)
        return iterFile

    def is_substring_contained (self, text, subString, caseSensitive):
        """This method checks whether the specified substring is contained
        in the specified text. This can be done case sensitive or not."""
        
        if caseSensitive == False:
            text = text.lower ()
            subString = subString.lower ()
            
        return string.find (text, subString) >= 0
