# -*- coding: utf-8 -*-
#
# Copyright (c) 2012-2018 by nils_2 <weechatter@arcor.de>
#
# save and restore global and/or buffer command history
#
# 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/>.
#
# 2021-05-02, Sébastien Helleu <flashcode@flashtux.org>
#       0.8.2 : add compatibility with WeeChat >= 3.2 (XDG directories)
#
# 2018-08-04: nils_2 (freenode.#weechat)
#       0.8 : add option 'save_buffer'
#           : thanks catbeard for revise the help text
#
# 2018-08-01: nils_2 (freenode.#weechat)
#       0.7 : rmodifier routine removed
#           : bug fixes
#
# 2017-12-14: Sébastien Helleu <flashcode@flashtux.org>
#       0.6 : rename command "/autosetbuffer" by "/buffer_autoset" in example
#
# 2015-04-05: nils_2 (freenode.#weechat)
#       0.5 : change priority of hook_signal('buffer_opened') to 100
#
# 2013-01-25: nils_2 (freenode.#weechat)
#       0.4 : make script compatible with Python 3.x
#
# 2013-01-20: nils_2, (freenode.#weechat)
#       0.3: fix wrong command argument in help-text
#
# 2012-12-21: nils_2, (freenode.#weechat)
#       0.2 : fix UnicodeEncodeError
#
# 2012-12-09: nils_2, (freenode.#weechat)
#       0.1 : initial release
#
# thanks to nestib for help with regex and for the rmodifier idea
#
# requires: WeeChat version 0.4.0
#
# Development is currently hosted at
# https://github.com/weechatter/weechat-scripts

try:
    import weechat,re,os

except Exception:
    print("This script must be run under WeeChat.")
    print("Get WeeChat now at: http://www.weechat.org/")
    quit()

SCRIPT_NAME     = 'histman'
SCRIPT_AUTHOR   = 'nils_2 <weechatter@arcor.de>'
SCRIPT_VERSION  = '0.8.2'
SCRIPT_LICENSE  = 'GPL'
SCRIPT_DESC     = 'save and restore global and/or buffer command history'

OPTIONS         = { 'number'       : ('0','number of history commands/text to save. A positive number will save from oldest to latest, a negative number will save from latest to oldest. 0 = save whole history (e.g. -10 will save the last 10 history entries'),
                    'pattern'      : ('(.*password|.*nickserv|/quit)','a simple regex to ignore commands/text. Empty value disable pattern matching'),
                    'skip_double'  : ('on','skip lines that already exists (case sensitive)'),
                    'save'         : ('all','define what should be saved from history. Possible values are \"command\", \"text\", \"all\". This is a fallback option (see /help ' + SCRIPT_NAME +')'),
                    'history_dir'  : ('%h/history','locale cache directory for history files (\"%h\" will be replaced by WeeChat data directory)'),
                    'save_global'  : ('off','save global history, possible values are \"command\", \"text\", \"all\" or \"off\"(default: off)'),
                    'save_buffer'  : ('off','save buffer history from all buffers, possible values are \"on\", \"off\". Using this option, localvar from buffer will be ignored (default: off)'),
                    'min_length'   : ('2','minimum length of command/text (default: 2)'),
                    'buffer_close' : ('off','save command history, when buffer will be closed (default: off)'),
                  }

filename_global_history = 'global_history'
possible_save_options = ['command', 'text', 'all']

history_list = []

# =================================[ save/restore buffer history ]================================
def save_history():
    global history_list
    # get buffers
    ptr_infolist_buffer = weechat.infolist_get('buffer','','')

    while weechat.infolist_next(ptr_infolist_buffer):
        ptr_buffer = weechat.infolist_pointer(ptr_infolist_buffer,'pointer')

        # check for localvar_save_history
        if not weechat.buffer_get_string(ptr_buffer, 'localvar_save_history') and OPTIONS['save_buffer'].lower() == 'off':
            continue

        plugin = weechat.buffer_get_string(ptr_buffer, 'localvar_plugin')
        name = weechat.buffer_get_string(ptr_buffer, 'localvar_name')
        filename = get_filename_with_path('%s.%s' % (plugin,name))

        get_buffer_history(ptr_buffer)
        if len(history_list):
            write_history(filename)

    weechat.infolist_free(ptr_infolist_buffer)

    if OPTIONS['save_global'].lower() != 'off':
        get_buffer_history('')  # buffer pointer (if not set, return global history)
        if len(history_list):
            write_history(filename_global_history)

def get_buffer_history(ptr_buffer):
    global history_list

    history_list = []
    ptr_buffer_history = weechat.infolist_get('history',ptr_buffer,'')

    if not ptr_buffer_history:
        return

    while weechat.infolist_next(ptr_buffer_history):
        line = weechat.infolist_string(ptr_buffer_history, 'text')

        if add_buffer_line(line,ptr_buffer):
            history_list.insert(0,line)

    weechat.infolist_free(ptr_buffer_history)

# return 1; line will be saved
# return 0; line won't be saved
def add_buffer_line(line, ptr_buffer):
    global history_list

    # min_length reached?
    if len(line) < int(OPTIONS['min_length']):
        return 0

    add_line = 0

    # ptr_buffer empty = global history
    if ptr_buffer: # buffer history
        save_history = weechat.buffer_get_string(ptr_buffer, 'localvar_save_history').lower()
        if not save_history.lower() in possible_save_options:
            save_history = OPTIONS['save'].lower()
    else:       # global history
        save_history = OPTIONS['save_global'].lower()
        if not OPTIONS['save_global'].lower() in possible_save_options:
            save_history = OPTIONS['save'].lower()

    # no save option given? save nothing
    if save_history == '':
        return 0

    if save_history == 'command':
        command_chars = ("/%s" % weechat.config_string(weechat.config_get('weechat.look.command_chars')))
        # a valid command must have at least two chars and first and second char are not equal!
        if len(line) > 1 and line[0] in command_chars and line[0] != line[1]:
            add_line = 1
        else:
            return 0
    elif save_history == 'text':
        command_chars = ("/%s" % weechat.config_string(weechat.config_get('weechat.look.command_chars')))
        # test for "//" = text
        if line[0] == line[1] and line[0] in command_chars:
            add_line = 1
        # test for "/n" = command
        elif line[0] != line[1] and line[0] in command_chars:
            return 0
        else:
            add_line = 1
    elif save_history == 'all':
        add_line = 1
    else: # not one of given values. save nothing!
        return 0

    # lines already exist?
    if OPTIONS['skip_double'].lower() == 'on':
        history_list2 = []
        history_list2 = [element.lower() for element in history_list]
        if line.lower() in history_list2:
            return 0
        else:
            add_line = 1
    else:
        add_line = 1

    if add_line == 0:
        return 0

    pattern_matching = 0
    # pattern matching for user option
    if OPTIONS['pattern'] != '':
        filter_re=re.compile(OPTIONS['pattern'], re.I)
        # pattern matched
        if filter_re.match(line):
            pattern_matching = 1

    if add_line == 1 and pattern_matching == 0:
        return 1
    return 0

# =================================[ read/write history to file ]=================================
def read_history(filename,ptr_buffer):
    global_history = 0

    # get buffer_autoset_option as a fallback. could happen that the localvar is not already set to buffer
    plugin = weechat.buffer_get_string(ptr_buffer, 'localvar_plugin')
    name = weechat.buffer_get_string(ptr_buffer, 'localvar_name')
    buffer_autoset_option = ('buffer_autoset.buffer.%s.%s.localvar_set_save_history' % (plugin,name))
    buffer_autoset_option = weechat.config_get(buffer_autoset_option)

    # global history does not use buffer pointers!
    if filename == filename_global_history:
        global_history = 1

    filename = get_filename_with_path(filename)

    # filename exists?
    if not os.path.isfile(filename):
        return

    # check for buffer history (0, global = 1)
    if global_history == 0 and OPTIONS['save_buffer'].lower() == 'off':
        # localvar_save_history exists for buffer?
        if not ptr_buffer and not buffer_autoset_option and not weechat.buffer_get_string(ptr_buffer, 'localvar_save_history'):
            return
    hdata = weechat.hdata_get('history')
    if not hdata:
        return

    try:
        f = open(filename, 'r')
#        for line in f.xreadlines():    # old python 2.x
        for line in f:                  # should also work with python 2.x
#            line = line.decode('utf-8')
            line = str(line.strip())
            if ptr_buffer:
                # add to buffer history
                weechat.hdata_update(hdata, '', { 'buffer': ptr_buffer, 'text': line })
            else:
                # add to global history
                weechat.hdata_update(hdata, '', { 'text': line })
        f.close()
    except:
        if global_history == 1:
            weechat.prnt('','%s%s: Error loading global history from "%s"' % (weechat.prefix('error'), SCRIPT_NAME, filename))
        else:
            name = weechat.buffer_get_string(ptr_buffer, 'localvar_name')
            weechat.prnt('','%s%s: Error loading buffer history for buffer "%s" from "%s"' % (weechat.prefix('error'), SCRIPT_NAME, name, filename))
        raise

def write_history(filename):
    global history_list

    filename = get_filename_with_path(filename)

    if OPTIONS['number'] != '' and OPTIONS['number'].isdigit():
        if int(OPTIONS['number']) < 0:
            save_from_position = len(history_list) - abs(int(OPTIONS['number']))
            if save_from_position == len(history_list) or save_from_position < 0:
                save_from_position = 0
        elif int(OPTIONS['number']) > 0:
            save_to_position = int(OPTIONS['number'])
            if save_to_position > len(history_list):
                save_to_position = len(history_list)
        else:
            save_from_position = 0
    try:
        f = open(filename, 'w')

        if int(OPTIONS['number']) <= 0:
            i = save_from_position
            # for i in range(len(a)):
            while i < len(history_list):
                f.write('%s\n' % history_list[i])
                i = i + 1
        if int(OPTIONS['number']) > 0:
            i = 0
            while i < save_to_position:
                f.write('%s\n' % history_list[i])
                i = i + 1
        f.close()

    except:
        weechat.prnt('','%s%s: Error writing history to "%s"' % (weechat.prefix('error'),SCRIPT_NAME,filename))
        raise

def get_filename_with_path(filename):
    options = {
        'directory': 'data',
    }
    path = weechat.string_eval_path_home(OPTIONS['history_dir'], {}, {}, options)
    return os.path.join(path, filename)

def config_create_dir():
    options = {
        'directory': 'data',
    }
    dir = weechat.string_eval_path_home(OPTIONS['history_dir'], {}, {}, options)
    if not os.path.isdir(dir):
        os.makedirs(dir, mode=0o700)

# ===========================================[ Hooks() ]==========================================
def create_hooks():
    # create hooks
    weechat.hook_signal('quit', 'quit_signal_cb', '')
    weechat.hook_signal('upgrade_ended', 'upgrade_ended_cb', '')
    # low priority for hook_signal('buffer_opened') to ensure that buffer_autoset hook_signal() runs first
    weechat.hook_signal('1000|buffer_opened', 'buffer_opened_cb', '')
    weechat.hook_config('plugins.var.python.' + SCRIPT_NAME + '.*', 'toggle_refresh', '' )
    weechat.hook_signal('buffer_closing', 'buffer_closing_cb', '')

def quit_signal_cb(data, signal, signal_data):
    # create dir, if not exist
    config_create_dir()
    save_history()
    return weechat.WEECHAT_RC_OK

def buffer_opened_cb(data, signal, signal_data):
    ptr_buffer = signal_data
    plugin = weechat.buffer_get_string(ptr_buffer, 'localvar_plugin')
    name = weechat.buffer_get_string(ptr_buffer, 'localvar_name')
    filename = get_filename_with_path('%s.%s' % (plugin,name))
    read_history(filename,ptr_buffer)
    return weechat.WEECHAT_RC_OK

def buffer_closing_cb(data, signal, signal_data):
    if OPTIONS['buffer_close'].lower() == 'on' and signal_data:
        # check for localvar_save_history
        if not weechat.buffer_get_string(signal_data, 'localvar_save_history') and OPTIONS['save_buffer'].lower() == 'off':
            return weechat.WEECHAT_RC_OK

        plugin = weechat.buffer_get_string(signal_data, 'localvar_plugin')
        name = weechat.buffer_get_string(signal_data, 'localvar_name')
        filename = get_filename_with_path('%s.%s' % (plugin,name))
        get_buffer_history(signal_data)

        if len(history_list):
            write_history(filename)
    return weechat.WEECHAT_RC_OK

def upgrade_ended_cb(data, signal, signal_data):
    weechat.buffer_set(weechat.buffer_search_main(), 'localvar_set_histman', 'on')
    return weechat.WEECHAT_RC_OK

def histman_cmd_cb(data, buffer, args):
    if args == '':
        weechat.command('', '/help %s' % SCRIPT_NAME)
        return weechat.WEECHAT_RC_OK

    argv = args.strip().split(' ', 1)
    if len(argv) == 0:
        return weechat.WEECHAT_RC_OK

    if argv[0].lower() == 'save':
        config_create_dir()
        save_history()
    elif argv[0].lower() == 'list':
        weechat.command('','/set *.localvar_set_save_history')
    else:
        weechat.command('', '/help %s' % SCRIPT_NAME)

    return weechat.WEECHAT_RC_OK

# ================================[ weechat options & description ]===============================
def init_options():
    for option,value in list(OPTIONS.items()):
        if not weechat.config_is_set_plugin(option):
            weechat.config_set_plugin(option, value[0])
            weechat.config_set_desc_plugin(option, '%s (default: "%s")' % (value[1], value[0]))
            OPTIONS[option] = value[0]
        else:
            OPTIONS[option] = weechat.config_get_plugin(option)

def toggle_refresh(pointer, name, value):
    global OPTIONS
    option = name[len('plugins.var.python.' + SCRIPT_NAME + '.'):]        # get optionname
    OPTIONS[option] = value                                            # save new value
    weechat.bar_item_update(SCRIPT_NAME)
    return weechat.WEECHAT_RC_OK

# ================================[ main ]===============================
if __name__ == '__main__':
    if weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, SCRIPT_DESC, '', ''):
        version = weechat.info_get('version_number', '') or 0

        weechat.hook_command(SCRIPT_NAME, SCRIPT_DESC, '[save] || [list]',
                            '  save: force to save command history\n'
                            '  list: list local buffer variable(s)\n'
                            '\n'
                            'If you \"/quit\" WeeChat, the script will automatically save the command history to a file.\n'
                            'You can also force the script to save command history, when you close a buffer with /buffer close.\n'
                            'If you restart WeeChat, the command history will be restored, when buffer opens again.\n'
                            'To save and restore \"global\" command history, use option \"save_global\".\n'
                            '\n'
                            'The command history of a buffer will be saved \"only\", if the the local variable \"save_history\" is set.\n'
                            'You will need script \"buffer_autoset.py\" to make a local variabe persistent (see examples, below)!!\n'
                            '\n'
                            'You can use option \"save_buffer\" to save command history for *all* buffers, localvar will be ignored using\n'
                            'this option.\n'
                            'You can use following values for local variable:\n'
                            '  command: save commands only\n'
                            '     text: save text only (text sent to a channel buffer)\n'
                            '      all: save commands and text\n'
                            '\n'
                            'Examples:\n'
                            ' save the command history manually (for example with /cron script or with an /trigger):\n'
                            '   /' + SCRIPT_NAME + ' save\n'
                            ' save and restore command history for buffer #weechat on freenode (text only):\n'
                            '   /buffer_autoset add irc.freenode.#weechat localvar_set_save_history text\n'
                            ' save and restore command history for weechat core buffer (commands only):\n'
                            '   /buffer_autoset add core.weechat localvar_set_save_history command\n',
                            'save %-'
                            '|| list %-',
                            'histman_cmd_cb', '')

        if int(version) >= 0x00040000:
            if weechat.buffer_get_string(weechat.buffer_search_main(),'localvar_histman') == 'on':
                init_options()
                create_hooks()
                weechat.prnt('','%s%s: do not start this script two times. command history was already restored, during this session!' % (weechat.prefix('error'),SCRIPT_NAME))
            else:
                init_options()
                # create dir, if not exist
                config_create_dir()

                # look for global_history
                if OPTIONS['save_global'].lower() != 'off':
                    read_history(filename_global_history,'')

                # core buffer is already open on script startup. Check manually!
                filename = get_filename_with_path('core.weechat')
                read_history('core.weechat',weechat.buffer_search_main())

                create_hooks()

                # set localvar, to start script only once!
                weechat.buffer_set(weechat.buffer_search_main(), 'localvar_set_histman', 'on')
        else:
            weechat.prnt('','%s%s: needs version 0.4.0 or higher' % (weechat.prefix('error'),SCRIPT_NAME))
            weechat.command('','/wait 1ms /python unload %s' % SCRIPT_NAME)
