# Terminator by Chris Jones <cmsj@tenshu.net>
# GPL v2 only
"""searchbar.py - classes necessary to provide a terminal search bar"""

import gi
from gi.repository import Gtk, Gdk
gi.require_version('Vte', '2.91')  # vte-0.38 (gnome-3.14)
from gi.repository import Vte
from gi.repository import GObject
from gi.repository import GLib

from .translation import _
from .config import Config
from . import regex
from .util import dbg

# pylint: disable-msg=R0904
class Searchbar(Gtk.HBox):
    """Class implementing the Searchbar widget"""

    __gsignals__ = {
        'end-search': (GObject.SignalFlags.RUN_LAST, None, ()),
    }

    entry = None
    next = None
    prev = None
    wrap = None

    vte = None
    config = None

    searchstring = None
    searchre = None

    def __init__(self):
        """Class initialiser"""
        GObject.GObject.__init__(self)

        # default regex flags are not CASELESS
        self.regex_flags_pcre2 = regex.FLAGS_PCRE2
        self.regex_flags_glib = regex.FLAGS_GLIB

        self.config = Config()

        if not self.config.base.get_item('case_sensitive'):
            self.regex_flags_pcre2 |= regex.PCRE2_CASELESS
            self.regex_flags_glib |= regex.GLIB_CASELESS

        self.get_style_context().add_class("terminator-terminal-searchbar")

        # Search text
        self.entry = Gtk.Entry()
        self.entry.set_activates_default(True)
        self.entry.show()
        self.entry.connect('activate', self.do_search)
        self.entry.connect('key-press-event', self.search_keypress)

        # Label
        label = Gtk.Label(label=_('Search:'))
        label.show()
        label.set_margin_start(10)
        label.set_margin_end(5)

        # Close Button
        close = Gtk.Button()
        close.set_relief(Gtk.ReliefStyle.NONE)
        close.set_focus_on_click(False)
        icon = Gtk.Image()
        icon.set_from_stock(Gtk.STOCK_CLOSE, Gtk.IconSize.MENU)
        close.add(icon)
        close.set_name('terminator-search-close-button')
        if hasattr(close, 'set_tooltip_text'):
            close.set_tooltip_text(_('Close Search bar'))
        close.connect('clicked', self.end_search)
        close.show_all()

        # Next Button
        self.next = Gtk.Button.new_with_label('Next')
        self.next.show()
        self.next.set_sensitive(False)
        self.next.connect('clicked', self.next_search)

        # Previous Button
        self.prev = Gtk.Button.new_with_label('Prev')
        self.prev.show()
        self.prev.set_sensitive(False)
        self.prev.connect('clicked', self.prev_search)

        # Match Case checkbox
        self.match_case = Gtk.CheckButton.new_with_label('Match Case')
        self.match_case.show()
        self.match_case.set_sensitive(True)
        self.match_case.set_active(self.config.base.get_item('case_sensitive'))
        self.match_case.connect('toggled', self.match_case_toggled)

        # Wrap checkbox
        self.wrap = Gtk.CheckButton.new_with_label('Wrap')
        self.wrap.show()
        self.wrap.set_sensitive(True)
        self.wrap.set_active(True)
        self.wrap.connect('toggled', self.wrap_toggled)

        # Invert Search checkbox
        self.invert_search = Gtk.CheckButton.new_with_label('Invert Search')
        self.invert_search.show()
        self.search_is_inverted = self.config.base.get_item('invert_search')
        self.invert_search.set_active(self.search_is_inverted)
        self.invert_search.connect('toggled', self.wrap_invert_search)

        self.pack_start(label, False, True, 0)
        self.pack_start(self.entry, True, True, 0)
        self.pack_start(self.prev, False, False, 0)
        self.pack_start(self.next, False, False, 0)
        self.pack_start(self.wrap, False, False, 0)
        self.pack_start(self.match_case, False, False, 0)
        self.pack_start(self.invert_search, False, False, 0)
        self.pack_end(close, False, False, 0)

        self.hide()
        self.set_no_show_all(True)

    def wrap_invert_search(self, toggled):
        self.search_is_inverted = toggled.get_active()
        self.invert_search.set_active(toggled.get_active())
        self.config.base.set_item('invert_search', toggled.get_active())
        self.config.save()

    def wrap_toggled(self, toggled):
        toggled_state = toggled.get_active()
        self.vte.search_set_wrap_around(toggled_state)
        if toggled_state:
            self.prev.set_sensitive(True)
            self.next.set_sensitive(True)

    def match_case_toggled(self, toggled):
        """Handles Match Case checkbox toggles"""

        toggled_state = toggled.get_active()
        if not toggled_state:
            #  Add the CASELESS regex flags when the checkbox is not checked.
            try:
                self.regex_flags_pcre2 = (regex.FLAGS_PCRE2 | regex.PCRE2_CASELESS)
            except TypeError:
                # if PCRE2 support is not available
                pass

            # The code will fall back to use this GLib regex when PCRE2 is not available
            self.regex_flags_glib = (regex.FLAGS_GLIB | regex.GLIB_CASELESS)
        else:
            # Default state of the check box is unchecked. CASELESS regex flags are not added.
            self.regex_flags_pcre2 = regex.FLAGS_PCRE2
            self.regex_flags_glib = regex.FLAGS_GLIB
        
        self.config.base.set_item('case_sensitive', toggled_state)
        self.config.save()
        self.do_search(self.entry) #  Start a new search every time the check box is toggled.    

    def get_vte(self):
        """Find our parent widget"""
        parent = self.get_parent()
        if parent:
            self.vte = parent.vte
            #turn on wrap by default
            self.vte.search_set_wrap_around(True)

    # pylint: disable-msg=W0613
    def search_keypress(self, widget, event):
        """Handle keypress events"""
        key = Gdk.keyval_name(event.keyval)
        if key == 'Escape':
            self.end_search()
        elif (event.state & Gdk.ModifierType.SHIFT_MASK)\
                and (event.keyval == Gdk.KEY_Return or event.keyval == Gdk.KEY_KP_Enter):
            if self.search_is_inverted:
                self.next_search(None)
            else:
                self.prev_search(None)
            return True

    def start_search(self):
        """Show ourselves"""
        if not self.vte:
            self.get_vte()

        self.show()
        self.entry.grab_focus()

    def do_search(self, widget):
        """Trap and re-emit the clicked signal"""
        dbg('entered do_search')
        searchtext = self.entry.get_text()
        dbg('searchtext: %s' % searchtext)
        if searchtext == '':
            return

        self.searchre = None
        if regex.FLAGS_PCRE2:
            try:
                self.searchre = Vte.Regex.new_for_search(searchtext, len(searchtext), self.regex_flags_pcre2)
                dbg('search RE: %s' % self.searchre)
                self.vte.search_set_regex(self.searchre, 0)
            except GLib.Error:
                # happens when PCRE2 support is not builtin (Ubuntu < 19.10)
                pass

        if not self.searchre:
            # fall back to old GLib regex
            self.searchre = GLib.Regex(searchtext, self.regex_flags_glib, 0)
            dbg('search RE: %s' % self.searchre)
            self.vte.search_set_gregex(self.searchre, 0)

        self.next.set_sensitive(True)
        self.prev.set_sensitive(True)
        # switch search direction based on the inversion checkbox
        if not self.search_is_inverted:
            self.next_search(None)
        else:
            self.prev_search(None)

    def next_search(self, widget):
        """Search forwards and jump to the next result, if any"""
        found_result = self.vte.search_find_next()
        if not self.wrap.get_active():
            self.next.set_sensitive(found_result)
        else:
            self.next.set_sensitive(True)
        self.prev.set_sensitive(True)
        return

    def prev_search(self, widget):
        """Jump back to the previous search"""
        found_result = self.vte.search_find_previous()
        if not self.wrap.get_active():
            self.prev.set_sensitive(found_result)
        else:
            self.prev.set_sensitive(True)
        self.next.set_sensitive(True)
        return

    def end_search(self, widget=None):
        """Trap and re-emit the end-search signal"""
        self.searchstring = None
        self.searchre = None
        self.emit('end-search')

    def get_search_term(self):
        """Return the currently set search term"""
        return(self.entry.get_text())

GObject.type_register(Searchbar)
