# Copyright (C) 2008 Dejan Muhamedagic <dmuhamedagic@suse.de>
# 
# 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.1 of the License, or (at your option) any later version.
# 
# This software 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 library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
#

import os
import re
from cache import WCache
from utils import odict, page_string
from vars import gethomedir
from msg import *

#
# help or make users feel less lonely
#
def add_shorthelp(topic,shorthelp,topic_help):
    '''
    Join topics ("%s,%s") if they share the same short
    description.
    '''
    for i in range(len(topic_help)):
        if topic_help[i][1] == shorthelp:
            topic_help[i][0] = "%s,%s" % (topic_help[i][0], topic)
            return
    topic_help.append([topic, shorthelp])
def dump_short_help(help_tab):
    topic_help = []
    for topic in help_tab:
        if topic == '.':
            continue
        # with odict, for whatever reason, python parses differently:
        # help_tab["..."] = ("...","...") and
        # help_tab["..."] = ("...","""
        # ...""")
        # a parser bug?
        if type(help_tab[topic][0]) == type(()):
            shorthelp = help_tab[topic][0][0]
        else:
            shorthelp = help_tab[topic][0]
        add_shorthelp(topic,shorthelp,topic_help)
    for t,d in topic_help:
        print "\t%-16s %s" % (t,d)
def overview(help_tab):
    print ""
    print help_tab['.'][1]
    print ""
    print "Available commands:"
    print ""
    dump_short_help(help_tab)
    print ""
def topic_help(help_tab,topic):
    if topic not in help_tab:
        print "There is no help for topic %s" % topic
        return
    if type(help_tab[topic][0]) == type(()):
        shorthelp = help_tab[topic][0][0]
        longhelp = help_tab[topic][0][1]
    else:
        shorthelp = help_tab[topic][0]
        longhelp = help_tab[topic][1]
    if longhelp:
        page_string(longhelp)
    else:
        print shorthelp
def cmd_help(help_tab,topic = ''):
    "help!"
    # help_tab is an odict (ordered dictionary):
    # help_tab[topic] = (short_help,long_help)
    # topic '.' is a special entry for the top level
    if not help_tab:
        common_info("sorry, help not available")
        return
    if not topic:
        overview(help_tab)
    else:
        topic_help(help_tab,topic)

def is_level(s):
    return len(s.split("_")) == 2

def help_short(s):
    r = re.search("help_[^,]+,(.*)\]\]", s)
    return r and r.group(1) or ''

class HelpSystem(object):
    '''
    The help system. All help is in the following form in the
    manual:
    [[cmdhelp_<level>_<cmd>,<short help text>]]
    === ...
    Long help text.
    ...
    [[cmdhelp_<level>_<cmd>,<short help text>]]

    Help for the level itself is like this:

    [[cmdhelp_<level>,<short help text>]]
    '''
    help_text_file = "@datadir@/@PACKAGE@/crm.8.txt"
    index_file = os.path.join(gethomedir(),".crm_help_index")
    def __init__(self):
        self.key_pos = {}
        self.leveld = {}
        self.no_help_file = False  # don't print repeatedly messages
        self.bad_index = False  # don't print repeatedly warnings for bad index
    def open_file(self,name,mode):
        try:
            f = open(name,mode)
            return f
        except IOError,msg:
            common_err("%s open: %s"%(name,msg))
            common_err("extensive help system is not available")
            self.no_help_file = True
            return None
    def drop_index(self):
        common_info("removing index")
        os.unlink(self.index_file)
        self.key_pos = {}
        self.leveld = {}
        self.bad_index = True
    def mk_index(self):
        '''
        Prepare an index file, sorted by topic, with seek positions
        Do we need a hash on content?
        '''
        if self.no_help_file:
            return False
        crm_help_v = os.getenv("CRM_HELP_FILE")
        if crm_help_v:
            self.help_text_file = crm_help_v
        help_f = self.open_file(self.help_text_file,"r")
        if not help_f:
            return False
        idx_f = self.open_file(self.index_file,"w")
        if not idx_f:
            return False
        common_debug("building help index")
        key_pos = odict()
        while 1:
            pos = help_f.tell()
            s = help_f.readline()
            if not s:
                break
            if s.startswith("[["):
                r = re.search(r'..([^,]+),', s)
                if r:
                    key_pos[r.group(1)] = pos
        help_f.close()
        for key in key_pos:
            print >>idx_f, '%s %d' % (key,key_pos[key])
        idx_f.close()
        return True
    def is_index_old(self):
        try:
            t_idx = os.path.getmtime(self.index_file)
        except:
            return True
        try:
            t_help = os.path.getmtime(self.help_text_file)
        except:
            return True
        return t_help > t_idx
    def load_index(self):
        if self.is_index_old():
            self.mk_index()
        self.key_pos = {}
        self.leveld = {}
        idx_f = self.open_file(self.index_file,"r")
        if not idx_f:
            return False
        cur_lvl = ''
        for s in idx_f:
            a = s.split()
            if len(a) != 2:
                if not self.bad_index:
                    common_err("index file corrupt")
                    idx_f.close()
                    self.drop_index()
                    return self.load_index() # this runs only once
                return False
            key = a[0]
            fpos = long(a[1])
            if key.startswith("cmdhelp_"):
                if is_level(key):
                    if key != cur_lvl:
                        cur_lvl = key
                        self.leveld[cur_lvl] = []
                else:
                    self.leveld[cur_lvl].append(key)
            self.key_pos[key] = fpos
        idx_f.close()
        return True
    def __filter(self,s):
        if '<<' in s:
            return re.sub(r'<<[^,]+,(.+)>>', r'\1', s)
        else:
            return s
    def __load_help_one(self,key,skip = 2):
        longhelp = ''
        self.help_f.seek(self.key_pos[key])
        shorthelp = help_short(self.help_f.readline())
        for i in range(skip-1):
            self.help_f.readline()
        l = []
        for s in self.help_f:
            if s.startswith("[[") or s.startswith("="):
                break
            l.append(self.__filter(s))
        if l and l[-1] == '\n':  # drop the last line of empty
            l.pop()
        if l:
            longhelp = ''.join(l)
        if not shorthelp or not longhelp:
            if not self.bad_index:
                common_warn("help topic %s not found" % key)
                self.drop_index()
        return shorthelp,longhelp
    def cmdhelp(self,s):
        if not self.key_pos and not self.load_index():
            return None,None
        if not s in self.key_pos:
            if not self.bad_index:
                common_warn("help topic %s not found" % s)
                self.drop_index()
            return None,None
        return self.__load_help_one(s)
    def __load_level(self,lvl):
        '''
        For the given level, create a help table.
        '''
        if wcache.is_cached("lvl_help_tab_%s" % lvl):
            return wcache.retrieve("lvl_help_tab_%s" % lvl)
        if not self.key_pos and not self.load_index():
            return None
        self.help_f = self.open_file(self.help_text_file,"r")
        if not self.help_f:
            return None
        lvl_s = "cmdhelp_%s" % lvl
        if not lvl_s in self.leveld:
            if not self.bad_index:
                common_warn("help table for level %s not found" % lvl)
                self.drop_index()
            return None
        common_debug("loading help table for level %s" % lvl)
        help_tab = odict()
        help_tab["."] = self.__load_help_one(lvl_s)
        try:
            for key in self.leveld[lvl_s]:
                cmd = key[len(lvl_s)+1:]
                help_tab[cmd] = self.__load_help_one(key)
        except: pass
        self.help_f.close()
        help_tab["quit"] = ("exit the program", "")
        help_tab["help"] = ("show help", "")
        help_tab["end"] = ("go back one level", "")
        return help_tab
    def load_level(self,lvl):
        help_tab = self.__load_level(lvl)
        if self.bad_index: # try again
            help_tab = self.__load_level(lvl)
        return wcache.store("lvl_help_tab_%s" % lvl, help_tab)

wcache = WCache.getInstance()

# vim:ts=4:sw=4:et:
