# -*- coding: utf-8 -*-
#
# Copyright (c) 2009 by FlashCode <flashcode@flashtux.org>
#
# 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/>.
#

#
# Quick jump to buffers.
# (this script requires WeeChat 0.3.0 or newer)
#
# History:
#
# 2010-03-25, m4v <lambdae2@gmail.com>:
#     version 1.1: use a space for match the end of a string
# 2009-11-16, FlashCode <flashcode@flashtux.org>:
#     version 1.0: add new option for displaying short names
# 2009-06-15, FlashCode <flashcode@flashtux.org>:
#     version 0.9: fix typo in /help go with command /key
# 2009-05-16, FlashCode <flashcode@flashtux.org>:
#     version 0.8: search buffer by number, fix bug when window is split
# 2009-05-03, FlashCode <flashcode@flashtux.org>:
#     version 0.7: eat tab key (do not complete input, just move buffer pointer)
# 2009-05-02, FlashCode <flashcode@flashtux.org>:
#     version 0.6: sync with last API changes
# 2009-03-22, FlashCode <flashcode@flashtux.org>:
#     version 0.5: update modifier signal name for input text display,
#                  fix arguments for function string_remove_color
# 2009-02-18, FlashCode <flashcode@flashtux.org>:
#     version 0.4: do not hook command and init options if register failed
# 2009-02-08, FlashCode <flashcode@flashtux.org>:
#     version 0.3: case insensitive search for buffers names
# 2009-02-08, FlashCode <flashcode@flashtux.org>:
#     version 0.2: add help about Tab key
# 2009-02-08, FlashCode <flashcode@flashtux.org>:
#     version 0.1: initial release
#

import weechat

SCRIPT_NAME    = "go"
SCRIPT_AUTHOR  = "FlashCode <flashcode@flashtux.org>"
SCRIPT_VERSION = "1.1"
SCRIPT_LICENSE = "GPL3"
SCRIPT_DESC    = "Quick jump to buffers"

# script options
settings = {
    "color_number"                 : "yellow,magenta",
    "color_number_selected"        : "yellow,red",
    "color_name"                   : "black,cyan",
    "color_name_selected"          : "black,brown",
    "color_name_highlight"         : "red,cyan",
    "color_name_highlight_selected": "red,brown",
    "message"                      : "Go to: ",
    "short_name"                   : "off",
}

# hooks management
hook_command_run = {
    "input" : ("/input *",  "command_run_input"),
    "buffer": ("/buffer *", "command_run_buffer"),
    "window": ("/window *", "command_run_window"),
}
hooks = {}

# input before command /go (we'll restore it later)
saved_input = ""

# last user input (if changed, we'll update list of matching buffers)
old_input = None

# matching buffers
buffers = []
buffers_pos = 0

if weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE,
                    SCRIPT_DESC, "go_unload_script", ""):
    weechat.hook_command("go", "Quick jump to buffers", "",
                         "You can bind command to a key, for example:\n" +
                         "  /key bind meta-g /go\n\n" +
                         "You can use completion key (commonly Tab and shift-Tab) to select " +
                         "next/previous buffer in list.",
                         "", "go_cmd", "")
    for option, default_value in settings.iteritems():
        if weechat.config_get_plugin(option) == "":
            weechat.config_set_plugin(option, default_value)

def unhook_one(hook):
    """ Unhook something hooked by this script """
    global hooks
    if hook in hooks:
        weechat.unhook(hooks[hook])
        del hooks[hook]

def unhook_all():
    """ Unhook all """
    global hook_command_run
    unhook_one("modifier")
    map(unhook_one, hook_command_run.keys())

def hook_all():
    """ Hook command_run and modifier """
    global hook_command_run, hooks
    for hook, value in hook_command_run.iteritems():
        if hook not in hooks:
            hooks[hook] = weechat.hook_command_run(value[0], value[1], "")
    if "modifier" not in hooks:
        hooks["modifier"] = weechat.hook_modifier(
            "input_text_display_with_cursor", "input_modifier", "")

def go_start(buffer):
    """ Start go on buffer """
    global saved_input, old_input, buffers_pos
    hook_all()
    saved_input = weechat.buffer_get_string(buffer, "input")
    weechat.buffer_set(buffer, "input", "")
    old_input = None
    buffers_pos = 0

def go_end(buffer):
    """ End go on buffer """
    global saved_input, old_input
    unhook_all()
    weechat.buffer_set(buffer, "input", saved_input)
    old_input = None

def go_cmd(data, buffer, args):
    """ Command "/go": just hook what we need """
    global hooks
    if "modifier" in hooks:
        go_end(buffer)
    else:
        go_start(buffer)
    return weechat.WEECHAT_RC_OK

def get_matching_buffers(input):
    """ Return list with buffers matching user input """
    global buffers_pos
    list = []
    if len(input) == 0:
        buffers_pos = 0
    input = input.lower()
    infolist = weechat.infolist_get("buffer", "", "")
    while weechat.infolist_next(infolist):
        if weechat.config_get_plugin("short_name") == "on":
            name = weechat.infolist_string(infolist, "short_name")
        else:
            name = weechat.infolist_string(infolist, "name")
        number = weechat.infolist_integer(infolist, "number")
        matching = name.lower().find(input) >= 0
        if not matching and input[-1] == ' ':
            matching = name.lower().endswith(input.strip())
        if not matching and input.isdigit():
            matching = str(number).startswith(input)
        if len(input) == 0 or matching:
            list.append({"number": number, "name": name})
            if len(input) == 0 and weechat.infolist_pointer(infolist, "pointer") == weechat.current_buffer():
                buffers_pos = len(list) - 1
    weechat.infolist_free(infolist)
    return list

def buffers_to_string(buffers, pos, input):
    """ Return string built using list of buffers found (matching user input) """
    global settings
    string = ""
    colors = {}
    input = input.lower()
    for option in settings:
        colors[option] = weechat.config_get_plugin(option)
    for i in range(len(buffers)):
        selected = ""
        if i == pos:
            selected = "_selected"
        index = buffers[i]["name"].lower().find(input)
        if index >= 0:
            index2 = index + len(input)
            string += " " + \
                weechat.color(colors["color_number" + selected]) + str(buffers[i]["number"]) + \
                weechat.color(colors["color_name" + selected]) + buffers[i]["name"][:index] + \
                weechat.color(colors["color_name_highlight" + selected]) + buffers[i]["name"][index:index2] + \
                weechat.color(colors["color_name" + selected]) + buffers[i]["name"][index2:] + \
                weechat.color("reset")
        else:
            string += " " + \
                weechat.color(colors["color_number" + selected]) + str(buffers[i]["number"]) + \
                weechat.color(colors["color_name" + selected]) + buffers[i]["name"] + \
                weechat.color("reset")
    if string != "":
        string = "  " + string
    return string

def input_modifier(data, modifier, modifier_data, string):
    """ This modifier is called when input text item is built by WeeChat
    (commonly after changes in input or cursor move), it builds new input with
    prefix ("Go to:"), and suffix (list of buffers found) """
    global old_input, buffers, buffers_pos
    if modifier_data != weechat.current_buffer():
        return ""
    names = ""
    input = weechat.string_remove_color(string, "")
    input = input.lstrip()
    if old_input == None or input != old_input:
        old_buffers = buffers
        buffers = get_matching_buffers(input)
        if buffers != old_buffers and len(input) > 0:
            buffers_pos = 0
        old_input = input
    names = buffers_to_string(buffers, buffers_pos, input.strip())
    return weechat.config_get_plugin("message") + string + names

def command_run_input(data, buffer, command):
    """ Function called when a command "/input xxxx" is run """
    global buffers, buffers_pos
    if command == "/input search_text" or command.find("/input jump") == 0:
        # search text or jump to another buffer is forbidden now
        return weechat.WEECHAT_RC_OK_EAT
    elif command == "/input complete_next":
        # choose next buffer in list
        buffers_pos += 1
        if buffers_pos >= len(buffers):
            buffers_pos = 0
        weechat.hook_signal_send("input_text_changed",
                                 weechat.WEECHAT_HOOK_SIGNAL_STRING, "")
        return weechat.WEECHAT_RC_OK_EAT
    elif command == "/input complete_previous":
        # choose previous buffer in list
        buffers_pos -= 1
        if buffers_pos < 0:
            buffers_pos = len(buffers) - 1
        weechat.hook_signal_send("input_text_changed",
                                 weechat.WEECHAT_HOOK_SIGNAL_STRING, "")
        return weechat.WEECHAT_RC_OK_EAT
    elif command == "/input return":
        # switch to selected buffer (if any)
        go_end(buffer)
        if len(buffers) > 0:
            weechat.command(buffer, "/buffer " + str(buffers[buffers_pos]["number"]))
        return weechat.WEECHAT_RC_OK_EAT
    return weechat.WEECHAT_RC_OK

def command_run_buffer(data, buffer, command):
    """ Function called when a command "/buffer xxxx" is run """
    return weechat.WEECHAT_RC_OK_EAT

def command_run_window(data, buffer, command):
    """ Function called when a command "/buffer xxxx" is run """
    return weechat.WEECHAT_RC_OK_EAT

def go_unload_script():
    """ Function called when script is unloaded """
    unhook_all()
    return weechat.WEECHAT_RC_OK
