#!/usr/bin/python

#
#   modules.py
#
#   -Python Darkness Bot-
#          Module support module
#
#   Copyright by Jannis A. Schnitzer
#   Created by Jannis A. Schnitzer on 2008-09-04
#
# This library 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 library 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., 59
# Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#

import irc
import auth
import sys
from essentials import *
    
class Help:
    "Help wrapper class"
    def __init__(self, modules):
        self.modules   = modules
        self.helpstr   = "This is Xjs' modularized python Darkness Bot."
        self.helpstr2  = "Type 'help all' to show descriptions of all modules and commands, 'help modules' to show all modules,"
        self.helpstr3  = "'help <modulename>' to show all commands of a specific module, or help <command> to show help about a specific command."
        
    def module(self, modname, outlong = False):
        "Returns help for the given module, and long help for all commands there if 'outlong' is True"
        if not self.modules.modlist.has_key(modname):
            return ""
        general = helpstr(self.modules.modlist[modname])
        if general:
            general = " "+general
        modstr = "["+modname+"]"+general
        cmdlist = [modstr]
        cmdstr = "    "
        count = 0
        if not self.modules.cmdlist.has_key(modname):
            return [modstr, cmdstr]
        for i in self.modules.cmdlist[modname]:
            cstr = i.command
            if not cstr:
                continue
            if not outlong:
                count = count + 1
                cmdstr += cstr + ", "
            else:
                cmdstr = "    "+self.cmd_long_raw(i)
                cmdlist.append(cmdstr)
        if not outlong:
            return [modstr, cmdstr[:len(cmdstr)-2]]
        else:
            return cmdlist
    
    def cmd_long_raw (self, cmd):
        "Returns a fancy string from a command entry"
        if not cmd.command:
            return ""
        authstr = ""
        if cmd.access:
            authstr = " (Access level "+str(cmd.access)+")"
        return "\'"+cmd.command+"\': "+helpstr(cmd.get_fcn())+authstr
    
    def cmd_long(self, cmdname):
        "Searches for the command in the databases and returns a string"
        for i in self.modules.cmdlist:
            for j in self.modules.cmdlist[i]:
                if j.command == cmdname:
                    return self.cmd_long_raw(j)
        return "Command "+cmdname+" was not found!"
    
    def version(self, irc):
        "Returns bot version"
        return irc.version

def empty(): return None


class Modules:
    "Module manager: supports loading and unloading of modules"
    def __init__(self, irc):
        self.irc        = irc
        self.modlist    = {}
        self.classlist  = {}
        self.activelist = {}
        self.typelist   = {} # contains lists for each typelist[type]
        self.cmdlist    = {} # contains Commands for later lookup
        
        self.typelist[types.ALL] = {"":{}}
        
        pm = Command("-ModuleManager-")
        pm.ctype = types.DMSG
        pm.command = "module"
        pm.chanre  = "*"
        pm.access  = 100
        pm.clas = self
        pm.fcn  = "pmodules"
        
        ph = Command("-ModuleManager-")
        ph.ctype = types.DMSG
        ph.command = "help"
        ph.chanre  = "*"
        ph.access  = 0
        ph.clas = self
        ph.fcn  = "p_help"
        
        pv = Command("-ModuleManager-")
        pv.ctype = types.DMSG
        pv.command = "version"
        pv.chanre  = "*"
        pv.access  = 0
        pv.clas = self
        pv.fcn  = "p_version"
        
        self.typelist[types.DMSG] = {}
        self.typelist[types.DMSG]["module"] = {}
        self.typelist[types.DMSG]["module"]["-ModuleManager-"] = pm
        self.typelist[types.DMSG]["help"] = {}
        self.typelist[types.DMSG]["help"]["-ModuleManager-"] = ph
        self.typelist[types.DMSG]["version"] = {}
        self.typelist[types.DMSG]["version"]["-ModuleManager-"] = pv
        
        self.cmdlist["-AuthModule-"] = []
        for ac in self.irc.auth.cmdlist:
            if not self.typelist.has_key(ac.ctype):
                self.typelist[ac.ctype] = {}
            if ac.has_cmd():
                self.typelist[ac.ctype][ac.command] = {}
                self.typelist[ac.ctype][ac.command]["-AuthModule-"] = ac
            else:
                self.typelist[ac.ctype] = {}
                self.typelist[ac.ctype]["-AuthModule-"] = ac
            self.cmdlist["-AuthModule-"].append(ac)
                
        self.modlist["-AuthModule-"] = self.irc.auth # for the helpstring ;-)    
        
        self.cmdlist["-ModuleManager-"] = [pm, ph]
        self.modlist["-ModuleManager-"] = self
        
    def __del__(self):
        nlist = []
        for i in self.classlist:
            nlist.append(i)
        for i in nlist:
            self.ondelete(i)
            del self.classlist[i]
            
        
    def pmodules(self, msg):
        "[add, del] Activate or deactivate modules from IRC"
        if len(msg.arglist) < 2:
            return False
        name = msg.arglist[1]
        if msg.arglist[0] == "add":
            retval = self.addmod(name)
            if retval:
                xsend(self.irc, msg.sender.nick, msg.channel, "Successfully loaded module "+name+".")
            else:
                xsend(self.irc, msg.sender.nick, msg.channel, "Error while loading module "+name+".")
        elif msg.arglist[0] == "del":
            self.delmod(name)
            xsend(self.irc, msg.sender.nick, msg.channel, "Successfully deactivated module "+name+".")
        return True
            
    def p_version(self, msg):
        helpc = Help(self)
        self.irc.notice(msg.sender.nick, helpc.version(self.irc))
        return True
            
    def gethelp (self, name, module = False):
        if module:
            if self.modlist.has_key(name):
                return helpstr(self.modlist[name])
                
        for i in (types.MSG, types.DMSG, types.NOTICE):
            if self.typelist.has_key[i]:
                if self.typelist[i].has_key(name):
                    return helpstr(self.typelist[i][name].values()[0].get_fcn())
        
    def p_help(self, msg):
        "Show this help"
        helpc = Help(self)
        if not len (msg.arglist):
            if msg.channel[0] == '#':
                dest   = msg.channel
                prefix = msg.sender.nick+": "
            else:
                dest   = msg.sender.nick
                prefix = ""
            if not prefix:
                self.irc.notice(dest, helpc.helpstr)
            else:
                self.irc.msg(dest, prefix+helpc.helpstr)
            self.irc.notice(msg.sender.nick, helpc.helpstr2)
            self.irc.notice(msg.sender.nick, helpc.helpstr3)
        else:
            if msg.arglist[0] == "modules":
                mylist = []
                modlist = self.modlist.keys()
                modlist.sort()
                for i in modlist:
                    mylist.extend(helpc.module(i))
                self.irc.notice(msg.sender.nick, "Modules listing:")
                for i in mylist:
                    self.irc.notice(msg.sender.nick, i)
            elif msg.arglist[0] == "all":
                mylist = []
                modlist = self.modlist.keys()
                modlist.sort()
                for i in modlist:
                    mylist.extend(helpc.module(i, True))
                self.irc.notice(msg.sender.nick, "Long modules and commands listing:")
                for i in mylist:
                    self.irc.notice(msg.sender.nick, i)
            elif self.modlist.has_key(msg.arglist[0]):
                modlist = helpc.module(msg.arglist[0], True)
                for i in modlist:
                    self.irc.notice(msg.sender.nick, i)
            else:
                self.irc.notice(msg.sender.nick, helpc.cmd_long(msg.arglist[0]))
            return True
        
    def addmod (self, name): # name is the modname, and it's saved in the database under this name.
        "Loads module if it's not already loaded, adds it to the database, activates it and reads its given commands"
        if self.classlist.has_key(name) and self.activelist[name]:
            self.ondelete(name)
            themod = reload (self.modlist[name])
            for i in self.typelist:
                # i contains the different types now
                if i == types.MSG or i == types.NOTICE or i == types.DMSG:
                    for j in self.typelist[i]:
                        # j contains the func name
                        if self.typelist[i][j].has_key(name):
                            del (self.typelist[i][j][name])
                else:
                    if self.typelist[i].has_key(name):
                        del (self.typelist[i][name])
            for i in self.cmdlist[name]:
                del i
            # Ok, cleaned up.
        else:
            themod = None
            try:
                modules = __import__("modules."+name) # (we hope that works ;-)
                modules = reload(modules)
                themod  = getattr(modules, name)
            except:
                print "Sorry, import error while importing "+str(name)+" module: "+str(sys.exc_info()[0])
                return False
                
        self.modlist[name] = themod

        there_is_parse = "parse" in dir(themod) and "register" in dir(themod.parse) # register gives us all its commands.
        if there_is_parse:
            try:
                self.classlist[name] = themod.parse(self.irc) # parse gets the irc object as argument.
            except:
                print "Sorry, import error while importing "+str(name)+" module: "+str(sys.exc_info()[0])
                return False
        else:
            print "Sorry, import error while importing "+str(name)+" module: module not valid"
            return False

        self.activelist[name] = True
        
        self.cmdlist[name] = self.classlist[name].register()
        for cmd in self.cmdlist[name]:
            if not self.typelist.has_key(cmd.ctype):
                self.typelist[cmd.ctype] = {}
            if cmd.has_cmd():
                # It's the same for DMSGs as for Channel PRIVMSGs here.
                if not self.typelist[cmd.ctype].has_key(cmd.command):
                    self.typelist[cmd.ctype][cmd.command] = {}
                self.typelist[cmd.ctype][cmd.command][cmd.module] = cmd
            elif cmd.ctype == types.MSG or cmd.ctype == types.DMSG or cmd.ctype == types.NOTICE or cmd.ctype == types.ALL:
                if not self.typelist[cmd.ctype].has_key(""):
                    self.typelist[cmd.ctype][""] = {}
                self.typelist[cmd.ctype][""][cmd.module] = cmd
            else:
                self.typelist[cmd.ctype][cmd.module] = cmd
        
        return True
        
    
    def ondelete(self, name):
        try:
            self.classlist[name].ondelete()
        except:
            pass

    
    def delmod (self, name):
        "Deactivates the given module"
        self.ondelete(name)
        self.activelist[name] = False
        for cmd in self.cmdlist[name]:
            if cmd.has_cmd():
                self.typelist[cmd.ctype][cmd.command][cmd.module].active = False
            elif cmd.ctype == types.MSG or cmd.ctype == types.DMSG or cmd.ctype == types.NOTICE or cmd.ctype == types.ALL:
                self.typelist[cmd.ctype][""][cmd.module].active = False
            else:
                self.typelist[cmd.ctype][cmd.module].active = False
        #del self.cmdlist[name]
        
    def call (self, cmd, msg):
        retval = False
        try:
            #print "Let "+i+" parse msg..."
            retval = getattr(cmd.clas, cmd.fcn).__call__(msg)
        except NameError:
            exc_info = sys.exc_info()
            print "NameError: "+str(cmd.command)+"/"+str(cmd.fcn)+" of "+cmd.module+" - message: "+str(exc_info[1])
            print "Line: "+str(exc_info[2].tb_lineno)
            return False
        except (SystemExit, IRCQuit), e:
            exc_info = sys.exc_info()
            print "Raised", str(exc_info[0])
            raise
        except:
            exc_info = sys.exc_info()
            print "Error ("+str(exc_info[0])+") in "+str(cmd.command)+"/"+str(cmd.fcn)+" of "+cmd.module+" - message: "+str(exc_info[1])
            print "Line: "+str(exc_info[2].tb_lineno)
            return False
        return retval
        
    def parse(self, msg):
        mtype = msg.mtype
        if msg.dmsg: mtype = types.DMSG
        if not self.typelist.has_key(mtype):
            self.typelist[mtype] = {}
        if msg.has_cmd():
            if not self.typelist[mtype].has_key(msg.command):
                if (mtype == types.MSG or mtype == types.DMSG or mtype == types.NOTICE) and self.typelist[mtype].has_key("") and len(self.typelist[mtype][""]) > 0:
                    cmd = self.typelist[mtype][""]
                elif self.typelist[types.ALL].has_key(msg.command):
                    cmd = self.typelist[types.ALL][msg.command]
                else:
                    cmd = self.typelist[types.ALL][""]
            else:
                cmd = self.typelist[mtype][msg.command]
        elif (mtype == types.MSG or mtype == types.DMSG or mtype == types.NOTICE):
            if self.typelist[mtype].has_key("") and len(self.typelist[mtype][""]) > 0:
                cmd = self.typelist[mtype][""]
            else:
                cmd = self.typelist[types.ALL][""]
        else:
            cmd = self.typelist[mtype]
            if len(cmd) == 0:
                cmd = self.typelist[types.ALL][""]


        for i in cmd:
            if not cmd[i].active or (cmd[i].access and (self.irc.auth.is_authed(msg.sender.nick) < cmd[i].access)):
                continue
            if self.call(cmd[i], msg):
                break
        
        
